需求:
有些时候,我们需要连接多个数据库,但是,在方法调用前并不知道到底是调用哪个。即同时保持多个数据库的连接,在方法中根据传入的参数来确定。
下图的单数据源的调用和多数据源动态调用的流程,可以看出在Dao层中需要有一个DataSource选择器,来确定到底是调用哪个数据源。
实现方式
对Dao层提供一个公共父类,保持有多个数据源的连接(本人是基于iBatis,即保持多个SQLSessionTemplate)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/**
* Created by hzlizhou on 2018/2/6.
*/
public
abstract
class
MultiDatasourceDao
implements
IDaoSupport {
private
Map<String, SqlSessionTemplate> sqlSessionTemplateMap;
private
MultiDataSourceSelector multiDataSourceSelector;
public
MultiDatasourceDao(Map<String, SqlSessionTemplate> sqlSessionTemplateMap, MultiDataSourceSelector multiDataSourceSelector) {
this
.sqlSessionTemplateMap = sqlSessionTemplateMap;
this
.multiDataSourceSelector = multiDataSourceSelector;
}
public
Map<String, SqlSessionTemplate> getSqlSessionTemplateMap() {
return
sqlSessionTemplateMap;
}
public
void
setSqlSessionTemplateMap(Map<String, SqlSessionTemplate> sqlSessionTemplateMap) {
this
.sqlSessionTemplateMap = sqlSessionTemplateMap;
}
//子类通过这个方法动态获取SqlSessionTemplate
protected
SqlSessionTemplate getSqlSessionTemplate() {
String clusterName = multiDataSourceSelector.getName();
SqlSessionTemplate result = sqlSessionTemplateMap.get(clusterName);
Assert.notNull(result);
return
result;
}
}
|
MultiDataSourceSelector是一个借口,根据当前的调用环境,返回不不同的参数,根据这个参数就可以确定使用哪一个SQLSessionTemplate,例如我是把当前环境放入到ThreadLocal中的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
interface
MultiDataSourceSelector {
String getName();
}
public
class
DubboContextDataSourceSelector
implements
MultiDataSourceSelector {
private
String defaultName;
public
DubboContextDataSourceSelector(String defaultName) {
this
.defaultName = defaultName;
}
@Override
public
String getName() {
//DubboContextHolder 是一个保持一个ThreadLocal的Map
String res = DubboContextHolder.getContext().get(DubboContextConstants.CLUSTER_NAME);
if
(res ==
null
) {
res = getDefaultName();
}
return
res;
}
public
String getDefaultName() {
return
defaultName;
}
}
|
然后在Dao层的中获取SQLSessionTemplate的时候就是动态了。
动态事务
其实这个都好办,然后我们就面临一个稍微复杂一点的问题,那DataSource是动态的,事务也就必须是动态了的。而且还对原有的代码没有侵入(例如Spring中的@Transactional 注解),那实现一个类似@Transactional的方法吧。名字就叫做@DynamicTransactional
1
2
3
4
5
6
7
8
9
|
@Documented
@Target
({METHOD, TYPE})
@Retention
(RUNTIME)
public
@interface
DynamicTransactional {
Propagation propagation()
default
Propagation.REQUIRED;
Class<?
extends
Throwable>[] rollbackFor()
default
{};
}
|
基本思想是在通过AOP切面拦截@DynamicTransactional注解,调用,然后自己编程实现事务
切面内的核心方法是
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
private
Object invokeWithinTransaction(
final
ProceedingJoinPoint pjp,
final
DynamicTransactional dynamicTransaction) {
//创建TransactionTemplate
final
PlatformTransactionManager tran = multiTransactionManagerHolder.getTransactionManager();
TransactionTemplate transactionTemplate =
new
TransactionTemplate();
transactionTemplate.setPropagationBehavior(dynamicTransaction.propagation().value());
transactionTemplate.setTransactionManager(tran);
//在事务中执行
return
transactionTemplate.execute(
new
TransactionCallback<Object>() {
@Override
public
Object doInTransaction(TransactionStatus status) {
Object result =
null
;
try
{
result = pjp.proceed();
}
catch
(Throwable throwable) {
Class<?
extends
Throwable>[] c = dynamicTransaction.rollbackFor();
for
(Class<?
extends
Throwable> tmp : c) {
if
(tmp.isAssignableFrom(throwable.getClass())) {
status.setRollbackOnly();
}
}
}
return
result;
}
});
}
|
其中multiTransactionManagerHolder和上面动态数据源选择的原理一样,通过从ThreadLocal中拿去变量,选择对应的TransactionManager返回
切面的配置:重点是怎么对指定注解进行切面
<aop:config>
<aop:aspect id="multiTransactionManagerAspect" ref="multiTransactionManagerAop">
<aop:around method="invokeWithinTransaction"
arg-names="dynamicTransaction"
pointcut="@annotation(dynamicTransaction)"/>
</aop:aspect>
</aop:config>
当然,这里只是实现了在方法上的@DynamicTransactional使用,如果该注解还要对类使用,对所有函数加一个切点,判断该切点的类上是否有@DynamicTransactional注解
注意:由于切面的优先级,如果要实现 方法上的注解优先级高于类上的,还需要一点点小的处理
调用时序图
自己实现基于AbstractRoutingDataSource,把多个DataSource加入到SQLSessionFactory,和之前的方式一样,通过ThreadLocal来确定使用哪个DataSource。
关于动态事务,上面是使用切面,自定义标签,使用TransactionTemplate来实现的,如果想更优雅的话,可以仿照DataSourceTransactionManager写一个