詳解基于spring多數(shù)據(jù)源動(dòng)態(tài)調(diào)用及其事務(wù)處理
需求:
有些時(shí)候,我們需要連接多個(gè)數(shù)據(jù)庫,但是,在方法調(diào)用前并不知道到底是調(diào)用哪個(gè)。即同時(shí)保持多個(gè)數(shù)據(jù)庫的連接,在方法中根據(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)方式
對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)了的。而且還對原有的代碼沒有侵入(例如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中拿去變量,選擇對應(yīng)的TransactionManager返回
切面的配置:重點(diǎn)是怎么對指定注解進(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使用,如果該注解還要對類使用,對所有函數(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è),
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot 任務(wù)調(diào)度動(dòng)態(tài)設(shè)置方式(不用重啟服務(wù))
這篇文章主要介紹了SpringBoot 任務(wù)調(diào)度 動(dòng)態(tài)設(shè)置方式(不用重啟服務(wù)),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Java使用注解實(shí)現(xiàn)BigDecimal的四舍五入
BigDecimal是Java中的一個(gè)類,位于java.math包中,它提供了任意精度的有符號(hào)十進(jìn)制數(shù)字的表示,以及對這些數(shù)字進(jìn)行算術(shù)運(yùn)算的方法,本文介紹了Java使用注解實(shí)現(xiàn)BigDecimal的四舍五入的相關(guān)知識(shí),需要的朋友可以參考下2024-09-09Spring的@PreAuthorize注解自定義權(quán)限校驗(yàn)詳解
這篇文章主要介紹了Spring的@PreAuthorize注解自定義權(quán)限校驗(yàn)詳解,由于項(xiàng)目中,需要對外開放接口,要求做請求頭校驗(yàn),不做其他權(quán)限控制,所以準(zhǔn)備對開放的接口全部放行,不做登錄校驗(yàn),需要的朋友可以參考下2023-11-11MyBatis配置文件解析與MyBatis實(shí)例演示
這篇文章主要介紹了MyBatis配置文件解析與MyBatis實(shí)例演示以及怎樣編譯安裝MyBatis,需要的朋友可以參考下2022-04-04SpringBoot數(shù)據(jù)訪問的實(shí)現(xiàn)
本文主要介紹了SpringBoot數(shù)據(jù)訪問的實(shí)現(xiàn),引入各種xxxTemplate,xxxRepository來簡化我們對數(shù)據(jù)訪問層的操作,感興趣的可以了解一下2023-11-11SpringBoot Actuator潛在的OOM問題的解決
本文主要介紹了SpringBoot Actuator潛在的OOM問題的解決,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11詳解直接插入排序算法與相關(guān)的Java版代碼實(shí)現(xiàn)
這篇文章主要介紹了直接插入排序算法與相關(guān)的Java版代碼實(shí)現(xiàn),需要的朋友可以參考下2016-05-05