Spring事務管理(詳解+例項)

thetimelyrain發表於2020-12-08

目錄

1 初步理解

2 核心介面

2.1 事務管理器

2.1.1 JDBC事務

2.1.2 Hibernate事務

2.1.3 Java持久化API事務(JPA)

2.1.4 Java原生API事務

2.2 基本事務屬性的定義

2.2.1 傳播行為

2.2.2 隔離級別

2.2.3 只讀

2.2.4 事務超時

2.2.5 回滾規則

2.3 事務狀態

3 程式設計式事務

3.1 程式設計式和宣告式事務的區別

3.2 如何實現程式設計式事務?

3.2.1 使用TransactionTemplate

3.2.2 使用PlatformTransactionManager

4 宣告式事務

4.1 配置方式

4.2 一個宣告式事務的例項


寫這篇部落格之前我首先讀了《Spring in action》,之後在網上看了一些關於Spring事務管理的文章,感覺都沒有講全,這裡就將書上的和網上關於事務的知識總結一下,參考的文章如下:

1 初步理解

理解事務之前,先講一個你日常生活中最常乾的事:取錢。
比如你去ATM機取1000塊錢,大體有兩個步驟:首先輸入密碼金額,銀行卡扣掉1000元錢;然後ATM出1000元錢。這兩個步驟必須是要麼都執行要麼都不執行。如果銀行卡扣除了1000塊但是ATM出錢失敗的話,你將會損失1000元;如果銀行卡扣錢失敗但是ATM卻出了1000塊,那麼銀行將損失1000元。所以,如果一個步驟成功另一個步驟失敗對雙方都不是好事,如果不管哪一個步驟失敗了以後,整個取錢過程都能回滾,也就是完全取消所有操作的話,這對雙方都是極好的。
事務就是用來解決類似問題的。事務是一系列的動作,它們綜合在一起才是一個完整的工作單元,這些動作必須全部完成,如果有一個失敗的話,那麼事務就會回滾到最開始的狀態,彷彿什麼都沒發生過一樣。
在企業級應用程式開發中,事務管理必不可少的技術,用來確保資料的完整性和一致性。
事務有四個特性:ACID

  • 原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要麼全部完成,要麼完全不起作用。
  • 一致性(Consistency):一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的資料不應該被破壞。
  • 隔離性(Isolation):可能有許多事務會同時處理相同的資料,因此每個事務都應該與其他事務隔離開來,防止資料損壞。
  • 永續性(Durability):一旦事務完成,無論發生什麼系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化儲存器中。

2 核心介面

Spring事務管理的實現有許多細節,如果對整個介面框架有個大體瞭解會非常有利於我們理解事務,下面通過講解Spring的事務介面來了解Spring實現事務的具體策略。
Spring事務管理涉及的介面的聯絡如下:

這裡寫圖片描述

2.1 事務管理器

Spring並不直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委託給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。
Spring事務管理器的介面是org.springframework.transaction.PlatformTransactionManager,通過這個介面,Spring為各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,但是具體的實現就是各個平臺自己的事情了。此介面的內容如下:

Public interface PlatformTransactionManager()...{  
    // 由TransactionDefinition得到TransactionStatus物件
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滾
    Void rollback(TransactionStatus status) throws TransactionException;  
    } 

從這裡可知具體的具體的事務管理機制對Spring來說是透明的,它並不關心那些,那些是對應各個平臺需要關心的,所以Spring事務管理的一個優點就是為不同的事務API提供一致的程式設計模型,如JTA、JDBC、Hibernate、JPA。下面分別介紹各個平臺框架實現事務管理的機制。

2.1.1 JDBC事務

如果應用程式中直接使用JDBC來進行持久化,DataSourceTransactionManager會為你處理事務邊界。為了使用DataSourceTransactionManager,你需要使用如下的XML將其裝配到應用程式的上下文定義中:

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

實際上,DataSourceTransactionManager是通過呼叫java.sql.Connection來管理事務,而後者是通過DataSource獲取到的。通過呼叫連線的commit()方法來提交事務,同樣,事務失敗則通過呼叫rollback()方法進行回滾。

2.1.2 Hibernate事務

