基于spring多数据源动态调用及其事务处理

需求:

有些时候,我们需要连接多个数据库,但是,在方法调用前并不知道到底是调用哪个。即同时保持多个数据库的连接,在方法中根据传入的参数来确定。

下图的单数据源的调用和多数据源动态调用的流程,可以看出在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写一个

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值