关于Spring多数据源TransactionManager冲突的解决方案

 更新时间:2023年07月06日 14:20:52   作者:bboyzqh  
这篇文章主要介绍了关于Spring多数据源TransactionManager冲突的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

现象

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

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

数据源 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() {
    ... 
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot自动装配原理小结

    SpringBoot自动装配原理小结

    Spring Boot主要作用就是简化Spring应用的开发,开发者只需要通过少量代码就可以创建一个Spring应用,而达到这一目的最核心的思想就是约定优于配置。
    2021-05-05
  • java调用chatgpt接口来实现专属于自己的人工智能助手

    java调用chatgpt接口来实现专属于自己的人工智能助手

    这篇文章主要介绍了用java来调用chatget的接口,实现自己的聊天机器人,对人工智能感兴趣的小伙伴可以参考阅读
    2023-03-03
  • Java分层开发必知之PO、BO、DTO、VO、POJO概念实例详解

    Java分层开发必知之PO、BO、DTO、VO、POJO概念实例详解

    Java编程模型是一种在软件开发中帮助组织和管理代码的框架,其中,VO,BO,PO,DO,以及DTO是一些关键概念,它们在不同层次和阶段起到重要作用,这篇文章主要介绍了Java分层开发必知之PO、BO、DTO、VO、POJO概念的相关资料,需要的朋友可以参考下
    2026-02-02
  • Spring Cloud 服务网关Zuul的实现

    Spring Cloud 服务网关Zuul的实现

    这篇文章主要介绍了Spring Cloud 服务网关Zuul的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-07-07
  • 详解Java中的三种流程控制语句

    详解Java中的三种流程控制语句

    这篇文章主要介绍了Java中常用的三种流程控制语句的使用,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2022-04-04
  • 一文让你彻底明白Java中的值传递和引用传递

    一文让你彻底明白Java中的值传递和引用传递

    这篇文章主要给大家介绍了关于Java中值传递和引用传递的相关资料,值传递是指在调用函数时将实际参数复制一份传递到函数中,引用传递是指在调用函数时将实际参数的引用直接传递到函数中,需要的朋友可以参考下
    2023-10-10
  • plantuml画图实现代码画时序图UML用例图

    plantuml画图实现代码画时序图UML用例图

    这篇文章主要为大家介绍了plantuml画图实现代码画时序图示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • ShardingSphere解析SQL示例详解

    ShardingSphere解析SQL示例详解

    这篇文章主要为大家介绍了ShardingSphere解析SQL的示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • SpringBoot如何使用注解进行XSS防御

    SpringBoot如何使用注解进行XSS防御

    在SpringBoot中,可以通过自定义@XSS注解和实现XSSValidator类来防御XSS攻击,此方法适用于GET和POST请求,通过在方法参数或实体类属性上添加@XSS注解,并结合@Valid或@Validated注解使用,有效拦截潜在的XSS脚本,保障应用安全
    2024-11-11
  • Java Long类型对比分析

    Java Long类型对比分析

    这篇文章主要介绍了Java Long类型对比分析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07

最新评论