如果應用程式的持久化是通過Hibernate實習的,那麼你需要使用HibernateTransactionManager。對於Hibernate3,需要在Spring上下文定義中新增如下的<bean>宣告:

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

sessionFactory屬性需要裝配一個Hibernate的session工廠,HibernateTransactionManager的實現細節是它將事務管理的職責委託給org.hibernate.Transaction物件,而後者是從Hibernate Session中獲取到的。當事務成功完成時,HibernateTransactionManager將會呼叫Transaction物件的commit()方法,反之,將會呼叫rollback()方法。

2.1.3 Java持久化API事務(JPA)

Hibernate多年來一直是事實上的Java持久化標準,但是現在Java持久化API作為真正的Java持久化標準進入大家的視野。如果你計劃使用JPA的話,那你需要使用Spring的JpaTransactionManager來處理事務。你需要在Spring中這樣配置JpaTransactionManager:

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

JpaTransactionManager只需要裝配一個JPA實體管理工廠(javax.persistence.EntityManagerFactory介面的任意實現)。JpaTransactionManager將與由工廠所產生的JPA EntityManager合作來構建事務。

2.1.4 Java原生API事務

如果你沒有使用以上所述的事務管理,或者是跨越了多個事務管理源(比如兩個或者是多個不同的資料來源),你就需要使用JtaTransactionManager:

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:/TransactionManager" />
    </bean>

JtaTransactionManager將事務管理的責任委託給javax.transaction.UserTransaction和javax.transaction.TransactionManager物件,其中事務成功完成通過UserTransaction.commit()方法提交,事務失敗通過UserTransaction.rollback()方法回滾。

2.2 基本事務屬性的定義

上面講到的事務管理器介面PlatformTransactionManager通過getTransaction(TransactionDefinition definition)方法來得到事務,這個方法裡面的引數是TransactionDefinition類,這個類就定義了一些基本的事務屬性。
那麼什麼是事務屬性呢?事務屬性可以理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面,如圖所示:

這裡寫圖片描述

而TransactionDefinition介面內容如下:

public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事務的傳播行為
    int getIsolationLevel(); // 返回事務的隔離級別,事務管理器根據它來控制另外一個事務可以看到本事務內的哪些資料
    int getTimeout();  // 返回事務必須在多少秒內完成
    boolean isReadOnly(); // 事務是否只讀,事務管理器能夠根據這個返回值進行優化,確保事務是隻讀的
} 

我們可以發現TransactionDefinition正好用來定義事務屬性,下面詳細介紹一下各個事務屬性。

2.2.1 傳播行為

事務的第一個方面是傳播行為(propagation behavior)。當事務方法被另一個事務方法呼叫時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中執行,也可能開啟一個新事務,並在自己的事務中執行。Spring定義了七種傳播行為:

傳播行為含義
PROPAGATION_REQUIRED表示當前方法必須執行在事務中。如果當前事務存在,方法將會在該事務中執行。否則,會啟動一個新的事務
PROPAGATION_SUPPORTS表示當前方法不需要事務上下文,但是如果存在當前事務的話,那麼該方法會在這個事務中執行
PROPAGATION_MANDATORY表示該方法必須在事務中執行,如果當前事務不存在,則會丟擲一個異常
PROPAGATION_REQUIRED_NEW表示當前方法必須執行在它自己的事務中。一個新的事務將被啟動。如果存在當前事務,在該方法執行期間,當前事務會被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager
PROPAGATION_NOT_SUPPORTED表示該方法不應該執行在事務中。如果存在當前事務,在該方法執行期間,當前事務將被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager
PROPAGATION_NEVER表示當前方法不應該執行在事務上下文中。如果當前正有一個事務在執行,則會丟擲異常
PROPAGATION_NESTED表示如果當前已經存在一個事務,那麼該方法將會在巢狀事務中執行。巢狀的事務可以獨立於當前事務進行單獨地提交或回滾。如果當前事務不存在,那麼其行為與PROPAGATION_REQUIRED一樣。注意各廠商對這種傳播行為的支援是有所差異的。可以參考資源管理器的文件來確認它們是否支援巢狀事務


