Spring聲明式事務、編程式事務一文打盡

Spring事務官方文檔翻譯

關於數據庫事務、鎖可以先行查看此文:MySQL數據庫讀寫鎖示例詳解、事務隔離級別示例詳解

Spring事務屬於Data Access模塊中的內容,該模塊包含事務管理支持以及其它數據訪問的集成。

事務管理

全面的事務支持是使用Spring框架的最重要原因之一。Spring爲事務管理提供了一個始終如一的抽象,優點如下:

  • 提供不同事務的API但是一致的編程模型,如Java事務API(JTA)、JDBC、Hibernate和Java持久化API(JPA)。
  • 支持聲明式事務
  • 比JTA更簡單的編程式事務API
  • 與Spring數據訪問抽象的優秀集成

Spring框架事務模型的優點

習慣上,Java EE 開發者有兩種事務管理方式:全局事務管理、本地事務管理,兩者都有很大的侷限性。

全局性事務管理

全局事務允許你操作多個事務資源,典型的是關係型數據庫和消息隊列。應用服務器通過JTA管理全局性事務,而JTA API是非常笨重的。另外,一個JTA的UserTransaction通常需要從JNDI中加載資源,意味着使用JTA必須配置JNDI。全局性事務限制了代碼的重用性,因爲JTA通常只在應用服務器環境中可用。

本地事務管理

本地事務是特定於資源的,例如與JDBC關聯的事務。本地事務更容易使用,但是也有一個重大的缺陷:不能跨多個事務資源工作。例如,使用JDBC連接的事務管理代碼不能在一個JTA的全局性事務中使用。因爲應用服務器不參與事務管理,它不能幫助確保跨多個資源的正確性。

Spring框架一致性編程模型

Spring解決了全局性事務和本地事務的缺陷,它可以讓應用開發者在任何環境下使用一致的編程模型API。你在一個地方編寫你的代碼,它可以在不同環境的不同事務管理策略中工作。Spring框架提供了聲明式事務編程式事務。大都數用戶偏愛聲明式事務,因爲編碼更簡單。

通過編程式事務,開發者通過Spring框架事務抽象來進行開發,可以運行在任何底層事務基礎設施上。
使用首選的聲明式事務模型,開發者僅需要編寫一點點與事務管理關聯的代碼,因此,不需要依賴Spring框架事務的API或其他事務API。

Spring事務相關的類

  • org.springframework.transaction.PlatformTransactionManager事務管理器接口。
  • org.springframework.transaction.TransactionDefinition事務定義。
  • org.springframework.transaction.TransactionStatus事務狀態。
  • org.springframework.transaction.support.TransactionSynchronization
  • org.springframework.transaction.support.AbstractPlatformTransactionManager實現了PlatformTransactionManager。其它框架集成Spring一般會繼承該類。

在這裏插入圖片描述

Spring框架事務抽象的關鍵點是事務策略的概念。一個事務策略通過org.springframework.transaction.PlatformTransactionManager接口來定義,像以下所展示的:

/**事務管理器*/
public interface PlatformTransactionManager {
    /**根據事務定義獲取事務狀態*/
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    /**提交事務*/
    void commit(TransactionStatus status) throws TransactionException;
    /**回滾事務*/
    void rollback(TransactionStatus status) throws TransactionException;
}

這主要是一個服務提供者接口(SPI),儘管你可以使用編程方式使用它。因爲PlatformTransactionManager是一個接口,它可以根據需要很容易地被mock或作爲存根使用。它沒有綁定到查找策略,比如JNDI等。
PlatformTransactionManager 實現的定義與Spring框架IOC容器中其他任何bean是一樣的,僅這一點就使得Spring事務是一個有價值的抽象,甚至你在使用JTA的時候。

同樣,爲了保持和Spring理念一致,PlatformTransactionManager 接口的方法可以拋出 TransactionException異常。
getTransaction(…) 方法返回一個 TransactionStatus對象,依賴於一個 TransactionDefinition參數,返回的TransactionStatus可能代表一個新的事務或者一個已經存在的事務(如果當前調用堆棧中存在事務)。後一種情況的含義是,與Java EE事務上下文一樣,事務狀態與執行線程相關聯。

TransactionDefinition接口指定了:

  • 傳播性Propagation:通常,在事務範圍內執行的所有代碼都在該事務中運行。但是,事務方法在已存在事務上下文執行時,你可以指定其行爲。例如,代碼可以繼續在已經存在的事務中運行(通過是這樣的),或者已存在的事務會掛起然後創建一個新的事務。Spring提供了和EJM CMT類似的所有事務傳播性操作。

    • PROPAGATION_REQUIRED:支持當前事務,如果當前沒有事務則新建一個事務。這是默認的事務傳播行爲。
    • PROPAGATION_SUPPORTS:支持當前事務,如果不存在事務則以非事務形式執行。
    • PROPAGATION_MANDATORY:支持當前事務,如果沒有事務則拋出異常,transaction synchronization還是可用的。
    • PROPAGATION_REQUIRES_NEW:新建一個事務,如果當前存在事務則還會掛起已經存在的事務。
    • PROPAGATION_NOT_SUPPORTED:不支持當前事務,總是以非事務方式執行。
    • PROPAGATION_NEVER:不支持事務,存在事務則拋出異常,transaction synchronization不可用。
    • PROPAGATION_NESTED:如果當前存在事務則在嵌套事務中執行,有點類似PROPAGATION_REQUIRED。
  • 隔離性Isolation:指定了事務的隔離性。

    • ISOLATION_READ_UNCOMMITTED:讀未提交。可能出現髒讀、不可重複讀、幻讀。這個隔離級別,一個事務可以讀取另一個事務未提交的內容。
    • ISOLATION_READ_COMMITTED:讀已提交。阻止了髒讀,但是不可重複讀、幻讀可能會發生。此級別僅禁止事務讀取包含未提交更改的行。
    • ISOLATION_REPEATABLE_READ:可重複度。阻止了髒讀、不可重複度,但是幻讀可能會發生。這個級別禁止事務讀取包含未提交更改的行,還禁止一個事務讀取行、第二個事務更改行、第一個事務重新讀取行,第二次獲得不同的值(“不可重複讀取”)。
    • ISOLATION_SERIALIZABLE:串行化。解決了髒讀、不可重複度和幻讀的問題。效率低,一般生產不用。
  • 超時Timeout:此事務在超時並由事務基礎設施自動回滾之前運行多長時間。

  • 是否只讀Read-only:當你的代碼僅僅讀取數據不會更改數據時可以設置只讀屬性。

