基於Spring的多數據源動態調用,多事務動態選擇

原創地址:http://www.javashuo.com/article/p-uiycfbai-ev.htmlsql

需求:

有些時候,咱們須要鏈接多個數據庫,可是,在方法調用前並不知道究竟是調用哪一個。即同時保持多個數據庫的鏈接,在方法中根據傳入的參數來肯定。數據庫

下圖的單數據源的調用和多數據源動態調用的流程,能夠看出在Dao層中須要有一個DataSource選擇器,來肯定究竟是調用哪一個數據源。編程

實現方式

對Dao層提供一個公共父類,保持有多個數據源的鏈接(本人是基於iBatis,即保持多個SQLSessionTemplate)ide

/**
 * 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中的(若是你看過Dubbo傳遞上下文的那篇文章就知道爲何了)函數

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的時候就是動態了。this

動態事務

其實這個都好辦,而後咱們就面臨一個稍微複雜一點的問題,那DataSource是動態的,事務也就必須是動態了的。並且還對原有的代碼沒有侵入(例如Spring中的@Transactional 註解),那實現一個相似@Transactional的方法吧。名字就叫作@DynamicTransactionalspa

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

    Propagation propagation() default Propagation.REQUIRED;

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

基本思想是在經過AOP切面攔截@DynamicTransactional註解,調用,而後本身編程實現事務.net

切面內的核心方法是orm

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返回blog

切面的配置:重點是怎麼對指定註解進行切面

<aop:config>

    <aop:aspect id="multiTransactionManagerAspect" ref="multiTransactionManagerAop">

        <aop:around method="invokeWithinTransaction"

                             arg-names="dynamicTransaction"

                             pointcut="@annotation(dynamicTransaction)"/>

    </aop:aspect>

</aop:config>

固然,這裏只是實現了在方法上的@DynamicTransactional使用,若是該註解還要對類使用,對全部函數加一個切點,判斷該切點的類上是否有@DynamicTransactional註解

注意:因爲切面的優先級,若是要實現 方法上的註解優先級高於類上的,還須要一點點小的處理 

調用時序圖

 

------------------------------------------2017年2月10日 更新---------------------------------------

 

更優雅的實現方式

本身實現基於AbstractRoutingDataSource,把多個DataSource加入到SQLSessionFactory,和以前的方式同樣,經過ThreadLocal來肯定使用哪一個DataSource。

關於動態事務,上面是使用切面,自定義標籤,使用TransactionTemplate來實現的,若是想更優雅的話,能夠仿照DataSourceTransactionManager寫一個,不過先要知曉裏面的原理。

相關文章
相關標籤/搜索