关于Spring多数据源TransactionManager冲突的解决方案
作者:bboyzqh
现象
近期做了一个业务需求,需要增加多数据源,同时对事务也进行了配置,待发布上线后出现使用 @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
的父类 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() { ... }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。