注:以下具體講解傳播行為的內容參考自Spring事務機制詳解
(1)PROPAGATION_REQUIRED 如果存在一個事務,則支援當前事務。如果沒有事務則開啟一個新的事務。

//事務屬性 PROPAGATION_REQUIRED
methodA{
    ……
    methodB();
    ……
}
//事務屬性 PROPAGATION_REQUIRED
methodB{
   ……
}

使用spring宣告式事務,spring使用AOP來支援宣告式事務,會根據事務屬性,自動在方法呼叫之前決定是否開啟一個事務,並在方法執行之後決定事務提交或回滾事務。

單獨呼叫methodB方法:

main{ 
    metodB(); 
}  

相當於

Main{ 
    Connection con=null; 
    try{ 
        con = getConnection(); 
        con.setAutoCommit(false); 

        //方法呼叫
        methodB(); 

        //提交事務
        con.commit(); 
    } Catch(RuntimeException ex) { 
        //回滾事務
        con.rollback();   
    } finally { 
        //釋放資源
        closeCon(); 
    } 
} 

Spring保證在methodB方法中所有的呼叫都獲得到一個相同的連線。在呼叫methodB時,沒有一個存在的事務,所以獲得一個新的連線,開啟了一個新的事務。
單獨呼叫MethodA時,在MethodA內又會呼叫MethodB.

執行效果相當於:

main{ 
    Connection con = null; 
    try{ 
        con = getConnection(); 
        methodA(); 
        con.commit(); 
    } catch(RuntimeException ex) { 
        con.rollback(); 
    } finally {    
        closeCon(); 
    }  
} 

呼叫MethodA時,環境中沒有事務,所以開啟一個新的事務.當在MethodA中呼叫MethodB時,環境中已經有了一個事務,所以methodB就加入當前事務。

(2)PROPAGATION_SUPPORTS 如果存在一個事務,支援當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。

//事務屬性 PROPAGATION_REQUIRED
methodA(){
  methodB();
}

//事務屬性 PROPAGATION_SUPPORTS
methodB(){
  ……
}

單純的呼叫methodB時,methodB方法是非事務的執行的。當呼叫methdA時,methodB則加入了methodA的事務中,事務地執行。

(3)PROPAGATION_MANDATORY 如果已經存在一個事務,支援當前事務。如果沒有一個活動的事務,則丟擲異常。

//事務屬性 PROPAGATION_REQUIRED
methodA(){
    methodB();
}

//事務屬性 PROPAGATION_MANDATORY
    methodB(){
    ……
}

當單獨呼叫methodB時,因為當前沒有一個活動的事務,則會丟擲異常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);當呼叫methodA時,methodB則加入到methodA的事務中,事務地執行。

(4)PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。

//事務屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事務屬性 PROPAGATION_REQUIRES_NEW
methodB(){
    ……
}

呼叫A方法:

main(){
    methodA();
}

相當於

main(){
    TransactionManager tm = null;
    try{
        //獲得一個JTA事務管理器
        tm = getTransactionManager();
        tm.begin();//開啟一個新的事務
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//掛起當前事務
        try{
            tm.begin();//重新開啟第二個事務
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二個事務
        } Catch(RunTimeException ex) {
            ts2.rollback();//回滾第二個事務
        } finally {
            //釋放資源
        }
        //methodB執行完後,恢復第一個事務
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一個事務
    } catch(RunTimeException ex) {
        ts1.rollback();//回滾第一個事務
    } finally {
        //釋放資源
    }
}

在這裡,我把ts1稱為外層事務,ts2稱為內層事務。從上面的程式碼可以看出,ts2與ts1是兩個獨立的事務,互不相干。Ts2是否成功並不依賴於 ts1。如果methodA方法在呼叫methodB方法後的doSomeThingB方法失敗了,而methodB方法所做的結果依然被提交。而除了 methodB之外的其它程式碼導致的結果卻被回滾了。使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作為事務管理器。

(5)PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作為事務管理器。(程式碼示例同上,可同理推出)

(6)PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則丟擲異常。

