Spring事務詳解

1 初步理解

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

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

2 核心接口

Spring事務管理的實現有許多細節,若是對整個接口框架有個大致瞭解會很是有利於咱們理解事務,下面經過講解Spring的事務接口來了解Spring實現事務的具體策略。 
Spring事務管理涉及的接口的聯繫以下:java

技術分享

2.1 事務管理器

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

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。下面分別介紹各個平臺框架實現事務管理的機制。sql

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()方法進行回滾。express

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合做來構建事務。session

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類。有一些JTA的事務管理器實現可能也提供了一樣的功能。使用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"));
    }
}
相關文章
相關標籤/搜索