最初第一版的内容
一、说明
验证spring事务tx:annotation-driven
在使用中可能存在的问题。起因是在某实际工程中,配置了多个tx:annotation-driven
以及多个org.springframework.jdbc.datasource.DataSourceTransactionManager
导致最终事务失效的问题。
二、重点
<tx:annotation-driven />
只需要配置一个即可,如果配置了多个,事务以第一个为准,其它属性项,以级别最高的为准- 如果使用默认值
transactionManager
则<tx:annotation-driven />
中可以不指定属性transaction-manager
- 第一个
<tx:annotation-driven />
加载时,指定的事务会成为默认事务,无论指定的事务管理器的名称是否是transactionManager
- 使用注解
@Transactional
时,如果不指定具体的事务管理器,会使用上面这个默认事务处理器 - 如果在多个配置文件中配置了
<tx:annotation-driven />
,无法保证启动时的加载顺序,此时默认事务管理器就有可能会变 - 为保证使用的事务永远是期望的,应该做到在使用注解
@Transactional
时,指明使用的事务处理器的id、name或者qualifier
2018-01-21补充
最上面说,遇到事务不生效的问题,那么这个问题的根源在下面说明一下
一、简单原因:
使用了下面的配置
<beans>
<!--省略其它配置-->
<tx:advice id="1" transaction-manager="transactionManagerTest">
<tx:attributes>
<tx:method name="*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="interceptorPointCuts"
expression="execution(* org.nomadic.test.service.WordTestService.*(..))"/>
<aop:advisor advice-ref="2"
pointcut-ref="interceptorPointCuts"/>
</aop:config>
</beans>
二、详细原因:
在 tx:advice
配置中,无法指定 tx:attribute
的 qualifier
,导致创建 TransactionInterceptor
时,对其 Properties
没有设置 qualifier
,而是直接将引用的类写入了其父类 TransactionAspectSupport
的属性 transactionManagerCache
中,而 transactionManagerCache
中的值默认都是 SoftReference
( 只有 SOFT
和 WEAK
可选,默认 SOFT
),导致当内存不足时,此缓存中的事务配置会被GC回收掉。因此在执行事务的时候,会重新获取,由于没有 qualifier
,又没有类名(配置的时候只配置了具体的实例对象),所以 Spring 会通过类型获取,即获取 PlatformTransactionManager
的实现,而此时,多事务的情况下,就会有多个 PlatformTransactionManager
的 Bean
即会出现异常org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: transactionManager,transactionManagerTest
。
三、解决方案:
方案一:
使用配置bean的方式配置一个 TransactionInterceptor
,不使用注解 tx:advice
。
<beans>
<!--省略其它配置-->
<tx:advice id="1" transaction-manager="transactionManagerTest">
<tx:attributes>
<tx:method name="*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<!--使用2这个bean替换1这个bean就好了。毕竟注解只是为了配置方便,但是功能有缺陷-->
<bean id="2" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionAttributeSource">
<bean class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
<property name="nameMap">
<map>
<entry key="*">
<bean class="org.springframework.transaction.interceptor.RuleBasedTransactionAttribute">
<!--指定事务-->
<property name="qualifier" value="transactionManagerTest"/>
<property name="rollbackRules">
<list >
<bean class="org.springframework.transaction.interceptor.RollbackRuleAttribute">
<constructor-arg type="java.lang.Class" value="java.lang.Exception"/>
</bean>
</list>
</property>
</bean>
</entry>
</map>
</property>
</bean>
</property>
</bean>
</beans>
方案二:
使用全注解的方式,原因如下。
- spring 在解析注解
@Transactional
的时候,会将value
的值写入到qualifier
中,和上面2
的配置一样。如果不写,则是空的(会使用默认事务)。所以建议@Transactional
一定要带上value
属性,指定具体的事务管理器。 <tx:annotation-driven transaction-manager="transactionManager"/>
会将属性transaction-manager
的值设置到TransactionInterceptor
的父类TransactionAspectSupport
的transactionManagerBeanName
属性中。如果不指定transaction-manager
,设置的也是transactionManager
。- 事务执行的时候,在
TransactionAspectSupport#determineTransactionManager()
中,先检查qualifier
,再检查transactionManagerBeanName
,如果这两个都没有值,则获取配置的TransactionManager bean
对象。 - 如上面原因说的那样,再结合以上三点,由于这个
TransactionManager bean
对象是放在SoftReference
中的,随时可能会丢失。所以通过tx:advice
配置的事务是严重不靠谱的,它会丢失bean
,在单事务管理下没有问题,但是在多事务管理下,就会出现org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: transactionManager,transactionManagerTest
这样的异常。所以使用全注解的方式,可以保证事务执行的正确性,另外,可以避免tx:advice
在多事务下出现的异常。 <tx:annotation-driven transaction-manager="transactionManager"/>
在 xml 中可以配置多个,但是只有第一个会生效,后面的不会创建新的TransactionInterceptor
实例。
四、总结
这个问题总算是解决了,不再出现事务错误的问题了。谨记一点,能使用注解就使用注解 <tx:annotation-driven transaction-manager="transactionManager"/>
和 @Transactional
,避免使用 <tx:advice />
这个配置项。
2023-05-11补充(之前的结论只是特定版本问题)
以下结论基于spring 4.3.8 以及 5.x 3.2.11版本中的代码。
一、问题
在XML配置模式下,tx:advice和 tx:annotation-driver 两个配置都会加上默认事务bean(org.springframework.transaction.interceptor.TransactionAspectSupport#transactionManager),所以无论如何都应该是对的,这就推翻了之前的结论。
二、排查
而现在debug看,发现实际有三个 TransactionInterceptor 实例,tx:advice(org.springframework.transaction.config.TxAdviceBeanDefinitionParser#doParse) 和 tx:annotation-drive (org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser#parse )的两个,都是有默认事务定义的,所以都没问题。而另一个,没有在代码中定义,不知道哪里生成的,也没有默认事务定义,所以就会触发多事务的异常,要看看这个是哪来的,很有可能是手动创建的。
不过三个实际生效的只有一个,看起来是最后一个生效了,可能只是有些类没有走到,需要再确认下。
三、新结论(查阅源码后的,这个是确定的)
两个可以一起配置:
- tx:annotation-drive 会拦截所有Class,并对每个方法做判断是否需要走事务拦截器,判断规则为“是否存在@Transactional注解”,解析一次后会有缓存。
- tx:advice 只是定义了一个TransactionInterceptor的Bean,还需要定义一个aop:config 来使事务拦截器生效。
所以两者并存的情况下,有一定的可能性会走两遍事务(这一点要确认下)。
四、关于第三个Bean的问题检查:
TransactionProxyFactoryBean 看看是不是有地方创建了这个bean?等查到了再补充。
-
如下图是三个拦截器
-
如下图是生效的事务是没有属性的
3. 事务拦截器只是一个 MethodInterceptor,实际还得靠 AOP 才能生效,所以找一下实际的 AOP 类:
- 事务的 AOP 类名是org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor
- 由下图可见,实际是因为:org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration 创建了一个BeanFactoryTransactionAttributeSourceAdvisor
- 这个类只有一个实例,名字为org.springframework.transaction.config.internalTransactionAdvisor ,说明是在此处的定义导致org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser 这里的定义没有执行
- 所以虽然tx:annotation-driven生成了一个 TransactionInterceptor,但实际并没有被 advisor 引用(AOP引用),导致实际tx:annotation-driven定义的事务并没有生效,而这个自动生成的事务生效了
- 这个需要用 @EnableTransactionManagement 开启,并且如果需要赋值 transactionManager 的话,需要通过 TransactionManagementConfigurer 类实例进行扩展,显然我的项目肯定不会有这个扩展,那么就不会有属性。
4. 经过查询,因为我们原来是 SpringBoot 项目,确实在部分地方开启了@EnableTransactionManagement,而现在集成到 Spring 项目中,所以导致注解的定义方式也生效,从而导致生成了三个TransactionInterceptor ,且其中一个是没有属性的,这个没有属性的实例,需要通过 TransactionManagementConfigurer 扩展才会有属性,否则就会去BeanFactory 获取,以及使用缓存。
五、关于之前文章内容的说明
问:为什么之前的那个问题,是在启动后执行一段时间才出现,而不是启动后就立即有异常(似乎不是缓存的问题)?
答:Spring 4.1.4~4.3.3版本是在org.springframework.transaction.interceptor.TransactionAspectSupport#setTransactionManager 时写入了缓存,而不是类的属性,而其他版本都是写了类属性。所以之前可能确实是用的某些版本出了问题,而现在就不会出问题。这就有点离谱了,估计Spring当时工程师设计错了,后来回滚了。
六、解决办法
- 换Spring版本
- 配置默认bean ,类似@Primary ,其中一个transactionManager 修改为Primary,确保在上面有bug的版本也能拿到
- 每个@Transactional加明确定义,这个太麻烦,不建议该方案
- 干掉不应该创建的的事务拦截器声明,只用你明确知道存在的事务拦截器,并且确保每个声明的事务拦截器里面都有默认的事务管理器