Spring多數(shù)據(jù)源TransactionManager沖突解決方案

[TOC]

現(xiàn)象

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

exception.png

報錯方法示例:

@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ù)機制

首先結(jié)合 Spring 源碼來分析下 Spring 的事務(wù)執(zhí)行機制,核心代碼如下(org.springframework.transaction.interceptor.TransactionAspectSupport):

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
            final InvocationCallback invocation) throws Throwable {
    
    // 1. 獲取事務(wù)屬性,如傳播機制、別名等,事務(wù)屬性解析為 RuleBasedTransactionAttribute 實例
    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ù)增強方法
        } 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); // 提交事務(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ù)源碼,這里重點分析獲取事務(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ù)管理器主要包括三個分支:

  • Case 1:@Transactional 配置了 value 值
public @interface Transactional {


    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";
    
    //......
}

spring 在解析注解 @Transactional 的時候,會將 value 的值寫入到 qualifier 中,會根據(jù) qualifier 來獲取事務(wù)管理器

  • Case 2:指定了 transactionManagerBeanName

從 Spring 源碼上理解, <tx:annotation-driven transaction-manager="transactionManager"/> 會在解析該標(biāo)簽時將屬性 transaction-manager 的值設(shè)置到 TransactionInterceptor 的父類 TransactionAspectSupporttransactionManagerBeanName 屬性中(本質(zhì)上是生成 TransactionInterceptor Bean 實例),這里可參考方法:org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.AopAutoProxyConfigurer#configureAutoProxyCreator。從業(yè)務(wù)代碼配置上看,兩個數(shù)據(jù)源都指定了 transactionManagerBeanName,即使隨機加載一個也應(yīng)該會找到相應(yīng)的 TransactionManager,所以這里就不太明白為什么在事務(wù)攔截器執(zhí)行的時候獲取不到 transactionManagerBeanName,留給后面做個研究。

  • Case 3:除了上述兩種 case,其他情況會根據(jù)類型獲取注入的 TransactionManager

報錯原因及解決方案

了解了 Spring 事務(wù)機制,再來分析問題就比較簡單,根據(jù)上述報錯日志,直接定位到 determineTransactionManager 的 Case 3 情況,說明 Spring 容器中注入了兩個 TransactionManager ,所以常用解決方案有以下幾種:

  • 解決方式一:因業(yè)務(wù)在數(shù)據(jù)源 adsDateSource 中只有查詢,無寫入操作,所以直接去掉 adsDateSource 事務(wù)配置即可,這樣只有一個 TransactionManager 實例,不會出現(xiàn)類型注入沖突
  • 解決方式二:因為配置了多個數(shù)據(jù)源,在 @Transactional 注解中未指定應(yīng)用哪個數(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() {
    ... 
}

公wx眾:方辰的博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容