詳解基于spring多數(shù)據(jù)源動(dòng)態(tài)調(diào)用及其事務(wù)處理
需求:
有些時(shí)候,我們需要連接多個(gè)數(shù)據(jù)庫(kù),但是,在方法調(diào)用前并不知道到底是調(diào)用哪個(gè)。即同時(shí)保持多個(gè)數(shù)據(jù)庫(kù)的連接,在方法中根據(jù)傳入的參數(shù)來確定。
下圖的單數(shù)據(jù)源的調(diào)用和多數(shù)據(jù)源動(dòng)態(tài)調(diào)用的流程,可以看出在Dao層中需要有一個(gè)DataSource選擇器,來確定到底是調(diào)用哪個(gè)數(shù)據(jù)源。

實(shí)現(xiàn)方式
對(duì)Dao層提供一個(gè)公共父類,保持有多個(gè)數(shù)據(jù)源的連接(本人是基于iBatis,即保持多個(gè)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;
}
//子類通過這個(gè)方法動(dòng)態(tài)獲取SqlSessionTemplate
protected SqlSessionTemplate getSqlSessionTemplate() {
String clusterName = multiDataSourceSelector.getName();
SqlSessionTemplate result = sqlSessionTemplateMap.get(clusterName);
Assert.notNull(result);
return result;
}
}
MultiDataSourceSelector是一個(gè)借口,根據(jù)當(dāng)前的調(diào)用環(huán)境,返回不不同的參數(shù),根據(jù)這個(gè)參數(shù)就可以確定使用哪一個(gè)SQLSessionTemplate,例如我是把當(dāng)前環(huán)境放入到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 是一個(gè)保持一個(gè)ThreadLocal的Map
String res = DubboContextHolder.getContext().get(DubboContextConstants.CLUSTER_NAME);
if (res == null) {
res = getDefaultName();
}
return res;
}
public String getDefaultName() {
return defaultName;
}
}
然后在Dao層的中獲取SQLSessionTemplate的時(shí)候就是動(dòng)態(tài)了。
動(dòng)態(tài)事務(wù)
其實(shí)這個(gè)都好辦,然后我們就面臨一個(gè)稍微復(fù)雜一點(diǎn)的問題,那DataSource是動(dòng)態(tài)的,事務(wù)也就必須是動(dòng)態(tài)了的。而且還對(duì)原有的代碼沒有侵入(例如Spring中的@Transactional 注解),那實(shí)現(xiàn)一個(gè)類似@Transactional的方法吧。名字就叫做@DynamicTransactional
@Documented
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface DynamicTransactional {
Propagation propagation() default Propagation.REQUIRED;
Class<? extends Throwable>[] rollbackFor() default {};
}
基本思想是在通過AOP切面攔截@DynamicTransactional注解,調(diào)用,然后自己編程實(shí)現(xiàn)事務(wù)

切面內(nèi)的核心方法是
private Object invokeWithinTransaction(final ProceedingJoinPoint pjp, final DynamicTransactional dynamicTransaction) {
//創(chuàng)建TransactionTemplate
final PlatformTransactionManager tran = multiTransactionManagerHolder.getTransactionManager();
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setPropagationBehavior(dynamicTransaction.propagation().value());
transactionTemplate.setTransactionManager(tran);
//在事務(wù)中執(zhí)行
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和上面動(dòng)態(tài)數(shù)據(jù)源選擇的原理一樣,通過從ThreadLocal中拿去變量,選擇對(duì)應(yīng)的TransactionManager返回
切面的配置:重點(diǎn)是怎么對(duì)指定注解進(jìn)行切面
<aop:config>
<aop:aspect id="multiTransactionManagerAspect" ref="multiTransactionManagerAop">
<aop:around method="invokeWithinTransaction"
arg-names="dynamicTransaction"
pointcut="@annotation(dynamicTransaction)"/>
</aop:aspect>
</aop:config>
當(dāng)然,這里只是實(shí)現(xiàn)了在方法上的@DynamicTransactional使用,如果該注解還要對(duì)類使用,對(duì)所有函數(shù)加一個(gè)切點(diǎn),判斷該切點(diǎn)的類上是否有@DynamicTransactional注解
注意:由于切面的優(yōu)先級(jí),如果要實(shí)現(xiàn) 方法上的注解優(yōu)先級(jí)高于類上的,還需要一點(diǎn)點(diǎn)小的處理
調(diào)用時(shí)序圖
自己實(shí)現(xiàn)基于AbstractRoutingDataSource,把多個(gè)DataSource加入到SQLSessionFactory,和之前的方式一樣,通過ThreadLocal來確定使用哪個(gè)DataSource。

關(guān)于動(dòng)態(tài)事務(wù),上面是使用切面,自定義標(biāo)簽,使用TransactionTemplate來實(shí)現(xiàn)的,如果想更優(yōu)雅的話,可以仿照DataSourceTransactionManager寫一個(gè),
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
通過自定制LogManager實(shí)現(xiàn)程序完全自定義的logger
本章主要闡述怎么完全定制化LogManager來實(shí)現(xiàn)應(yīng)用程序完全自定制的logger,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
Java后臺(tái)實(shí)現(xiàn)微信支付和微信退款
這篇文章主要介紹了Java后臺(tái)實(shí)現(xiàn)微信支付和微信退款,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Spring Security單項(xiàng)目權(quán)限設(shè)計(jì)過程解析
這篇文章主要介紹了Spring Security單項(xiàng)目權(quán)限設(shè)計(jì)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Java基礎(chǔ)-Java編程語(yǔ)言發(fā)展史
這篇文章主要介紹了Java基礎(chǔ)-Java編程語(yǔ)言發(fā)展簡(jiǎn)史,Java源自Sun公司的一個(gè)叫Green的項(xiàng)目,其原先的目的是為家用電子消費(fèi)產(chǎn)品開發(fā)一個(gè)分布式代碼系統(tǒng),這樣就可以將通信和控制信息發(fā)給電冰箱、電視機(jī)、烤面包機(jī)等家用電器,對(duì)它們進(jìn)行控制和信息交流,需要的朋友可以參考一下2022-01-01
SpringBoot+Spring Security基于內(nèi)存用戶認(rèn)證的實(shí)現(xiàn)
本文介紹了SpringBoot+Spring Security基于內(nèi)存用戶認(rèn)證的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
Java觀察者設(shè)計(jì)模式(Observable和Observer)
這篇文章主要介紹了 Java觀察者設(shè)計(jì)模式(Observable和Observer)的相關(guān)資料,需要的朋友可以參考下2015-12-12
詳解Java編程中線程同步以及定時(shí)啟動(dòng)線程的方法
這篇文章主要介紹了詳解Java編程中線程同步以及定時(shí)啟動(dòng)線程的方法, 講到了wait()與notify()方法以及阻塞隊(duì)列等知識(shí),需要的朋友可以參考下2016-01-01