這些設置反映了標準的事務概念。理解這些概念,是使用Spring框架或其它事務管理解決方案的基本前提。

TransactionStatus 接口爲事務代碼提供了一種簡單的方法來控制事務執行和查詢事務狀態。

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

無論您在Spring中選擇聲明式事務管理還是編程式事務管理,定義正確的PlatformTransactionManager實現都是絕對必要的。通常是通過依賴注入來定義此實現。

PlatformTransactionManager實現通常需要了解他們的環境:JDBC,JTA,Hibernate等等。以下示例展示了定義了一個本地的PlatformTransactionManager實現(此例中,使用了簡單的JDBC)。
你可以像以下一樣創建一個類似的beam,定義一個

  • 1.JDBC DataSource配置如下:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <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>

與之關聯的 PlatformTransactionManagerbean定義則可以引用 DataSource的定義,例如:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

註解方式:

@Bean(name = "myTxManager")
public PlatformTransactionManager txManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

如果你是在Java EE容器中使用JTA,你可以使用一個容器DataSource,可以通過JNDI獲取數據源,再結合Spring框架的JtaTransactionManager

  • 2.JTA和JDNI查找配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        https://www.springframework.org/schema/jee/spring-jee.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

    <!-- other <bean/> definitions here -->

</beans>

JtaTransactionManager不需要知道DataSource(或其他指定的數據源)因爲它使用了容器的全局事務管理基礎設施。

你也可以使用Hibernate本地事務,像以下示例展示的一樣。在此案例中,你需要定義一個Hibernate的LocalSessionFactoryBeanbean,則你的應用可以使用來獲取Hibernate的會話session實例,而DataSource bean則和本地JDBC示例類似。

❕ ❕

如果DataSource(被任何非JTA事務管理器使用的)是在一個Java EE容器中管理且通過JNDI查找到的,則它應該是非事務的,因爲Spring框架(而不是Java EE容器)負責管理事務。

在這個案例中的 txManagerbean是一個HibernateTransactionManager類型。和DataSourceTransactionManager類似,也需要依賴一個DataSource的引用,HibernateTransactionManager需要一個SessionFactory的引用。示例如下:

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

如果你是使用Hibernate和Java EE容器管理JTA事務,你應該和之前一樣使用JtaTransactionManager

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

❕ ❕

如果你使用的是JTA,你的事務管理器則應該看起來很像,不管使用什麼數據訪問技術,不管是JDBC、Hibernate JPA還是任何其他受支持的技術。這是因爲JTA事務是全局性事務,它可以徵募任何事務資源。

在這些案例中,應用的代碼是不需要變更的。您可以僅通過更改配置來更改事務的管理方式,即使這種變化意味着從本地事務轉移到全局事務,或者反之亦然。

事務資源同步

