配置多事務管理時的一些問題

工程

https://github.com/obiteaaron/nomadic-test-spring-transaction

說明

此工程用於驗證spring事務tx:annotation-driven在使用中可能存在的問題。起因是在某實際工程中,配置了多個tx:annotation-driven以及多個org.springframework.jdbc.datasource.DataSourceTransactionManager導致最終事務失效的問題。

重點

  1. <tx:annotation-driven /> 只需要配置一個即可,如果配置了多個,事務以第一個爲準,其它屬性項,以級別最高的爲準
  2. 如果使用默認值transactionManager<tx:annotation-driven />中可以不指定屬性transaction-manager
  3. 第一個<tx:annotation-driven />加載時,指定的事務會成爲默認事務,無論指定的事務管理器的名稱是否是transactionManager
  4. 使用註解@Transactional時,如果不指定具體的事務管理器,會使用上面這個默認事務處理器
  5. 如果在多個配置文件中配置了<tx:annotation-driven />,無法保證啓動時的加載順序,此時默認事務管理器就有可能會變
  6. 爲保證使用的事務永遠是期望的,應該做到在使用註解@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:attributequalifier ,導致創建 TransactionInterceptor 時,對其 Properties 沒有設置 qualifier ,而是直接將引用的類寫入了其父類 TransactionAspectSupport 的屬性 transactionManagerCache 中,而 transactionManagerCache 中的值默認都是 SoftReference ( 只有 SOFTWEAK 可選,默認 SOFT ),導致當內存不足時,此緩存中的事務配置會被GC回收掉。因此在執行事務的時候,會重新獲取,由於沒有 qualifier ,又沒有類名(配置的時候只配置了具體的實例對象),所以 Spring 會通過類型獲取,即獲取 PlatformTransactionManager 的實現,而此時,多事務的情況下,就會有多個 PlatformTransactionManagerBean 即會出現異常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 的父類 TransactionAspectSupporttransactionManagerBeanName 屬性中。如果不指定 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 /> 這個配置項。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章