文章目录

  • 现象
  • Spring 事务机制
  • 报错原因及解决方案

现象

近期做了一个业务需求,需要增加多数据源,同时对事务也进行了配置,待发布上线后出现使用 @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:指定了 transactionManagerBeanNamereturn determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);} else {// Case 3:根据类型获取注入的 TransactionManagerTransactionManager 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() {...
}

欢迎如转载,请注明出处!欢迎关注微信公众号:方辰的博客。

Spring多数据源TransactionManager冲突解决方案相关推荐

  1. 让媳妇瞬间搞懂Spring 多数据源操作(SpringBoot + Durid)

    1.快速理解 Spring 多数据源操作 最近在调研 Spring 如何配置多数据源的操作,结果被媳妇吐槽,整天就坐在那打电脑,啥都不干.于是我灵光一现,跟我媳妇说了一下调研结果,第一版本原话如下: ...

  2. Spring Boot(七):Mybatis 多数据源最简解决方案

    Spring Boot(七):Mybatis 多数据源最简解决方案 参考文章: (1)Spring Boot(七):Mybatis 多数据源最简解决方案 (2)https://www.cnblogs. ...

  3. (转)Spring Boot(七):Mybatis 多数据源最简解决方案

    http://www.ityouknow.com/springboot/2016/11/25/spring-boot-multi-mybatis.html 说起多数据源,一般都来解决那些问题呢,主从模 ...

  4. spring多数据源分布式事务的分析与解决方案

    spring多数据源分布式事务的分析与解决方案 参考文章: (1)spring多数据源分布式事务的分析与解决方案 (2)https://www.cnblogs.com/qianjun2017/p/83 ...

  5. 关于纯洁的微笑《Spring Boot(七):Mybatis 多数据源最简解决方案》文章补充说明多数据源事务的配置

    关于Spring boot中使用Mybatis多数据源的配置,我推荐纯洁的微笑博主的<Spring Boot(七):Mybatis 多数据源最简解决方案>这篇文章,简单清晰易懂 疑问 但是 ...

  6. SpringBoot多数据源及事务解决方案

    目录 1. 背景 2. 数据源切换原理 3. 配置文件解决方案 3.1 创建数据源 3.2 AOP处理 3.3 方案不足 4. 数据库表解决方案 4.1 设计数据源表 4.2 自定义数据源管理 4.2 ...

  7. Spring多数据源配置和使用

    Spring多数据源配置和使用 1.配置信息 <!--==============================bpt_mobdb数据库配置========================== ...

  8. spring(16)------spring的数据源配置

    spring(16)------spring的数据源配置 在spring中,通过XML的形式实现数据源的注入有三种形式. 一.使用spring自带的DriverManagerDataSource 使用 ...

  9. 搭建eclipse版的ssm+maven+tk.mybatis+redis及mybatis+spring多数据源配置集成的demo

    前言:我这里搭建好eclipse版的ssm+maven+tk.mybatis+redis及mybatis+spring多数据源配置集成的demo.新手快速上手直接看demo. 最后处提供完整高质量de ...

最新文章

  1. Anaconda中pytorch环境搭建(包括详细的虚拟环境创建,以及虚拟环境中jupyter notebook的使用)
  2. CSS自定义鼠标样式。JS获取鼠标坐标,实现提示气泡框跟随鼠标移动
  3. lay和lied_lie和lay的区别
  4. SpringCloud Zuul初体验
  5. python发送邮件拒绝_人生苦短之Python发邮件
  6. oracle中$的用法,关于expdp 中query用法小结
  7. [转载] Python中pandas dataframe删除一行或一列:drop函数
  8. 异常错误 - MySQL导入时错误
  9. matlab中wavread函数错误改用方法
  10. 053试题 - 320/321/322/323/324/326/330/332/544/553/585/586/587/588/589/592/596/597/598/599 rman backup
  11. 树莓派pico从零开始的入门(一)
  12. 逆向工程实验Lab7
  13. 解决Mac没有consolas字体的问题,idea也能用
  14. OA与财务系统集成:核算准、入账快、报销易
  15. 几种数据预处理方法汇总(标准/中心化、归一化、正则化)+Python代码
  16. 老调重弹:JDBC系列 之 JDBC层次结构和基本构成
  17. [JVM]了断局: Class文件结构梳理
  18. Pulmonary--Detection5
  19. 2CoreIDRAW 软件的实际应用
  20. 大小端介绍,你知道常用的VS2019内存中字节序存储的顺序吗?

热门文章

  1. Java 获取当前时间并且格式化
  2. 符合python命名规范的标识符是什么_Python标识符命名规范
  3. 计算斯皮尔曼的等级相关系数(Spearman’s rank correlation coefficient)步骤
  4. 博客园动态小老鼠特效
  5. 大雄和哆啦A梦WP(实验吧-隐写术)
  6. 80后男人口述娶妻20条标准
  7. Matlab .mat文件默认为Microsoft Access文件
  8. Rpad 中文 mysql_lpadamp;amp;rpad
  9. python爬虫脚本入门-通过电影脚本讲些爬虫知识
  10. 重庆公交一卡通可由支付宝充值