怎樣創建不同的事務管理器和它們是怎樣關聯那些需要同步到事務中的資源的(例如,DataSourceTransactionManager之餘一個JDBC DataSource,HibernateTransactionManager之於一個Hibernate的```SessionFactory,等等)現在應該是比較清晰的了。

這個部分描述應用代碼(直接或間接使用持久化API如JDBC、Hibernate,或者JPA)怎樣確保這些資源是如何創建、複用和清除的。

也討論事務同步(transaction synchronization)是如何通過關聯的PlatformTransactionManager觸發的。

高級同步方法

首選的方法是使用Spring最高級的基於模板的持久性集成api或者使用基於transaction-aware factory的原生的ORM API ben 或者代理 去管理本地資源工廠。
這種transaction-aware(事務感知)解決方案是在內部處理資源、重用、清除,資源的可選事務同步,異常映射。
因此,用戶數據訪問代碼可以不用關心這些處理而僅僅將關注持久化邏輯的編寫。
一般而言,你可以使用原生ORM API或者使用JdbcTemplate處理JDBC數據訪問。

低級同步方法

DataSourceUtils類(for JDBC)一樣,EntityManagerFactoryUtils類(for JPA),SessionFactoryUtils類(for Hibernate ),等等就是比較低級的API了。
當你想在應用代碼中直接處理原生持久API的資源類型的時候,你可以使用這些類確保實例是由Spring框架管理的、事務同步是可選的、異常映射到合適的持久化API中。

例如,在 JDBC 的案例中,在DataSource中用於替代傳統的 JDBC的getConnection()的方法,可以使用 org.springframework.jdbc.datasource.DataSourceUtils類:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果一個已經存在的事務已經有一個連接connection同步給它了,則會返回該connection。否則,該方法會觸發創建一個新的connection,它(可選地)同步到任何現有事務,並可用於該事務的後續重用。
像之前提到過的一樣,任何 SQLException 都被包裝在Spring框架中的CannotGetJdbcConnectionException(這是一個Spring框架的未檢查unchecked的DataAccessException類型的層次結構之一)。
這種方法提供的信息比從SQLException獲得的信息要多,並且確保了跨數據庫甚至跨不同持久性技術的可移植性。
這種方式是沒有在Spring事務管理機制下工作的,因此,無論你是否使用Spring事務管理機制都可以使用它。

TransactionAwareDataSourceProxy事務感知數據源代理類

在最底層存在 TransactionAwareDataSourceProxy類。這是一個數據源DataSource的代理類,包裝了一個數據源並且將其添加到Spring事務的感知中。在這方面,類似於Java EE服務器提供的傳統的JNDI數據源。

你應該幾乎從不會使用這個類,除非當前的代碼必須通過一個標準的JDBC數據源接口調用實現。在這個場景中,這些代碼是有用的,但是它參與了Spring管理的事務。你可以使用高級的抽象編寫新的代碼。

聲明式事務管理

❕ ❕

大部分的Spring框架使用者會選擇聲明式事務管理。這個選擇對應用代碼影響更小,因此,它更符合非侵入式輕量級容器理念。

Spring框架的聲明式事務管理是通過Spring面向切面編程(AOP)實現的。

然而,因爲事務相關的代碼是隨Spring框架發行版本一塊發佈的,可以以樣板方式使用,通常不需要理解AOP的概念就可以使用這些代碼了。

Spring框架的聲明式事務管理機制類似於EJB CMT,在這種情況下,你可以將事務行爲(或缺少事務行爲)指定到單個方法級別。如果有必要的話,你可以在一個事務上下文中調用setRollbackOnly()方法。這兩種類型的事務管理的差異在於:

  • 不像EJB CMT是綁定了JTA的。Spring框架的聲明式事務管理可以在任何環境中工作,它可以通過調整配置文件就可以輕易地和JTA事務、使用JDBC的本地事務、JPA或者Hibernate一塊工作。
  • 你可以在任何類中使用Spring框架聲明式事務,而不是像EJB一樣只能指定某些類。
  • Spring框架提供了聲明式回滾規則,這是和EJB等同的特性。編程式、聲明式的回滾規則都提供了。
  • Spring框架可以讓你通過AOP自定義事務行爲。例如,你可以在事務回滾的時候插入自定義行爲。還可以添加任意的advice(通知),以及事務advice。而如果是EJB CMT的話,你不可能影響容器的事務管理機制,除非使用setRollbackOnly()
  • Spring框架不像高端應用服務器那樣支持在遠程調用之間傳播事務上下文。如果你需要這個特性,推薦你使用EJB。但是,在你使用該特性之前需要慎重,因爲,正常情況下,是不想在遠程調用之間傳播事務的。

回滾規則的概念是非常重要的。 它們可以讓你指定哪些異常應該引發自動回滾。你可以在配置中而不是Java代碼中指定這些聲明。所以,儘管你可以在TransactionStatus對象中調用setRollbackOnly()方法去回滾當前的事務,大都數情況下你可以指定一個規則,即可以自定義異常必須導致事務回滾。這種選擇的重要優點是業務對象不依賴事務基礎設施。例如,它們通常不需要導入Spring事務API或者其它Spring API。

儘管EJB容器默認行爲是在事務發生系統異常(通常是運行時異常)時自動回滾,EJB CMT並不會在出現應用異常時自動回滾。但是Spring聲明式事務的默認行爲是允許自定義異常變更回滾策略的。

理解Spring聲明式事務實現

僅僅告訴你使用 @Transactional註解標註你的類是不夠的,添加EnabledTransactionManagement到你的配置中,並希望你理解它是如何工作的。爲了提供一個深刻的理解,這個部分解釋在發生與事務相關的問題時,Speing聲明式事務機制的內部工作原理。

掌握Spring框架聲明式事務的最重要的概念是通過AOP代理實現的,事務通知由元數據(XML或者基於註解的)驅動。

AOP與事務元數據的結合產生了一個AOP代理,它使用一個事務攔截器TransactionInterceptor和一個適當的PlatformTransactionManager實現來驅動圍繞方法調用的事務。

以下是通過事務代理調用方法的概念視圖:

在這裏插入圖片描述

聲明式事務實現示例

考慮以下接口以及它的實現,這個示例使用了FooBar類,這樣你就可以專注於事務的實現而不用關注具體的域模型了。就這個示例而言,DefaultFooService類的每個方法拋出UnsupportedOperationException異常是OK的。該行爲允許創建事務,然後回滾以響應UnsupportedOperationException實例。

// 我們想進行事務性操作的目標接口

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}

實現類:

package x.y.service;

public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        throw new UnsupportedOperationException();
    }

    public Foo getFoo(String fooName, String barName) {
        throw new UnsupportedOperationException();
    }

    public void insertFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

    public void updateFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

}

假設FooService接口的前兩個方法getFoo(String)和getFoo(String, String)必須在具有隻讀語義的事務上下文中執行,而其他方法insertFoo(Foo)和updateFoo(Foo)必須在具有讀寫語義的事務上下文中執行。以下是符合要求的配置信息:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 將進行事務性操作的bean -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    
    <!-- 數據源配置 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
    
    <!-- 配置事務管理器 PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置事務通知 (將會發生什麼,查看下面的 <aop:advisor/> 配置) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- 事務性語義配置 -->
        <tx:attributes>
            <!-- 所有get開頭的方法都是隻讀性事務 -->
            <tx:method name="get*" read-only="true"/>
            <!-- 其他的方法使用默認的事務性行爲 -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- 確保上面配置的事務通知可以在FooService接口的任意操作中執行-->
    <aop:config>
        <!-- 配置切面(切點集合) -->
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        
        <!-- 配置AOP通知 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>
</beans>

檢查上面的配置,假設你想使一個service對象(fooService bean)可以進行事務性操作。事務性語義可以使用<tx:advice/>定義來封裝。<tx:advice/>定義會讀取所有的方法,如果是 get 開頭的方法則執行只讀性事務,其它的方法則執行默認的事務語義。<tx:advice/>transaction-manager屬性標籤會設置爲將要驅動事務的PlatformTransactionManager實現bean的name。

🎻
如果你配置的PlatformTransactionManager的name或者id是transactionManager的話,事務通知(<tx:advice/>)的transaction-manager屬性則可以忽略,如果不是的話則必須配置該屬性標籤。

<aop:config>定義確保txAdvicebean事務通知可以在適當的切點執行。
首先,首先,定義一個切點,它匹配在FooService接口(fooServiceOperation)中定義的任何操作的執行;
然後,使用一個advisor關聯切點和事務通知。
結果就是,只要切點fooServiceOperation匹配的方法執行了,txAdvice中定義的事務通知就會運行。

<aop:pointcut/>元素定義是AspectJ的切點表達式。可以查看AOP獲取Spring-AOP的信息。

一個常見的需求是使整個service具有事務性。最好的方式就是改變切點表達式:

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>

現在我們分析過了配置信息了,你也許會問自己:這些配置將會做什麼?

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
        FooService fooService = (FooService) ctx.getBean("fooService");
        fooService.insertFoo (new Foo());
    }
}

運行後查看輸出信息可以發現事務性操作的整個流程:

<!--  Spring 容器啓動... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors

<!-- DefaultFooService 被動態代理 -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... the insertFoo(..) 在代理中開始調用-->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- 事務通知在這裏起作用... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

<!--  DefaultFooService 的 insertFoo(..) 拋出一個異常... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
<!-- 事務攔截器,拋出異常,則回滾insertFoo方法上的事務 -->
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]

<!-- 事務回滾,事務完畢後釋放連接 -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
<!-- 歸還連接給數據源 -->
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- 爲了清晰起見,AOP基礎設施堆棧跟蹤元素被刪除 -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

回滾聲明式事務

向Spring框架的事務基礎結構表明要回滾事務的推薦方法是從當前正在事務上下文中執行的代碼中拋出異常.
Spring框架事務基礎結構代碼會捕獲任何沒有處理的異常因爲它會從堆棧中冒泡出來從而決定是否標記該事務需要回滾。

在默認配置中,Spring框架事務基礎機構代碼標記事務回滾只會在運行時異常、非檢查異常時回滾。RuntimeException(Error實例默認會導致事務回滾)。檢查的異常在默認情況下不會引起事務回滾操作。

你可以在配置中準確指明哪種異常類型會導致事務回滾,可以包括檢查異常(checked exception),例如:

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
    <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

如果你想在異常拋出時讓一個事務回滾,你也可以指定回滾規則。 如下示例告訴Spring框架事務基礎結構,即使面對未處理的InstrumentNotFoundException異常,也要提交事務:

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

當Spring事務框架基礎結構捕獲一個異常時,它會諮詢配置的事務回滾規則從而決定是否回滾事務,最強的匹配的規則獲勝。所以,以下示例中,所有除了InstrumentNotFoundException的異常均會導致事務回滾:

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
    </tx:attributes>
</tx:advice>

你也可以通過編程式指定一個需要的回滾機制,儘管簡單但是耦合了Spring框架事務基礎結構在你的代碼中:

public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

強烈推薦你使用聲明式方式處理回滾。編程式回滾可以使用,但是它的使用與面向一個純淨的POJO架構背道而馳。

爲不同的bean配置不同的事務語義

考慮您擁有許多service層對象的場景,並且你想對他們使用完全不同的事務配置。你可以定義不同的<aop:advisor/>元素通過advice-ref關聯不同的pointcut

作爲個比較點,首先假設你所有的service層都位於x.y.service包下面。使這個包下面的類所有以Service結尾的類的所有方法都有默認的事務配置,可以如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config>
        <!-- 定義切點,所有Service結尾的類的所有方法 -->
        <aop:pointcut id="serviceOperation"
                expression="execution(* x.y.service..*Service.*(..))"/>

        <!-- 定義AOP通知,引用事務通知和切點 -->
        <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>

    </aop:config>

    <!-- 這兩個bean會被事務性控制 -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>

    <!-- 這些bean不會被事務性處理 -->
    <bean id="anotherService" class="org.xyz.SomeService"/> <!-- (包名不匹配) -->
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (類名不是Service結尾) -->

    <!-- 事務通知 -->
    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- PlatformTransactionManager 配置省略... -->

</beans>

以下則是兩個不同的bean使用不同的事務配置信息,定義了兩組事務通知、兩組AOP通知、兩個切點:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config>

        <aop:pointcut id="defaultServiceOperation"
                expression="execution(* x.y.service.*Service.*(..))"/>

        <aop:pointcut id="noTxServiceOperation"
                expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>

        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>

        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

    </aop:config>

    <!-- this bean will be transactional (see the 'defaultServiceOperation' pointcut) -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this bean will also be transactional, but with totally different transactional settings -->
    <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>

    <tx:advice id="defaultTxAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <tx:advice id="noTxAdvice">
        <tx:attributes>
            <tx:method name="*" propagation="NEVER"/>
        </tx:attributes>
    </tx:advice>

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

<tx:advice/>事務通知配置選項詳解

這個部分總結了使用<tx:advice/>標籤的多種類型的事務配置操作。默認的<tx:advice/>配置如下:

  • 事務傳播類型默認是 PROPAGATION_REQUIRED。
  • 隔離級別是 DEFAULT。
  • 事務是可讀可寫的。
  • 事務超時爲底層事務系統的默認超時,如果不支持超時,則爲none。
  • 任意RuntimeException觸發回滾,checked 異常則不會導致回滾。

你可以修改這些默認配置,下表總結了<tx:advice/><tx:attributes/>標籤嵌套的<tx:method/>標籤的各個屬性:

屬性 必須? 默認值 描述
name Yes 方法名。佔位符()表示全符合(例如,表示所有方法、get表示所有get開頭的方法,onEvent表示on開頭且Event結尾的方法等等)
propagation No REQUIRED 和前面的事務傳播行爲PROPAGATION_REQUIRED一樣
isolation No DEFAULT 事務隔離級別。僅適用於REQUIRED或REQUIRES_NEW的傳播設置。
timeout No -1 事務超時設置。僅適用於REQUIRED或REQUIRES_NEW的傳播設置。
read-only No false 設置爲只讀事務。僅適用於REQUIRED或REQUIRES_NEW的傳播設置。
rollback-for No 指定需要回滾事務的異常
no-rollback-for No 指定不回滾事務的異常

使用 @Transaction註解

除了基於XML聲明事務配置外,還可以使用基於註解的方式配置事務。
聲明式事務語義直接在Java源代碼中聲明。
不存在過度耦合的危險,因爲用於事務的代碼幾乎總是這樣部署的。

標準的javax.transaction.Transactional註解還支持作爲Spring自身註解org.springframework.transaction.annotation.Transactional的一個替代。

最好的關於@Transactional註解的最容易使用的方式:

// 在你想事務性操作的類或者方法標註 @Transactional 註解 
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

上面的示例使用了類級別的註解,則註解默認在所有的方法中使用。你還可以在每個方法中單獨標註使用。注意,類級別的註解並不會對其祖先類作用,在這種情況下,需要在祖先類本地重新聲明方法,以便參與子類級別的註釋。

當一個POJO類類似上面作爲一個bean在Spring上下文中定義的一樣,你可以在一個@Configuration的配置類中通過一個@EnableTransactionManagerment註解使bean實例具有事務性。更多細節可以查看org.springframework.transaction.annotation.EnableTransactionManagement
該註解啓用Spring的註釋驅動的事務管理功能,類似於Spring的tx:* XML名稱空間中的支持,@Configuration 類中可以如下編寫代碼:

@Configuration
@EnableTransactionManagement
public class AppConfig {

   @Bean
   public FooRepository fooRepository() {
       // 配置並返回具有@Transactional方法的類
       return new JdbcFooRepository(dataSource());
   }

   @Bean
   public DataSource dataSource() {
       // 配置並返回必要的 JDBC DataSource
   }

   @Bean
   public PlatformTransactionManager txManager() {
       return new DataSourceTransactionManager(dataSource());
   }
}

類似XML中的配置:

   <beans>

     <tx:annotation-driven/>

     <bean id="fooRepository" class="com.foo.JdbcFooRepository">
         <constructor-arg ref="dataSource"/>
     </bean>

     <bean id="dataSource" class="com.vendor.VendorDataSource"/>

     <bean id="transactionManager" class="org.sfwk...DataSourceTransactionManager">
         <constructor-arg ref="dataSource"/>
     </bean>

 </beans>

方法可見性和@Transactional

當你使用代理時,你應該將@Transactional註解應用於public方法中。如果註解到protected、private或者包級別的方法中,不會報異常,但是事務配置不會生效。可以查看org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation方法

該方法調用org.springframework.core.annotation.AnnotationUtils#getAnnotatedMethodsInBaseType方法。如果你需要使用非public方法中,請考慮使用 AspectJ。

你可以把@Transactional標註到接口定義上、接口中的方法、類定義上、或者類的public方法上。但是僅存在@Transactional註釋不足以激活事務行爲。@Transactional註解只是元數據會在運行時被事務基礎設施感知然後使用元數據配置合適的bean產生事務行爲。在前面的示例中,<tx:annotation-driven/>元素會開啓事務行爲。

🔕🔕🔕

Spring組推薦你將@Transactional註解使用在具體的類上,而不是接口。當然,您可以將@Transactional註釋放在接口(或接口方法)上,但是只有在使用基於接口的代理時,纔會像您所期望的那樣工作。事實上Java註解並不會從接口中繼承意味着,如果你使用基於類的代理(```proxy-target-class=“true”)或者基於aspect的織入,事務設置不會被代理和aspect織入機制識別,則對象就不會被包裝進事務代理中。

