關于Spring多數(shù)據(jù)源TransactionManager沖突的解決方案
現(xiàn)象
近期做了一個業(yè)務需求,需要增加多數(shù)據(jù)源,同時對事務也進行了配置,待發(fā)布上線后出現(xiàn)使用 @Transactional 注解的方法拋出 NoUniqueBeanDefinitionException 異常:
No qualifying bean of type ‘org.springframework.transaction.PlatformTransactionManager’ available: expected single matching bean but found 2: adsTransactionManager,transactionManager,
報錯日志如下:

報錯方法示例:
@Transactional
public void generateFreezeBondId() {
...
}附多數(shù)據(jù)源配置示例代碼:
數(shù)據(jù)源 dataSource
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
<property name="mapperLocations" value="classpath:com/dao/*.xml"/>
</bean>
<!-- Mapper接口組件掃描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.zqh.dao"/>
</bean>
<!--配置聲明事務-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />數(shù)據(jù)源 adsDataSource
<bean id="adsDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${ads.jdbc.driverClassName}" />
<property name="url" value="${ads.jdbc.url}"/>
<property name="username" value="${ads.jdbc.username}"/>
<property name="password" value="${ads.jdbc.password}"/>
</bean>
<bean id="adsSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="adsDataSource"/>
<property name="configLocation" value="classpath:ads/mybatis.xml"/>
<property name="mapperLocations" value="classpath:com/ads/dao/*.xml"/>
</bean>
<!-- Mapper接口組件掃描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.zqh.ads.dao"/>
</bean>
<!--配置聲明事務-->
<bean id="adsTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="adsDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="adsTransactionManager" />Spring 事務機制
首先結合 Spring 源碼來分析下 Spring 的事務執(zhí)行機制,核心代碼如下(org.springframework.transaction.interceptor.TransactionAspectSupport):
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 1. 獲取事務屬性,如傳播機制、別名等,事務屬性解析為 RuleBasedTransactionAttribute 實例
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 2. 獲取事務管理器
final TransactionManager tm = determineTransactionManager(txAttr);
// ......
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 3. 聲明式事務處理,判斷條件: txAttr 為空(不是事務) || 事務管理器不是 CallbackPreferringPlatformTransactionManager
// 創(chuàng)建事務
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
retVal = invocation.proceedWithInvocation(); // 執(zhí)行事務增強方法
} catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex); // 異?;貪L
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
commitTransactionAfterReturning(txInfo); // 提交事務
return retVal;
} else {
// 4. 編程式事務
Object result;
final ThrowableHolder throwableHolder = new ThrowableHolder();
result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
try {
Object retVal = invocation.proceedWithInvocation();
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal))
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
return retVal;
} catch (Throwable ex) {
//.......
});
// ......
return result;
}
}主流程比較清晰,有興趣可參考Spring事務源碼,這里重點分析獲取事務管理器邏輯:
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
// Case 1:事務屬性上配置了 value 值
return determineQualifiedTransactionManager(this.beanFactory, qualifier);
} else if (StringUtils.hasText(this.transactionManagerBeanName)) {
// Case 2:指定了 transactionManagerBeanName
return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
} else {
// Case 3:根據(jù)類型獲取注入的 TransactionManager
TransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
if (defaultTransactionManager == null) {
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
this.transactionManagerCache.putIfAbsent(DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
}
return defaultTransactionManager;
}
}determineTransactionManager 函數(shù)中獲取事務管理器主要包括三個分支:
Case 1:@Transactional 配置了 value 值
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
//......
}spring 在解析注解 @Transactional 的時候,會將 value 的值寫入到 qualifier 中,會根據(jù) qualifier 來獲取事務管理器
Case 2:指定了 transactionManagerBeanName
從 Spring 源碼上理解,
<tx:annotation-driven transaction-manager="transactionManager"/>
會在解析該標簽時將屬性 transaction-manager 的值設置到 TransactionInterceptor 的父類 TransactionAspectSupport 的 transactionManagerBeanName 屬性中(本質上是生成 TransactionInterceptor Bean 實例),這里可參考方法:
org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.AopAutoProxyConfigurer#configureAutoProxyCreator
從業(yè)務代碼配置上看,兩個數(shù)據(jù)源都指定了 transactionManagerBeanName,即使隨機加載一個也應該會找到相應的 TransactionManager,所以這里就不太明白為什么在事務攔截器執(zhí)行的時候獲取不到 transactionManagerBeanName,留給后面做個研究。
Case 3:除了上述兩種 case,其他情況會根據(jù)類型獲取注入的 TransactionManager
報錯原因及解決方案
了解了 Spring 事務機制,再來分析問題就比較簡單,根據(jù)上述報錯日志,直接定位到 determineTransactionManager 的 Case 3 情況,說明 Spring 容器中注入了兩個 TransactionManager
所以常用解決方案有以下幾種
- 解決方式一:因業(yè)務在數(shù)據(jù)源 adsDateSource 中只有查詢,無寫入操作,所以直接去掉 adsDateSource 事務配置即可,這樣只有一個 TransactionManager 實例,不會出現(xiàn)類型注入沖突
- 解決方式二:因為配置了多個數(shù)據(jù)源,在 @Transactional 注解中未指定應用哪個數(shù)據(jù)源,所以直接指定數(shù)據(jù)源即,示例如下:
// Step 1:配置數(shù)據(jù)源指定 Qualifier
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
<qualifier value = "dataSourceQualifier"/>
</bean>
// Step 2:修改事務屬性配置
@Transactional("dataSourceQualifier")
public void generateFreezeBondId() {
...
}總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- SpringBoot配置多數(shù)據(jù)源的四種方式分享
- springboot項目實現(xiàn)多數(shù)據(jù)源配置使用dynamic-datasource-spring-boot-starter的操作步驟
- Spring?Boot多數(shù)據(jù)源事務@DSTransactional的使用詳解
- SpringBoot整合Mybatis-Plus+Druid實現(xiàn)多數(shù)據(jù)源配置功能
- SpringBoot項目配置postgresql數(shù)據(jù)庫完整步驟(配置多數(shù)據(jù)源)
- Springboot整合多數(shù)據(jù)源配置流程詳細講解
相關文章
Java 中HttpURLConnection附件上傳的實例詳解
這篇文章主要介紹了Java 中HttpURLConnection附件上傳的實例詳解的相關資料,希望通過本文大家能掌握這樣的知識內容,需要的朋友可以參考下2017-09-09