(7)PROPAGATION_NESTED如果一個活動的事務存在,則執行在一個巢狀的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行。這是一個巢狀事務,使用JDBC 3.0驅動時,僅僅支援DataSourceTransactionManager作為事務管理器。需要JDBC 驅動的java.sql.Savepoint類。有一些J他的事務管理器實現可能也提供了同樣的功能。使用PROPAGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設為true;而 nestedTransactionAllowed屬性值預設為false。

//事務屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事務屬性 PROPAGATION_NESTED
methodB(){
    ……
}

如果單獨呼叫methodB方法,則按REQUIRED屬性執行。如果呼叫methodA方法,相當於下面的效果:

main(){
    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
            methodB();
        } catch(RuntimeException ex) {
            con.rollback(savepoint);
        } finally {
            //釋放資源
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally {
        //釋放資源
    }
}

當methodB方法呼叫之前,呼叫setSavepoint方法,儲存當前的狀態到savepoint。如果methodB方法呼叫失敗,則恢復到之前儲存的狀態。但是需要注意的是,這時的事務並沒有進行提交,如果後續的程式碼(doSomeThingB()方法)呼叫失敗,則回滾包括methodB方法的所有操作。

巢狀事務一個非常重要的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所做的動作。而內層事務操作失敗並不會引起外層事務的回滾。

PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區別:它們非常類似,都像一個巢狀事務,如果不存在一個活動的事務,都會開啟一個新的事務。使用 PROPAGATION_REQUIRES_NEW時,內層事務與外層事務就像兩個獨立的事務一樣,一旦內層事務進行了提交後,外層事務不能對其進行回滾。兩個事務互不影響。兩個事務不是一個真正的巢狀事務。同時它需要JTA事務管理器的支援。

使用PROPAGATION_NESTED時,外層事務的回滾可以引起內層事務的回滾。而內層事務的異常並不會導致外層事務的回滾,它是一個真正的巢狀事務。DataSourceTransactionManager使用savepoint支援PROPAGATION_NESTED時,需要JDBC 3.0以上驅動及1.4以上的JDK版本支援。其它的JTA TrasactionManager實現可能有不同的支援方式。

PROPAGATION_REQUIRES_NEW 啟動一個新的, 不依賴於環境的 “內部” 事務. 這個事務將被完全 commited 或 rolled back 而不依賴於外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行。

另一方面, PROPAGATION_NESTED 開始一個 “巢狀的” 事務, 它是已經存在事務的一個真正的子事務. 潛套事務開始執行時, 它將取得一個 savepoint. 如果這個巢狀事務失敗, 我們將回滾到此 savepoint. 潛套事務是外部事務的一部分, 只有外部事務結束後它才會被提交。

由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於, PROPAGATION_REQUIRES_NEW 完全是一個新的事務, 而 PROPAGATION_NESTED 則是外部事務的子事務, 如果外部事務 commit, 巢狀事務也會被 commit, 這個規則同樣適用於 roll back.

PROPAGATION_REQUIRED應該是我們首先的事務傳播行為。它能夠滿足我們大多數的事務需求。

2.2.2 隔離級別

事務的第二個維度就是隔離級別(isolation level)。隔離級別定義了一個事務可能受其他併發事務影響的程度。
(1)併發事務引起的問題
在典型的應用程式中,多個事務併發執行,經常會操作相同的資料來完成各自的任務。併發雖然是必須的,但可能會導致一下的問題。

  • 髒讀(Dirty reads)——髒讀發生在一個事務讀取了另一個事務改寫但尚未提交的資料時。如果改寫在稍後被回滾了,那麼第一個事務獲取的資料就是無效的。
  • 不可重複讀(Nonrepeatable read)——不可重複讀發生在一個事務執行相同的查詢兩次或兩次以上,但是每次都得到不同的資料時。這通常是因為另一個併發事務在兩次查詢期間進行了更新。
  • 幻讀(Phantom read)——幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行資料,接著另一個併發事務(T2)插入了一些資料時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄。

不可重複讀與幻讀的區別

不可重複讀的重點是修改:
同樣的條件, 你讀取過的資料, 再次讀取出來發現值不一樣了
例如:在事務1中,Mary 讀取了自己的工資為1000,操作並沒有完成

    con1 = getConnection();  
    select salary from employee empId ="Mary";  