🔕🔕🔕

在代理模式中(默認),只有通過代理傳入的外部方法調用纔會被攔截。這意味着自身調用(實際上,目標對象的一個方法調用該目標對象的另一個方法)在運行時是不會產生真實事務的,即使被調用的方法被@Transactional標註了。而且,代理必須完全初始化以提供預期行爲,因此,您不應該在初始化代碼(即@PostConstruct)中依賴該特性。

考慮使用 AspectJ 模式如果你希望自身調用可以進行事務性操作的話。在這個情況下,沒有代理。而目標類是被織入(字節碼被修改)後的任何方法的運行時將@Transactional加入其中。

tx:annotation-driven/ 註解驅動事務設置清單:

XML屬性 註解屬性 默認值 描述
transaction-manager N/A(查看TransactionManagementConfigurer transactionManager 事務管理器的name。當不是transactionManager時則需要配置
mode mode proxy 默認模式(proxy)處理註解bean,使用Spring AOP框架代理。可選項模式(aspectj)通過織入(修改字節碼)改變事務行爲
proxy-target-class proxyTargetClass false 僅僅在proxy模式下有用。控制使用@Transactional註釋爲類創建什麼類型的事務代理。如果設置爲true,基於類的代理會被創建。爲false或者忽略設置,則基於JDK接口動態代理的類被創建。可以查看Spring AOP獲取更多關於代理機制的信息。
order order Ordered.LOWEST_PRECEDENCE 定義@Transactional標註的事務通知的順序。更多關於通知順序的信息可以參考Spring AOP

🔕🔕🔕

默認處理@Transactional註解的通知模式是proxy,只允許通過代理攔截調用。同一類內的本地調用不能以這種方式被攔截。對於更高級的攔截模式,可以考慮結合編譯時或加載時織入切換到aspectj模式。

🔕🔕🔕

proxy-target-class屬性控制被@Transactional標註時創建何種類型的事務代理類。如果設置爲true,基於proxy的類會被創建。如果設置爲false或者忽略這個屬性,標準的基於JDK接口的代理會被創建。

🔕🔕🔕

@EnableTransactionManagement<tx:annotation-driven/>會查找在同一個上下文中被 @Transactional標註的bean。意味着,如果你在一個WebApplicationContextDispatcherServlet配置了annotation-driven的話,它會檢查@Transactional標記的controller的bean而不是你的service bean,可以查看SpringMVC

計算事務設置時,最派生的位置優先。 如下示例中,DefaultFooService類,在類定義中被註解標記爲一個只讀事務。但是在updateFoo(Foo)方法中標記了非只讀事務,且傳播行爲設置爲新建事務,則update方法的事務設置以@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)爲準:

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // do something
    }

    // 該方法以這個事務設置優先級別高
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}

