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

Posted atheva

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于spring多数据源动态调用及其事务处理相关的知识,希望对你有一定的参考价值。

需求:

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

下图的单数据源的调用和多数据源动态调用的流程,可以看出在Dao层中需要有一个DataSource选择器,来确定到底是调用哪个数据源。

技术分享

实现方式

对Dao层提供一个公共父类,保持有多个数据源的连接(本人是基于iBatis,即保持多个SQLSessionTemplate)

/**
 * Created by hzlizhou on 2017/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中的

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

@Documented
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface DynamicTransactional {

    Propagation propagation() default Propagation.REQUIRED;

    Class<? extends Throwable>[] rollbackFor() default {};
}

  

基本思想是在通过AOP切面拦截@DynamicTransactional注解,调用,然后自己编程实现事务

技术分享

切面内的核心方法是

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写一个,

以上是关于基于spring多数据源动态调用及其事务处理的主要内容,如果未能解决你的问题,请参考以下文章

多数据源动态配置及事务控制

IOC 控制反转Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 创建 事件监听器 对应的 动态代理 | 动态代理的数据准备 | 创建调用处理程序 | 创建动态代理实例对象 )(代码片

基于Spring+Mybatis的多数据源动态切换

Spring事务传播机制

Spring Aop基于注解的实现

Spring动态配置多数据源的基于spring和ibatis的多数据源切换方案