Spring多数据源TransactionManager冲突解决方案
文章目录
- 现象
- 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
的父类 TransactionAspectSupport
的 transactionManagerBeanName
属性中(本质上是生成 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冲突解决方案相关推荐
- 让媳妇瞬间搞懂Spring 多数据源操作(SpringBoot + Durid)
1.快速理解 Spring 多数据源操作 最近在调研 Spring 如何配置多数据源的操作,结果被媳妇吐槽,整天就坐在那打电脑,啥都不干.于是我灵光一现,跟我媳妇说了一下调研结果,第一版本原话如下: ...
- Spring Boot(七):Mybatis 多数据源最简解决方案
Spring Boot(七):Mybatis 多数据源最简解决方案 参考文章: (1)Spring Boot(七):Mybatis 多数据源最简解决方案 (2)https://www.cnblogs. ...
- (转)Spring Boot(七):Mybatis 多数据源最简解决方案
http://www.ityouknow.com/springboot/2016/11/25/spring-boot-multi-mybatis.html 说起多数据源,一般都来解决那些问题呢,主从模 ...
- spring多数据源分布式事务的分析与解决方案
spring多数据源分布式事务的分析与解决方案 参考文章: (1)spring多数据源分布式事务的分析与解决方案 (2)https://www.cnblogs.com/qianjun2017/p/83 ...
- 关于纯洁的微笑《Spring Boot(七):Mybatis 多数据源最简解决方案》文章补充说明多数据源事务的配置
关于Spring boot中使用Mybatis多数据源的配置,我推荐纯洁的微笑博主的<Spring Boot(七):Mybatis 多数据源最简解决方案>这篇文章,简单清晰易懂 疑问 但是 ...
- SpringBoot多数据源及事务解决方案
目录 1. 背景 2. 数据源切换原理 3. 配置文件解决方案 3.1 创建数据源 3.2 AOP处理 3.3 方案不足 4. 数据库表解决方案 4.1 设计数据源表 4.2 自定义数据源管理 4.2 ...
- Spring多数据源配置和使用
Spring多数据源配置和使用 1.配置信息 <!--==============================bpt_mobdb数据库配置========================== ...
- spring(16)------spring的数据源配置
spring(16)------spring的数据源配置 在spring中,通过XML的形式实现数据源的注入有三种形式. 一.使用spring自带的DriverManagerDataSource 使用 ...
- 搭建eclipse版的ssm+maven+tk.mybatis+redis及mybatis+spring多数据源配置集成的demo
前言:我这里搭建好eclipse版的ssm+maven+tk.mybatis+redis及mybatis+spring多数据源配置集成的demo.新手快速上手直接看demo. 最后处提供完整高质量de ...
最新文章
- Anaconda中pytorch环境搭建(包括详细的虚拟环境创建,以及虚拟环境中jupyter notebook的使用)
- CSS自定义鼠标样式。JS获取鼠标坐标,实现提示气泡框跟随鼠标移动
- lay和lied_lie和lay的区别
- SpringCloud Zuul初体验
- python发送邮件拒绝_人生苦短之Python发邮件
- oracle中$的用法,关于expdp 中query用法小结
- [转载] Python中pandas dataframe删除一行或一列:drop函数
- 异常错误 - MySQL导入时错误
- matlab中wavread函数错误改用方法
- 053试题 - 320/321/322/323/324/326/330/332/544/553/585/586/587/588/589/592/596/597/598/599 rman backup
- 树莓派pico从零开始的入门(一)
- 逆向工程实验Lab7
- 解决Mac没有consolas字体的问题,idea也能用
- OA与财务系统集成:核算准、入账快、报销易
- 几种数据预处理方法汇总(标准/中心化、归一化、正则化)+Python代码
- 老调重弹:JDBC系列 之 JDBC层次结构和基本构成
- [JVM]了断局: Class文件结构梳理
- Pulmonary--Detection5
- 2CoreIDRAW 软件的实际应用
- 大小端介绍,你知道常用的VS2019内存中字节序存储的顺序吗?