@Transactional屬性設置

@Transactional 註解是擁有事務性語義的接口、類、方法的元數據(例如,開啓一個read-only事務,則當一個方法被調用時,會掛起已經存在的事務)。默認情況下@Transactional的屬性設置如下:

  • 傳播行爲設置爲 PROPAGATION_REQUIRED。
  • 隔離級別設置爲 ISOLATION_DEFAULT。
  • 事務是可讀可寫的。
  • 事務超時時間默認依賴底層事務系統,不支持超時則爲none。
  • 運行時異常會回滾事務,任何checked異常則不會。
@Transactional(rollbackFor = { Exception.class }, propagation = Propagation.REQUIRED)
public CoreOffsetReturn2AgentData handlerRepayFlowAndOffsetBill(
        String dataSourceKey, RepayServiceModel model,
        RepayOffsetCommonRequestDTO offsetCommonRequestDTO,
        RepayOrderNo repayOrderNo, Date offsetDate, List<String> loanNos,
        List<CoreOffsetCoupon2AgentParam> couponList, String mctNo)
        throws Exception {
            // TODO
}

設置屬性清單:

屬性 類型 描述
value String 指定要使用的事務管理器的可選限定符。
propagation enum: Propagation 可選的傳播行爲設置
isolation enum: Isolation 可選的事務隔離級別。僅僅在傳播行爲爲REQUIRED和REQUIRES_NEW時纔有效
timeout int 秒單位 可選的事務超時設置。僅僅在傳播行爲爲REQUIRED和REQUIRES_NEW時纔有效
readOnly boolean 讀寫事務與只讀事務的設置。僅僅在傳播行爲爲REQUIRED和REQUIRES_NEW時纔有效
rollbackFor Class數組,類型必須爲Throwable的派生類 可選的事務回滾指定的異常
rollbackForClassName String數組,指定類名 可選
noRollbackFor Class數組 可選項,用於指定不會引發事務回滾的異常
noRollbackForClassName String數組 可選

