美文网首页
Spring多数据源TransactionManager冲突解决

Spring多数据源TransactionManager冲突解决

作者: 恋恋风尘_79f0 | 来源:发表于2022-04-10 20:51 被阅读0次

    [TOC]

    现象

    近期做了一个业务需求,需要增加多数据源,同时对事务也进行了配置,待发布上线后出现使用 @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() {
        ... 
    }
    

    附多数据源配置示例代码:

    • 数据源 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" />
    
    • 数据源 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 的事务执行机制,核心代码如下(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
            // 创建事务
            TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
            Object retVal;
            try {
                retVal = invocation.proceedWithInvocation(); // 执行事务增强方法
            } 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); // 提交事务
            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:根据类型获取注入的 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 函数中获取事务管理器主要包括三个分支:

    • Case 1:@Transactional 配置了 value 值
    public @interface Transactional {
    
    
        @AliasFor("transactionManager")
        String value() default "";
    
        @AliasFor("value")
        String transactionManager() default "";
        
        //......
    }
    

    spring 在解析注解 @Transactional 的时候,会将 value 的值写入到 qualifier 中,会根据 qualifier 来获取事务管理器

    • Case 2:指定了 transactionManagerBeanName

    从 Spring 源码上理解, <tx:annotation-driven transaction-manager="transactionManager"/> 会在解析该标签时将属性 transaction-manager 的值设置到 TransactionInterceptor 的父类 TransactionAspectSupporttransactionManagerBeanName 属性中(本质上是生成 TransactionInterceptor Bean 实例),这里可参考方法:org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.AopAutoProxyConfigurer#configureAutoProxyCreator。从业务代码配置上看,两个数据源都指定了 transactionManagerBeanName,即使随机加载一个也应该会找到相应的 TransactionManager,所以这里就不太明白为什么在事务拦截器执行的时候获取不到 transactionManagerBeanName,留给后面做个研究。

    • Case 3:除了上述两种 case,其他情况会根据类型获取注入的 TransactionManager

    报错原因及解决方案

    了解了 Spring 事务机制,再来分析问题就比较简单,根据上述报错日志,直接定位到 determineTransactionManager 的 Case 3 情况,说明 Spring 容器中注入了两个 TransactionManager ,所以常用解决方案有以下几种:

    • 解决方式一:因业务在数据源 adsDateSource 中只有查询,无写入操作,所以直接去掉 adsDateSource 事务配置即可,这样只有一个 TransactionManager 实例,不会出现类型注入冲突
    • 解决方式二:因为配置了多个数据源,在 @Transactional 注解中未指定应用哪个数据源,所以直接指定数据源即,示例如下:
    // Step 1:配置数据源指定 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() {
        ... 
    }
    

    公wx众:方辰的博客

    相关文章

      网友评论

          本文标题:Spring多数据源TransactionManager冲突解决

          本文链接:https://www.haomeiwen.com/subject/afhbsrtx.html