上一篇的DynamicDataSource展示了实现多数据源切换的思路,实际上MP已经有封装好的多数据源切换框架了,它比手动实现的功能要多一些。
实现与原理
引入jar包
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
手动创建master和demo两个数据源,配置DynamicRoutingDataSource
@Bean(name = "dynamicDataSource")
@DependsOn({"master", "demo"})
@Primary
public DynamicRoutingDataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
// 配置多数据源
dynamicDataSource.addDataSource("master", mainSource());
dynamicDataSource.addDataSource("demo", demoSource());
dynamicDataSource.setPrimary("master");
// 读取yml中配置的数据源,本篇没有用到
dynamicDataSource.setProvider(dynamicDataSourceProvider);
return dynamicDataSource;
}
关于DynamicRoutingDataSource读取yml配置自动添加数据源,参见官方文档。
获取连接时,入口方法仍然是getConnection()。
DynamicRoutingDataSource->getConnection():
String xid = TransactionContext.getXID();
// 此时必然进if分支
if (StringUtils.isEmpty(xid)) {
// 获得数据源并调用它的getConnection()方法
return determineDataSource().getConnection();
} else {
......
}
DynamicRoutingDataSource->determineDataSource():
String dsKey = DynamicDataSourceContextHolder.peek();
return getDataSource(dsKey);
// 根据key返回数据源
DynamicRoutingDataSource->getDataSource():
if (StringUtils.isEmpty(ds)) {
return determinePrimaryDataSource();
} else if {
......
}
} else if (dataSourceMap.containsKey(ds)) {
return dataSourceMap.get(ds);
}
DynamicDataSourceContextHolder内部以栈形式存储,后进先出,使用push(String ds)设置当前数据源(入栈),使用peek()获得当前线程数据源(获得栈顶值),没有设置的话则返回主数据源,使用poll()清空当前数据源(出栈),使用clear()清空栈值,调用push需要确保最终清空(入栈需出栈)。
@DS
@DS注解可用在类或方法,作用是自动切换数据源。
它的Advisor是DynamicDataSourceAnnotationAdvisor,当类或方法存在@DS注解时满足切面表达式,拦截器是DynamicDataSourceAnnotationInterceptor。
DynamicDataSourceAnnotationInterceptor->invoke():
// 获得注解值
String dsKey = determineDatasourceKey(invocation);
// 设置数据源
DynamicDataSourceContextHolder.push(dsKey);
try {
// 执行过程
return invocation.proceed();
} finally {
// 清空数据源
DynamicDataSourceContextHolder.poll();
}
@DSTransactional
@DSTransactional是多数据源事务,多个数据源一起提交或者一起回滚,可用在类或方法。
它的定义位于DynamicDataSourceAutoConfiguration类,当类或方法存在@DSTransactional注解时满足切面表达式,拦截器是DynamicLocalTransactionAdvisor。
DynamicLocalTransactionAdvisor->invoke():
// xid是随机字符串,只要有值就是事务
if (!StringUtils.isEmpty(TransactionContext.getXID())) {
return methodInvocation.proceed();
}
boolean state = true;
// 设置随机字符串xid
String xid = UUID.randomUUID().toString();
TransactionContext.bind(xid);
try {
o = methodInvocation.proceed();
} catch (Exception e) {
state = false;
throw e;
} finally {
ConnectionFactory.notify(state); // --1
TransactionContext.remove();
}
return o;
多数据源事务获取连接时,入口方法仍然是getConnection()。
DynamicRoutingDataSource->getConnection():
String xid = TransactionContext.getXID();
// 此时必然进else分支
......
} else {
// 当前数据源
String ds = DynamicDataSourceContextHolder.peek();
// 缓存Map集合,key是数据源名称,value是数据源
ConnectionProxy connection = ConnectionFactory.getConnection(ds);
// 缓存Map集合没有的话,实时获取
return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;
}
AbstractRoutingDataSource->getConnectionProxy():
ConnectionProxy connectionProxy = new ConnectionProxy(connection, ds);
ConnectionFactory.putConnection(ds, connectionProxy); // --2
return connectionProxy;
ConnectionFactory->putConnection(): // --2
Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get();
if (!concurrentHashMap.containsKey(ds)) {
try {
// 设置自动提交为false
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
concurrentHashMap.put(ds, connection);
}
ConnectionFactory->notify(): // --1
Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get();
// 遍历所涉及到的数据源
for (ConnectionProxy connectionProxy : concurrentHashMap.values()) {
connectionProxy.notify(state);
}
ConnectionProxy->notify():
// 根据state的值决定是提交还是回滚
if (commit) {
connection.commit();
} else {
connection.rollback();
}
// 回收的时候会将自动提交属性还原
connection.close();
@DSTransactional不像@Transactional那样可以定义回滚异常与非回滚异常,也没有事务的传播机制,但是它提供了对多数据源事务的支持。