目前,您無法顯式控制事務的名稱,其中“name”表示出現在事務監視器(如果適用的話)和日誌輸出中的事務名稱(例如,WebLogic的事務監視器)。
對於聲明式事務,事務名稱總是爲 類的全限定名+.+方法名稱。例如,如果BusinessService類的handlePayment(...)方法被事務註解標記,則該事務名稱爲:com.example.BusinessService.handlePayment

使用@Transactional的多個事務管理器

大都數Spring應用只需要一個事務管理器,但是還是可能會在單個應用中使用多個獨立的事務管理器的。你可以使用value屬性指定需要使用的PlatformTransactionManager事務管理器。這可以是一個bean的name或者是事務管理器bean的限定名。例如,使用限定符,你可以將java代碼結合上下文中的事務管理bean一塊使用:

public class TransactionalService {

    @Transactional("order")
    public void setSomething(String name) { ... }

    @Transactional("account")
    public void doSomething() { ... }
}

以下是事務bean的配置聲明:

<tx:annotation-driven/>

<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    ...
    <qualifier value="order"/>
</bean>

<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    ...
    <qualifier value="account"/>
</bean>

在這個情況中,TransactionService中的兩個方法在不同的事務管理器中運行。

自定義快捷註解

如果你需要在不同方法中重複使用 @Transactional註解的相同屬性,Spring元註解支持可以讓你自定義快捷註解。

自定義快捷註解如下:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

使用自定義快捷註解:

public class TransactionalService {

    @OrderTx
    public void setSomething(String name) { ... }

    @AccountTx
    public void doSomething() { ... }
}

在前面的示例中,我們使用語法來定義事務管理器限定符,但是我們也可以包含傳播行爲、回滾規則、超時和其他特性。

事務傳播行爲

詳細描述了Spring中關於事務傳播的一些語義.

在spring管理的事務中,請注意物理事務和邏輯事務之間的差異,以及傳播設置如何應用於這種差異。

理解 PROPAGATION_REQUIRED

在這裏插入圖片描述

PROPAGATION_REQUIRED強制執行物理事務,如果當前範圍還不存在事務,則在本地執行當前範圍的事務,或者參與爲更大範圍定義的現有“外部”事務。這在同一個線程中通常是一個比較好的處理方式(例如,一個service門面委託給幾個repository的方法,其中底層資源必須參與service層的事務)。

默認情況下,一個事務參與了外部事務的特徵的話,會靜默地忽略本地事務隔離級別、超時設置、read-only標誌。考慮在你的事務管理中開啓validateExistingTransactions標誌爲true,如果你想拒絕接收外部事務隔離級別設置的話。這種非寬鬆模式還拒絕只讀不匹配(即,試圖參與只讀外部範圍的內部讀寫事務)。

當傳播行爲設置爲 PROPAGATION_REQUIRED 時,就會爲應用該設置的每個方法創建邏輯事務範圍。每個這樣的邏輯事務範圍都可以單獨確定回滾狀態,外部事務範圍在邏輯上獨立於內部事務範圍。標準的 PROPAGATION_REQUIRED 傳播行爲,所有這些事務範圍都會映射到物理事務中。所以如果一個內部事務標記了僅僅回滾的標誌會影響到外部事務提交的機會。

但是,當一個內部事務設置爲僅僅回滾的標記時,外部事務並沒有決定回滾本身,所以被內部事務觸發回滾操作不是外部事務所期望的。一個相應的UnexpectedRollbackException異常會被拋出。這是所期望的行爲,因此事務調用者永遠不會被誤導,以爲提交是在實際沒有執行的情況下執行的。So,如果一個內部事務(外部調用方並不知道)靜默地標記爲一個事務爲僅僅回滾,外部調用者仍然會調用commit。外部調用者需要接受一個UnexpectedRollbackException以清楚地表明執行了回滾。

理解 PROPAGATION_REQUIRES_NEW

在這裏插入圖片描述

PROPAGATION_REQUIRES_NEWPROPAGATION_REQUIRED剛好相反,總是爲每個可影響的事務範圍使用一個獨立的物理事務,從來不會參與外部已經存在的事務。
在這個佈置中,底層資源事務是不同的,因此,可以獨立提交或者回滾,外部事務不會受內部事務回滾狀態的影響,並且內部事務鎖會在它執行完後立馬釋放。
這樣的獨立的內部事務也可以聲明自己的隔離級別、超時時間、read-only屬性,並不會繼承外部事務的特徵。

理解 PROPAGATION_NESTED

PROPAGATION_NESTED在多個保存點savepoints中使用一個物理事務。所以一個內部事務的回滾會觸發其事務範圍內的回滾,外部事務可以繼續處理物理事務儘管已經回滾了一些操作。這個設置通常映射到JDBC保存點,所以僅僅在JDBC資源事務纔會工作。

事務通知操作

假設你想同時執行事務操作和profiling通知,如何在<tx:annotation-driven/>上下文中實現?

當你調用 updateFoo(Foo)方法的時候,你可能會看到以下行爲:

  • 啓動已配置的profiling aspect。
  • 執行事務通知(transactional advice)。
  • 被adviced的對象的執行方法。
  • 事務提交。
  • profiling aspect報告在整個事務方法調用時間。

以下是一個簡單的展示profiling aspect案例(StopWatch是一個關於打印時間的封裝,會記錄執行方法的耗時類似於System.currentTimeMillis()的一個改進,不建議使用在生產環境中。):

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;

public class SimpleProfiler implements Ordered {

    private int order;

    // allows us to control the ordering of advice
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    // this method is the around advice
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        Object returnValue;
        StopWatch clock = new StopWatch(getClass().getName());
        try {
            clock.start(call.toShortString());
            returnValue = call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
        return returnValue;
    }
}