在事務2中,這時財務人員修改了Mary的工資為2000,並提交了事務.

    con2 = getConnection();  
    update employee set salary = 2000;  
    con2.commit();  

在事務1中,Mary 再次讀取自己的工資時,工資變為了2000

    //con1  
    select salary from employee empId ="Mary"; 

在一個事務中前後兩次讀取的結果並不一致,導致了不可重複讀。

幻讀的重點在於新增或者刪除:
同樣的條件, 第1次和第2次讀出來的記錄數不一樣
例如:目前工資為1000的員工有10人。事務1,讀取所有工資為1000的員工。

    con1 = getConnection();  
    Select * from employee where salary =1000; 

共讀取10條記錄

這時另一個事務向employee表插入了一條員工記錄,工資也為1000

    con2 = getConnection();  
    Insert into employee(empId,salary) values("Lili",1000);  
    con2.commit();  

事務1再次讀取所有工資為1000的員工

    //con1  
    select * from employee where salary =1000;  

共讀取到了11條記錄,這就產生了幻像讀。

從總的結果來看, 似乎不可重複讀和幻讀都表現為兩次讀取的結果不一致。但如果你從控制的角度來看, 兩者的區別就比較大。
對於前者, 只需要鎖住滿足條件的記錄。
對於後者, 要鎖住滿足條件及其相近的記錄。

(2)隔離級別

隔離級別含義
ISOLATION_DEFAULT使用後端資料庫預設的隔離級別
ISOLATION_READ_UNCOMMITTED最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致髒讀、幻讀或不可重複讀
ISOLATION_READ_COMMITTED允許讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
ISOLATION_REPEATABLE_READ對同一欄位的多次讀取結果都是一致的,除非資料是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生
ISOLATION_SERIALIZABLE最高的隔離級別,完全服從ACID的隔離級別,確保阻止髒讀、不可重複讀以及幻讀,也是最慢的事務隔離級別,因為它通常是通過完全鎖定事務相關的資料庫表來實現的

2.2.3 只讀

事務的第三個特性是它是否為只讀事務。如果事務只對後端的資料庫進行該操作,資料庫可以利用事務的只讀特性來進行一些特定的優化。通過將事務設定為只讀,你就可以給資料庫一個機會,讓它應用它認為合適的優化措施。

2.2.4 事務超時

為了使應用程式很好地執行,事務不能執行太長的時間。因為事務可能涉及對後端資料庫的鎖定,所以長時間的事務會不必要的佔用資料庫資源。事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那麼就會自動回滾,而不是一直等待其結束。

2.2.5 回滾規則

事務五邊形的最後一個方面是一組規則,這些規則定義了哪些異常會導致事務回滾而哪些不會。預設情況下,事務只有遇到執行期異常時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的)
但是你可以宣告事務在遇到特定的檢查型異常時像遇到執行期異常那樣回滾。同樣,你還可以宣告事務遇到特定的異常不回滾,即使這些異常是執行期異常。

2.3 事務狀態

上面講到的呼叫PlatformTransactionManager介面的getTransaction()的方法得到的是TransactionStatus介面的一個實現,這個介面的內容如下:

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢復點
    void setRollbackOnly();  // 設定為只回滾
    boolean isRollbackOnly(); // 是否為只回滾
    boolean isCompleted; // 是否已完成
} 

可以發現這個介面描述的是一些處理事務提供簡單的控制事務執行和查詢事務狀態的方法,在回滾或提交的時候需要應用對應的事務狀態。

3 程式設計式事務

3.1 程式設計式和宣告式事務的區別

Spring提供了對程式設計式事務和宣告式事務的支援,程式設計式事務允許使用者在程式碼中精確定義事務的邊界,而宣告式事務(基於AOP)有助於使用者將操作與事務規則進行解耦。
簡單地說,程式設計式事務侵入到了業務程式碼裡面,但是提供了更加詳細的事務管理;而宣告式事務由於基於AOP,所以既能起到事務管理的作用,又可以不影響業務程式碼的具體實現。

3.2 如何實現程式設計式事務?

