關(guān)于Spring多數(shù)據(jù)源TransactionManager沖突的解決方案
現(xiàn)象
近期做了一個(gè)業(yè)務(wù)需求,需要增加多數(shù)據(jù)源,同時(shí)對(duì)事務(wù)也進(jìn)行了配置,待發(fā)布上線后出現(xiàn)使用 @Transactional 注解的方法拋出 NoUniqueBeanDefinitionException 異常:
No qualifying bean of type ‘org.springframework.transaction.PlatformTransactionManager’ available: expected single matching bean but found 2: adsTransactionManager,transactionManager,
報(bào)錯(cuò)日志如下:
報(bào)錯(cuò)方法示例:
@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> <!--配置聲明事務(wù)--> <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> <!--配置聲明事務(wù)--> <bean id="adsTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="adsDataSource"/> </bean> <tx:annotation-driven transaction-manager="adsTransactionManager" />
Spring 事務(wù)機(jī)制
首先結(jié)合 Spring 源碼來(lái)分析下 Spring 的事務(wù)執(zhí)行機(jī)制,核心代碼如下(org.springframework.transaction.interceptor.TransactionAspectSupport):
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // 1. 獲取事務(wù)屬性,如傳播機(jī)制、別名等,事務(wù)屬性解析為 RuleBasedTransactionAttribute 實(shí)例 TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); // 2. 獲取事務(wù)管理器 final TransactionManager tm = determineTransactionManager(txAttr); // ...... PlatformTransactionManager ptm = asPlatformTransactionManager(tm); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) { // 3. 聲明式事務(wù)處理,判斷條件: txAttr 為空(不是事務(wù)) || 事務(wù)管理器不是 CallbackPreferringPlatformTransactionManager // 創(chuàng)建事務(wù) TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal; try { retVal = invocation.proceedWithInvocation(); // 執(zhí)行事務(wù)增強(qiáng)方法 } catch (Throwable ex) { completeTransactionAfterThrowing(txInfo, ex); // 異常回滾 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); // 提交事務(wù) return retVal; } else { // 4. 編程式事務(wù) 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事務(wù)源碼,這里重點(diǎn)分析獲取事務(wù)管理器邏輯:
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) { if (txAttr == null || this.beanFactory == null) { return getTransactionManager(); } String qualifier = txAttr.getQualifier(); if (StringUtils.hasText(qualifier)) { // Case 1:事務(wù)屬性上配置了 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ù)中獲取事務(wù)管理器主要包括三個(gè)分支:
Case 1:@Transactional 配置了 value 值
public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; //...... }
spring 在解析注解 @Transactional
的時(shí)候,會(huì)將 value
的值寫入到 qualifier
中,會(huì)根據(jù) qualifier
來(lái)獲取事務(wù)管理器
Case 2:指定了 transactionManagerBeanName
從 Spring 源碼上理解,
<tx:annotation-driven transaction-manager="transactionManager"/>
會(huì)在解析該標(biāo)簽時(shí)將屬性 transaction-manager
的值設(shè)置到 TransactionInterceptor
的父類 TransactionAspectSupport
的 transactionManagerBeanName
屬性中(本質(zhì)上是生成 TransactionInterceptor Bean 實(shí)例),這里可參考方法:
org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.AopAutoProxyConfigurer#configureAutoProxyCreator
從業(yè)務(wù)代碼配置上看,兩個(gè)數(shù)據(jù)源都指定了 transactionManagerBeanName,即使隨機(jī)加載一個(gè)也應(yīng)該會(huì)找到相應(yīng)的 TransactionManager,所以這里就不太明白為什么在事務(wù)攔截器執(zhí)行的時(shí)候獲取不到 transactionManagerBeanName,留給后面做個(gè)研究。
Case 3:除了上述兩種 case,其他情況會(huì)根據(jù)類型獲取注入的 TransactionManager
報(bào)錯(cuò)原因及解決方案
了解了 Spring 事務(wù)機(jī)制,再來(lái)分析問(wèn)題就比較簡(jiǎn)單,根據(jù)上述報(bào)錯(cuò)日志,直接定位到 determineTransactionManager 的 Case 3 情況,說(shuō)明 Spring 容器中注入了兩個(gè) TransactionManager
所以常用解決方案有以下幾種
- 解決方式一:因業(yè)務(wù)在數(shù)據(jù)源 adsDateSource 中只有查詢,無(wú)寫入操作,所以直接去掉 adsDateSource 事務(wù)配置即可,這樣只有一個(gè) TransactionManager 實(shí)例,不會(huì)出現(xiàn)類型注入沖突
- 解決方式二:因?yàn)榕渲昧硕鄠€(gè)數(shù)據(jù)源,在 @Transactional 注解中未指定應(yīng)用哪個(gè)數(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:修改事務(wù)屬性配置 @Transactional("dataSourceQualifier") public void generateFreezeBondId() { ... }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringBoot配置多數(shù)據(jù)源的四種方式分享
- springboot項(xiàng)目實(shí)現(xiàn)多數(shù)據(jù)源配置使用dynamic-datasource-spring-boot-starter的操作步驟
- Spring?Boot多數(shù)據(jù)源事務(wù)@DSTransactional的使用詳解
- SpringBoot整合Mybatis-Plus+Druid實(shí)現(xiàn)多數(shù)據(jù)源配置功能
- SpringBoot項(xiàng)目配置postgresql數(shù)據(jù)庫(kù)完整步驟(配置多數(shù)據(jù)源)
- Springboot整合多數(shù)據(jù)源配置流程詳細(xì)講解
相關(guān)文章
Java 中HttpURLConnection附件上傳的實(shí)例詳解
這篇文章主要介紹了Java 中HttpURLConnection附件上傳的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文大家能掌握這樣的知識(shí)內(nèi)容,需要的朋友可以參考下2017-09-09