通知的順序是通過Ordered接口控制的。
以下配置創建了一個fooServicebean,並且通過切面和事務通知指定了期望的順序:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this is the aspect -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- 更低的order爲1會使得其在事務前面執行,因爲後面的事務通知的order是200 -->
        <property name="order" value="1"/>
    </bean>

    <tx:annotation-driven transaction-manager="txManager" order="200"/>

    <aop:config>
            <!-- 這個通知會環繞事務通知執行 -->
            <aop:aspect id="profilingAspect" ref="profiler">
                <aop:pointcut id="serviceMethodWithReturnValue"
                        expression="execution(!void x.y..*Service.*(..))"/>
                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
            </aop:aspect>
    </aop:config>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

你可以以類似的方式配置任意數量的切面。
以下示例創建了同樣的設置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the profiling advice -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" value="1"/>
    </bean>

    <aop:config>
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
        <!-- will execute after the profiling advice (c.f. the order attribute) -->

        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
        <!-- order value is higher than the profiling aspect -->

        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
                    expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>

    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here -->

</beans>

上面的示例中fooServicebean被帶有order屬性的切面和事務通知作用。order值越小優先級越高。
參考:org.springframework.core.annotation.Order以及org.springframework.core.Ordered

通過AspectJ使用@Transactional註解

您還可以通過AspectJ切面在Spring容器之外使用Spring框架的@Transactional支持。
爲了達到這個目標,首先使用@Transactional註解標註你的類,然後使用spring-aspects.jar中定義的org.springframework.transaction.aspectj.AnnotationTransactionAspect織入到你的應用中去。
你也可以通過事務管理器配置切面。
你可以使用Spring框架的IOC容器來處理依賴注入的切面。
配置事務管理的切面的最簡單方式是使用<tx:annotation-driven/>元素並且指定mode屬性爲aspectj(上面已經提到過了)。因爲我們在這裏聚焦於在Spring容器外面使用,展示如何編程式處理。

以下示例展示瞭如何創建一個事務管理器、配置AnnotationTransactionAspect使用它:

// 構造一個核實的事務管理器
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());AnnotationTransactionAspect

// 配置 AnnotationTransactionAspect 使用;必須在事務方法之前執行。
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);

當你使用這個切面的時候,你必須註解到實現類上面(或者實現類的方法中),而不是接口上。Aspectj遵循Java規則–在接口上的註解是不會繼承的。

類中方法的@Transactional註解指定了類中的public方法的默認的事務語義。
類中方法的@Transactional註解覆蓋了類中(如果指定了的話)的事務語義。
爲了在你的應用中織入AnnotationTransactionAspect,你必須通過Aspectj構建你的應用(Aspectj開發指南)或者在加載時織入。加載時織入可以查看在Spring框架中通過AspectJ織入

編程式事務管理

Spring框架提供了兩種編程式事務管理方式,通過使用:

  • TransactionTemplate
  • PlatformTransactionManager實現

Spring項目組推薦編程式事務管理使用TransactionTemplate。第二種方式和使用 JTA UserTransaction API 類似,儘管異常處理沒那麼麻煩。

使用事務模板類TransactionTemplateTransactionCallback事務回調類

TransactionTemplate採用了和Spring中其他模板類如JdbcTemplate類似的方式。使用回調的方式(將應用代碼從必須執行樣板文件獲取和釋放事務資源中解放出來)產生意圖驅動的代碼,在你的代碼中你只需聚焦於做你想做的。

如下示例中,使用TransactionTemplate從Spring事務基礎結構的API中完全解耦出來。編程式事務管理是否適合你的開發需要由你自己決定。

應用代碼必須在事務上下文中執行並顯示使用TransactionTemplate
作爲一個程序開發者,你可以編寫一個TransactionCallback實現(通常表示爲匿名內部類)包含你想在事務上下文中執行的代碼。
然後,你需要將自定義的TransactionCallback實例傳入到TransactionTemplate暴露的execute(...)方法中,以下是兩個示例:

public class SimpleService implements Service {

    // single TransactionTemplate shared amongst all methods in this instance
    private final TransactionTemplate transactionTemplate;

    // use constructor-injection to supply the PlatformTransactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            // the code in this method executes in a transactional context
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

如果沒有返回值的話,可以使用TransactionCallbackWithoutResult更簡潔:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    @Override
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            repayConfirm(dataSourceKey, resultDto);
        } catch (Exception e) {
            logger.error("確認還款處理數據失敗:" , e);
            throw new BusinessException(e);
        }
    }
});
  • org.springframework.transaction.support.TransactionTemplate 操作事務模板類
  • org.springframework.transaction.support.TransactionCallback 事務回調,在該回調中可以編寫自己的代碼
  • org.springframework.transaction.support.TransactionCallbackWithoutResult Spring中自帶的一個事務回調實現抽象類

事務回調中的代碼可以調用TransactionStatussetRollbackOnly()方法回滾事務:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            updateOperation1();
            updateOperation2();
        } catch (SomeBusinessException ex) {
            status.setRollbackOnly();//回滾事務
        }
    }
});

指定事務設置

你可以在通過編程式或者配置中指定TransactionTemplate的事務設置(例如傳播行爲、隔離級別、超時設置或者其他的設置)。
默認情況下,TransactionTemplate具有默認的事務設置。

編程式設置:

public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;

    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);

        // the transaction settings can be set here explicitly if so desired
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
        this.transactionTemplate.setTimeout(30); // 30 seconds
        // and so forth...
    }
}

XML配置:

<bean id="sharedTransactionTemplate"
        class="org.springframework.transaction.support.TransactionTemplate">
    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
    <property name="timeout" value="30"/>
</bean>"

然後你可以將sharedTransactionTemplate這個bean注入到任何需要的service中去。

最後,TransactionTemplate類是線程安全的,在則種情況下不會維護任何會話狀態。
但是,TransactionTemplate實例,會維護配置狀態。所以,很多類會共享一個單例的TransactionTemplate實例。
如果一個類需要使用不同的配置,則需要才能創建不同的TransactionTemplate實例。

使用PlatformTransactionManager