Spring提供兩種方式的程式設計式事務管理,分別是:使用TransactionTemplate和直接使用PlatformTransactionManager。

3.2.1 使用TransactionTemplate

採用TransactionTemplate和採用其他Spring模板,如JdbcTempalte和HibernateTemplate是一樣的方法。它使用回撥方法,把應用程式從處理取得和釋放資源中解脫出來。如同其他模板,TransactionTemplate是執行緒安全的。程式碼片段:

    TransactionTemplate tt = new TransactionTemplate(); // 新建一個TransactionTemplate
    Object result = tt.execute(
        new TransactionCallback(){  
            public Object doTransaction(TransactionStatus status){  
                updateOperation();  
                return resultOfUpdateOperation();  
            }  
    }); // 執行execute方法進行事務管理

使用TransactionCallback()可以返回一個值。如果使用TransactionCallbackWithoutResult則沒有返回值。

3.2.2 使用PlatformTransactionManager

示例程式碼如下:


    DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定義一個某個框架平臺的TransactionManager,如JDBC、Hibernate
    dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 設定資料來源
    DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定義事務屬性
    transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 設定傳播行為屬性
    TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 獲得事務狀態
    try {
        // 資料庫操作
        dataSourceTransactionManager.commit(status);// 提交
    } catch (Exception e) {
        dataSourceTransactionManager.rollback(status);// 回滾
    }

4 宣告式事務

4.1 配置方式

注:以下配置程式碼參考自Spring事務配置的五種方式

根據代理機制的不同,總結了五種Spring事務的配置方式,配置檔案如下:

(1)每個Bean都有一個代理

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務管理器(宣告式的事務) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 
           <!-- 配置事務管理器 --> 
           <property name="transactionManager" ref="transactionManager" />    
        <property name="target" ref="userDaoTarget" /> 
         <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
        <!-- 配置事務屬性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props> 
        </property> 
    </bean> 
</beans>

(2)所有Bean共享一個代理基類

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務管理器(宣告式的事務) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="transactionBase" 
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
            lazy-init="true" abstract="true"> 
        <!-- 配置事務管理器 --> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事務屬性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>   

    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" parent="transactionBase" > 
        <property name="target" ref="userDaoTarget" />  
    </bean>
</beans>

(3)使用攔截器

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務管理器(宣告式的事務) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 

    <bean id="transactionInterceptor" 
        class="org.springframework.transaction.interceptor.TransactionInterceptor"> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事務屬性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 
        <property name="beanNames"> 
            <list> 
                <value>*Dao</value>
            </list> 
        </property> 
        <property name="interceptorNames"> 
            <list> 
                <value>transactionInterceptor</value> 
            </list> 
        </property> 
    </bean> 

    <!-- 配置DAO -->
    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

(4)使用tx標籤配置的攔截器

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務管理器(宣告式的事務) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="interceptorPointCuts"
            expression="execution(* com.bluesky.spring.dao.*.*(..))" />
        <aop:advisor advice-ref="txAdvice"
            pointcut-ref="interceptorPointCuts" />       
    </aop:config>     
</beans>

(5)全註解

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務管理器(宣告式的事務) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

</beans>

此時在DAO上需加上@Transactional註解,如下:

package com.bluesky.spring.dao;

import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;

import com.bluesky.spring.domain.User;

@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {

    public List<User> listUsers() {
        return this.getSession().createQuery("from User").list();
    }  
}

4.2 一個宣告式事務的例項

注:該例項參考自Spring中的事務管理例項詳解

首先是資料庫表
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)

然後是XML配置

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <import resource="applicationContext-db.xml" />

    <context:component-scan
        base-package="com.springinaction.transaction">
    </context:component-scan>

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

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

</beans>

使用的類
BookShopDao

package com.springinaction.transaction;

public interface BookShopDao {
    // 根據書號獲取書的單價
    public int findBookPriceByIsbn(String isbn);
    // 更新書的庫存,使書號對應的庫存-1
    public void updateBookStock(String isbn);
    // 更新使用者的賬戶餘額:account的balance-price
    public void updateUserAccount(String username, int price);
}

BookShopDaoImpl

