### 实现原理 >Spring内置了一个AbstractRoutingDataSource类,它可以把多个数据源配置成一个Map,然后根据不同的key返回不同的数据源(通过实现抽象方法determineCurrentLookupKey)。 >我们在使用AbstractRoutingDataSource时,主要涉及两个操作: >1. 生成数据源相关map,并赋值给AbstractRoutingDataSource的targetDataSources >2. 实现determineCurrentLookupKey方法(方法的用途为:获取要使用数据源的key) ### 配置多数据源 首先,我们在SpringBoot中配置两个数据源,其中第二个数据源是slave-datasource: ```yaml spring: datasource: jdbc-url: jdbc:mysql://localhost/master?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true username: root #master password: dreamlyn #master_password driver-class-name: com.mysql.cj.jdbc.Driver hikari: pool-name: HikariCP auto-commit: false slave-datasource: jdbc-url: jdbc:mysql://localhost/slave?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true username: root #slave password: dreamlyn #slave_password driver-class-name: com.mysql.cj.jdbc.Driver hikari: pool-name: HikariCP auto-commit: false ``` 在SpringBoot的配置代码中,初始化两个数据源: ```java /** * Master data source. */ @Bean("masterDataSource") @ConfigurationProperties(prefix = "spring.datasource") DataSource masterDataSource() { return DataSourceBuilder.create().build(); } /** * Slave data source. */ @Bean("slaveDataSource") @ConfigurationProperties(prefix = "spring.slave-datasource") DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } ``` ### 生成数据源相关map 将两个数据源生成相关map,并赋值给AbstractRoutingDataSource的targetDataSources ```java @Bean @Primary DataSource primaryDataSource( @Autowired @Qualifier("masterDataSource") DataSource masterDataSource, @Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource ) { Map map = new HashMap<>(); map.put("master", masterDataSource); map.put("slave", slaveDataSource); DynamicDataSource routing = new DynamicDataSource(); routing.setTargetDataSources(map); routing.setDefaultTargetDataSource(masterDataSource); return routing; } ``` ### 实现determineCurrentLookupKey方法 ```java /** * 动态数据源 */ public class DynamicDataSource extends AbstractRoutingDataSource { /** * 指定路由Key */ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSource(); } } ``` 大家可能会好奇DynamicDataSourceContextHolder是个什么鬼: 在Servlet的线程模型中,使用ThreadLocal存储key最合适,因此,我们编写一个DynamicDataSourceContextHolder来设置并动态存储key: ```java /** * 根据当前线程来选择具体的数据源 */ @UtilityClass public class DynamicDataSourceContextHolder { private final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); /** * 提供给AOP去设置当前的线程的数据源的信息 */ public void setDataSource(String dataSourceType) { CONTEXT_HOLDER.set(dataSourceType); } /** * 提供给AbstractRoutingDataSource的实现类,通过key选择数据源 */ public String getDataSource() { return CONTEXT_HOLDER.get(); } /** * 清除数据源 */ public void clearDataSource() { CONTEXT_HOLDER.remove(); } } ``` 这样,在某个地方,就可以动态设置DataSource的Key: ```java @RunWith(SpringRunner.class) @SpringBootTest @Slf4j @MapperScan("com.dreamlyn.dynamic.mapper") class DynamicApplicationTests { @Autowired private MenuMapper menuMapper; @Autowired private UserMapper userMapper; @Test void dynamic() { //设置数据源 DynamicDataSourceContextHolder.setDataSource("master"); List users = userMapper.selectList(null); log.info("user count:{}", users.size()); DynamicDataSourceContextHolder.setDataSource("slave"); List menus = menuMapper.selectList(null); log.info("menu count:{}", menus.size()); } } ``` 到此为止,我们已经成功实现了数据库的动态路由访问。 ### 拓展 如果需要更方便一些,可以自己手动编写aop,通过注解来区分使用哪个数据源将更加方便,这种方法留给读者自己思考。 思考不出来可以参考代码:[https://gitee.com/dreamlyn/dynamic](https://gitee.com/dreamlyn/dynamic)