也可以使用org.springframework.transaction.PlatformTransactionManager直接管理你的應用。爲此,請通過bean引用將使用的PlatformTransactionManager的實現傳遞給bean。然後,通過使用TransactionDefinitionTransactionStatus對象,你可以初始化事務、回滾、提交。

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 顯示指定事務名稱
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
    // 執行業務邏輯代碼
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

在編程式事務管理和聲明式事務管理二者的選擇

編程式事務管理通常是一個好點子但僅僅在於存在少部分的事務操作的場景中。例如,如果你有一個web應用僅僅在update操作中需要事務,你不想使用Spring或者其他技術設置事務代理。在這種情況下,使用TransactionTemplate可能是一個很好的方式。
另一方面,如果你的應用存在大量的事務性操作,聲明式事務管理更好,它容易配置、並且是在業務邏輯外面管理事務。
使用Spring框架而不是EJB CMT,聲明性事務管理的配置成本將大大降低。

Spring事務監聽器、事務綁定的事件

從Spring4.2開始,一個事件的監聽器可以綁定到事務的各個階段。典型的示例就是在事務成功處理完成後處理事件。
當事務結果對監聽器很重要的時候,這個特點可以使得事務處理更靈活。

你可以使用@EventListener註解註冊一個常規的事件監聽器。如果你想將其綁定到事務中去,則使用@TransactionEventListener。默認情況下,是綁定到commit階段的。

以下示例展示了該概念,假設組件發佈了一個訂單創建的事件,並且我們希望定義一個監聽器,該監聽器應該只在發佈事件的事務提交成功後才處理該事件。

@Component
public class MyComponent{
    @TransactionalEventListener
    public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent){
        ...
    }
}

@TransactionalEventListener註解暴露了一個事務階段的屬性。可以讓你自定義綁定到事務的某個階段。TransactionPhase枚舉類指定了有效的事務階段值:

  • BEFORE_COMMIT : 事務提交之前
  • AFTER_COMMIT : 事務提交成功之後。默認值。
  • AFTER_ROLLBACK : 事務回滾。
  • AFTER_COMPLETION : 事務完成(包含回滾、或者提交完成)。

如果沒有事務運行,監聽器則不會被調用,因爲不能遵守所需的事務語義。但是你可以通過設置fallbackExecution屬性爲true來覆蓋其行爲。

特定於應用服務器的集成

Spring的事務抽象通常與應用服務器無關。另外,Spring的JtaTransactionManager類(可以選擇通過JNDI查找JTA UserTransaction和TransactionManager對象)自動發現後一個對象的位置,後者因應用服務器的不同而不同。
在訪問JTA TransactionManager時允許增強的事務語義——特別的,支持事務掛起。可以查看JtaTransactionManager獲取更多的信息。

Spring的JtaTransactionManager是衆所周知的運行Java EE應用服務的標準選擇。更高級的功能:例如事務掛起,在很多服務器中(GlassFish、JBoss等)不需要做額外的配置就能運行很好。但是,爲了支持完整的事務掛起和更多高級功能的集成,Spring爲WebLogic服務器和WebSphere服務器指定了特殊的適配器。

對於標準的場景,包括WebLogic服務器和WebSphere服務器,考慮使用<tx:jta-transaction-manager/>配置元素。在配置的時候,這個元素會自動地查找出下面的服務並選擇適用於平臺最好的事務管理器。這意味着你不必顯示地配置服務指定地適配器,相反JtaTransactionManager會自動地選擇。

IBM WebSphere

在WebSphere 6.1.0.9及以上版本中,推薦使用的Spring JTA事務管理器是WebSphereUowTransactionManager。這個適配器使用了IBM的UOWManager的API,存在於WebSphere 6.1.0.9及以上版本的服務器中。
使用這個適配器,支持Spring驅動的事務掛起(掛起和恢復由PROPAGATION_REQUIRES_NEW啓動)。

Oracle WebLogic 服務器

在WebLogic Server 9.0或更高版本上,通常使用WebLogicJtaTransactionManager而不是JtaTransactionManager類。這是JtaTransactionManager的一個子類,在weblogic管理的事務環境中支持Spring事務定義的全部功能,超出標準JTA語義。
特性包括:事務名稱、事務隔離級別設置、合適的事務重啓機制等。

常見問題的解決方案

對於指定的DataSource使用了錯誤的事務管理器

根據你的事務技術選擇和需求選擇正確的PlatformTransactionManager實現。爲了使用適當,Spring僅僅提供了一個簡單且可移植的抽象。如果你要使用全局性事務,你必須使用org.springframework.transaction.jta.JtaTransactionManager類。否則,事務基礎結構會將嘗試對容器數據源實例等資源執行本地事務。這樣的本地事務沒有意義,好的應用程序服務器會將它們視爲錯誤。

更多資源

更多關於Spring框架事務支持,可以查看:

Spring框架事務配置總結

  • 配置數據源DataSource
  • 配置事務管理器即PlatformTransactionManager相應的合適實現
  • 有必要則配置事務通知(或者註解 @Transactional)
  • 有必要則配置自定義AOP通知

Spring框架事務讀源碼類

  • org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction

  • org.springframework.jdbc.datasource.DataSourceTransactionManager.DataSourceTransactionObject

  • org.springframework.jdbc.datasource.JdbcTransactionObjectSupport#createSavepoint

  • org.springframework.jdbc.datasource.JdbcTransactionObjectSupport#rollbackToSavepoint

  • org.springframework.jdbc.datasource.ConnectionHolder 事務提交、回滾的委託類,佔有一個java.sql.Connection

  • org.springframework.transaction.annotation.EnableTransactionManagement 事務管理配置註解

  • org.springframework.transaction.support.DefaultTransactionDefinition 默認事務行爲類

  • org.springframework.transaction.support.TransactionTemplate 操作事務模板類

  • org.springframework.transaction.support.TransactionCallback 事務回調,在該回調中可以編寫自己的代碼

  • org.springframework.transaction.support.TransactionCallbackWithoutResult Spring中自帶的一個事務回調實現抽象類

  • org.springframework.transaction.event.TransactionalEventListener 事務監聽器

  • org.springframework.transaction.event.TransactionPhase 事務階段

  • org.springframework.transaction.support.TransactionSynchronization 用於事務同步回調的接口

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