工程
https://github.com/obiteaaron/nomadic-test-spring-transaction
說明
此工程用於驗證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>
方案二:
使用全註解的方式,原因如下。
1. spring 在解析註解 @Transactional
的時候,會將 value
的值寫入到 qualifier
中,和上面 2
的配置一樣。如果不寫,則是空的(會使用默認事務)。所以建議 @Transactional
一定要帶上 value
屬性,指定具體的事務管理器。
2. <tx:annotation-driven transaction-manager="transactionManager"/>
會將屬性 transaction-manager
的值設置到 TransactionInterceptor
的父類 TransactionAspectSupport
的 transactionManagerBeanName
屬性中。如果不指定 transaction-manager
,設置的也是 transactionManager
。
3. 事務執行的時候,在 TransactionAspectSupport#determineTransactionManager()
中,先檢查 qualifier
,再檢查 transactionManagerBeanName
,如果這兩個都沒有值,則獲取配置的 TransactionManager bean
對象。
4. 如上面原因說的那樣,再結合以上三點,由於這個 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
在多事務下出現的異常。
5. <tx:annotation-driven transaction-manager="transactionManager"/>
在 xml 中可以配置多個,但是隻有第一個會生效,後面的不會創建新的 TransactionInterceptor
實例。
總結
這個問題總算是解決了,不再出現事務錯誤的問題了。謹記一點,能使用註解就使用註解 <tx:annotation-driven transaction-manager="transactionManager"/>
和 @Transactional
,避免使用 <tx:advice />
這個配置項。