在相對複雜的應用服務中,配置多個數據源是常見現象,例如常見的:配置主從數據庫用來寫數據,再配置一個從庫讀數據,這種讀寫分離模式能夠緩解數據庫壓力,提升系統的併發能力和穩定性,執行效率。java
在處理這種常見問題,要學會查詢服務基礎框架的API,說直白點就是查詢Spring框架的API(工做幾年,還沒用過Spring以外的框架搭建環境),這種經常使用的業務模式,基本上Spring都提供了API支持。mysql
核心API:AbstractRoutingDataSourcegit
底層維護Map容器,用來保存數據源集合,提供一個抽象方法,實現自定義的路由策略。github
@Nullableprivate Map<Object, DataSource> resolvedDataSources;@Nullableprotected abstract Object determineCurrentLookupKey();
補刀一句
:爲什麼框架的原理很難經過一篇文章看明白?由於使用的很少,基本意識沒有造成,熟悉框架原理的基本要求:對框架的各類功能都熟悉,常常使用,天然而然的就明白了,鹽大曬的久,鹹魚纔夠味。web
配置兩個數據源spring
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.jdbc.Driver master: url: jdbc:mysql://localhost:3306/data_master username: root password: 123456 slave: url: jdbc:mysql://localhost:3306/data_slave username: root password: 123456
從實際開發角度,這兩個數據源須要配置主從複製流程,再基於安全角度,寫庫能夠只給寫權限,讀庫只給讀權限。sql
Map容器加載數據庫
@Configurationpublic class DruidConfig { // 忽略參數加載,源碼中有 @Bean @Primary public DataSource primaryDataSource() { Map<Object, Object> map = new HashMap<>(); map.put("masterDataSource", masterDataSource()); map.put("slaveDataSource", slaveDataSource()); RouteDataSource routeDataSource = new RouteDataSource(); routeDataSource.setTargetDataSources(map); routeDataSource.setDefaultTargetDataSource(masterDataSource()); return routeDataSource ; } private DataSource masterDataSource() { return getDefDataSource(masterUrl,masterUsername,masterPassword); } private DataSource slaveDataSource() { return getDefDataSource(slaveUrl,slaveUsername,slavePassword); } private DataSource getDefDataSource (String url,String userName,String passWord){ DruidDataSource datasource = new DruidDataSource(); datasource.setDriverClassName(driverClassName); datasource.setUrl(url); datasource.setUsername(userName); datasource.setPassword(passWord); return datasource; } }
這裏的Map容器管理兩個key,masterDataSource和slaveDataSource表明兩個不一樣的庫,使用不一樣的key即加載對應的庫。安全
使用ThreadLocal管理當前會會話中線程參數,存取使用極其方便。併發
public class RouteContext implements AutoCloseable { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void setRouteKey (String key){ threadLocal.set(key); } public static String getRouteKey() { String key = threadLocal.get(); return key == null ? "masterDataSource" : key; } @Override public void close() { threadLocal.remove(); } }
獲取ThreadLocal中,當前數據源的key,適配相關聯的數據源。鄭州的不孕不育醫院哪家好:http://jbk.39.net/yiyuanfengcai/tsyl_zztjyy/3031/
public class RouteDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return RouteContext.getRouteKey(); } }
基於AOP的切面思想,不一樣的方法類型,去設置對應路由Key,這樣就能夠在業務邏輯執行以前,切換到不一樣的數據源。
Aspect@Component@Order(1)public class ReadWriteAop { private static Logger LOGGER = LoggerFactory.getLogger(ReadWriteAop.class) ; @Before("execution(* com.master.slave.controller.*.*(..))") public void setReadDataSourceType() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String method = request.getRequestURI() ; boolean rwFlag = readOrWrite(method) ; if (rwFlag){ RouteContext.setRouteKey("slaveDataSource"); } else { RouteContext.setRouteKey("masterDataSource"); } LOGGER.info("請求方法:"+method+";執行庫:"+RouteContext.getRouteKey()); } private String[] readArr = new String[]{"select","count","query","get","find"} ; private boolean readOrWrite (String method){ for (String readVar:readArr) { if (method.contains(readVar)){ return true ; } } return false ; } }
常見的讀取方法:select、count、query、get、find等等,方法的命名要遵循自定義的路由規則。鄭州人工授精醫院:http://rgsj.zzfkyy120.com/
控制層API
import com.master.slave.entity.User;import com.master.slave.service.UserService;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestControllerpublic class UserController { @Resource private UserService userService ; @GetMapping("/selectById") public User selectById (@RequestParam("id") Integer id) { return userService.selectById(id) ; } @GetMapping("/insert") public String insert () { User user = new User("張三","write") ; userService.insert(user) ; return "success" ; } }
服務實現
@Servicepublic class UserService { @Resource private UserMapper userMapper ; public User selectById (Integer id) { return userMapper.selectById(id) ; } public void insert (User user){ userMapper.insert(user); } }
這樣數據源基於不一樣的類型方法就會一直的動態切換。
GitHub·地址https://github.com/cicadasmile/data-manage-parentGitEE·地址https://gitee.com/cicadasmile/data-manage-parent