package com.springinaction.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {

    @Autowired
    private JdbcTemplate JdbcTemplate;

    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "SELECT price FROM book WHERE isbn = ?";

        return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
        //檢查書的庫存是否足夠,若不夠,則丟擲異常
        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if (stock == 0) {
            throw new BookStockException("庫存不足!");
        }
        String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
        JdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price) {
        //檢查餘額是否不足,若不足,則丟擲異常
        String sql2 = "SELECT balance FROM account WHERE username = ?";
        int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
        if (balance < price) {
            throw new UserAccountException("餘額不足!");
        }       
        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
        JdbcTemplate.update(sql, price, username);
    }

}

BookShopService

package com.springinaction.transaction;
public interface BookShopService {
     public void purchase(String username, String isbn);
}

BookShopServiceImpl

package com.springinaction.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    /**
     * 1.新增事務註解
     * 使用propagation 指定事務的傳播行為,即當前的事務方法被另外一個事務方法呼叫時如何使用事務。
     * 預設取值為REQUIRED,即使用呼叫方法的事務
     * REQUIRES_NEW:使用自己的事務,呼叫的事務方法的事務被掛起。
     *
     * 2.使用isolation 指定事務的隔離級別,最常用的取值為READ_COMMITTED
     * 3.預設情況下 Spring 的宣告式事務對所有的執行時異常進行回滾,也可以通過對應的屬性進行設定。通常情況下,預設值即可。
     * 4.使用readOnly 指定事務是否為只讀。 表示這個事務只讀取資料但不更新資料,這樣可以幫助資料庫引擎優化事務。若真的是一個只讀取資料庫值得方法,應設定readOnly=true
     * 5.使用timeOut 指定強制回滾之前事務可以佔用的時間。
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            noRollbackFor={UserAccountException.class},
            readOnly=true, timeout=3)
    @Override
    public void purchase(String username, String isbn) {
        //1.獲取書的單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新書的庫存
        bookShopDao.updateBookStock(isbn);
        //3.更新使用者餘額
        bookShopDao.updateUserAccount(username, price);
    }
}

Cashier

package com.springinaction.transaction;
import java.util.List;
public interface Cashier {
    public void checkout(String username, List<String>isbns);
}

CashierImpl:CashierImpl.checkout和bookShopService.purchase聯合測試了事務的傳播行為

package com.springinaction.transaction;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("cashier")
public class CashierImpl implements Cashier {
    @Autowired
    private BookShopService bookShopService;

    @Transactional
    @Override
    public void checkout(String username, List<String> isbns) {
        for(String isbn : isbns) {
            bookShopService.purchase(username, isbn);
        }
    }
}

BookStockException


package com.springinaction.transaction;
public class BookStockException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public BookStockException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1) {
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(Throwable arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

UserAccountException

package com.springinaction.transaction;
public class UserAccountException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public UserAccountException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1) {
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(Throwable arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

測試類

package com.springinaction.transaction;

import java.util.Arrays;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTransitionTest {

    private ApplicationContext ctx = null;
    private BookShopDao bookShopDao = null;
    private BookShopService bookShopService = null;
    private Cashier cashier = null;
    {
        ctx = new ClassPathXmlApplicationContext("config/transaction.xml");
        bookShopDao = ctx.getBean(BookShopDao.class);
        bookShopService = ctx.getBean(BookShopService.class);
        cashier = ctx.getBean(Cashier.class);
    }

    @Test
    public void testBookShopDaoFindPriceByIsbn() {
        System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
    }

    @Test
    public void testBookShopDaoUpdateBookStock(){
        bookShopDao.updateBookStock("1001");
    }

    @Test
    public void testBookShopDaoUpdateUserAccount(){
        bookShopDao.updateUserAccount("AA", 100);
    }
    @Test
    public void testBookShopService(){
        bookShopService.purchase("AA", "1001");
    }

    @Test
    public void testTransactionPropagation(){
        cashier.checkout("AA", Arrays.asList("1001", "1002"));
    }
}

OVER

原文連結:https://blog.csdn.net/trigl/article/details/50968079

由於原文寫的實在太好,為了以後自己複習特此轉載,侵刪。

相關文章