zoukankan      html  css  js  c++  java
  • 基于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写一个,

  • 相关阅读:
    实现Vector对象的序列化的例子
    BigDecimal
    java.io.Serializable引发的问题——什么是序列化?在什么情况下将类序列化?
    删除表中重复记录的方法
    使用PreparedStatement为不同的数据库编写可移植的数据库存取方法
    hsqldb介绍
    ant管理项目
    在jsp中点击按钮,在bean中把已经查出的数据,生成csv文件,然后在ie中自动打开
    用JAVA操作日期类型
    ORACLE默认用户的问题?
  • 原文地址:https://www.cnblogs.com/lizo/p/7089263.html
Copyright © 2011-2022 走看看