之前MP系列讲的都是单数据源,在业务开发中很多场景都需要用到不同数据源的情况,本篇将讲述多数据源实现之一的AbstractRoutingDataSource
。
实现与原理
AbstractRoutingDataSource是DataSource接口的实现类,位于spring-jdbc
jar包,它是一个抽象类,子类需要实现其determineCurrentLookupKey()
方法,顾名思义,这是一个根据路由定位数据源的类。
首先需要自定义一个类来实现AbstractRoutingDataSource抽象类,本例中是DynamicDataSource
。
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
log.debug("数据源为{}", DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
}
}
随后分别手动创建数据源,本例中创建了master和demo两个数据源,它们返回的都是DruidDataSourceWrapper
。
@Bean(name = "master")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource mainSource() {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return dataSource;
}
@Bean(name = "demo")
@ConfigurationProperties(prefix = "spring.datasource.demo")
public DataSource demoSource() {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return dataSource;
}
配置DynamicDataSource,将多数据源以map形式进行管理,最后注入IoC容器。
/**
* 在实例化dynamicDataSource之前,需要保证master和demo数据源已注入IoC容器
* @Primary必不可少,多个数据源的前提下,引入MybatisPlusAutoConfiguration时需要有一个@Primary数据源才会后续注入SqlSessionFactory
* 且依赖注入DataSource时,返回DynamicDataSource
*/
@Bean(name = "dynamicDataSource")
@DependsOn({"master", "demo"})
@Primary
public DynamicDataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 配置多数据源
Map<Object, Object> dsMap = new HashMap<>(5);
// 这里的key需要与调用determineCurrentLookupKey()方法的返回值相对应
dsMap.put("master", mainSource());
dsMap.put("demo", demoSource());
dynamicDataSource.setTargetDataSources(dsMap);
// 设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(mainSource());
return dynamicDataSource;
}
获取连接时,入口方法仍然是getConnection()
。
AbstractRoutingDataSource->getConnection():
// 先获取数据源,再调用数据源的getConnection()方法
return determineTargetDataSource().getConnection();
AbstractRoutingDataSource->determineTargetDataSource():
// 调用子类DynamicDataSource的determineCurrentLookupKey()方法
Object lookupKey = determineCurrentLookupKey();
// 根据key获得数据源,这里是DruidDataSourceWrapper
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
return dataSource;
DynamicDataSource在启动阶段将数据源以Map形式存储,获取连接的时候根据key返回对应的数据源,再调用数据源的getConnection()
方法,完成数据源的切换。
事务下的数据源
在事务中曾介绍过,新建事务时决定了数据源与连接对象,多数据源的情况下,此时determineCurrentLookupKey()
方法返回空,但因为设置了默认数据源,所以返回默认数据源的连接。
DataSourceUtils->doGetConnection():
// 获取连接对象
// 如果conHolder不为空,说明处于事务下
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
// 没有连接对象则确定连接对象,一般情况下不会发生
if (!conHolder.hasConnection()) {
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
......
// 以非事务的方式获取数据源,调用DynamicDataSource的getConnection()方法,每次都会确定数据源
Connection con = fetchConnection(dataSource);
从代码中得出结论,同一事务下不支持数据源的切换,这一点需要注意。