1. 程式人生 > >Spring學習心得(24)-- Spring事務管理(詳解+例項)

Spring學習心得(24)-- Spring事務管理(詳解+例項)

Spring事務機制詳解  http://www.mamicode.com/info-detail-1248286.html
Spring事務配置的五種方式
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類。有一些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"));
    }
}
事物管理對於企業應用來說是至關重要的,好使出現異常情況,它也可以保證資料的一致性。
spring支援程式設計式事務管理和宣告式事務管理兩種方式。
        程式設計式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於程式設計式事務管理,spring推薦使用TransactionTemplate。

        宣告式事務管理建立在AOP之上的。其本質是對方法前後進行攔截,然後在目標方法開始之前建立或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。宣告式事務最大的優點就是不需要通過程式設計的方式管理事務,這樣就不需要在業務邏輯程式碼中摻雜事務管理的程式碼,只需在配置檔案中做相關的事務規則宣告(或通過基於@Transactional註解的方式),便可以將事務規則應用到業務邏輯中。

       顯然宣告式事務管理要優於程式設計式事務管理,這正是spring倡導的非侵入式的開發方式。宣告式事務管理使業務程式碼不受汙染,一個普通的POJO物件,只要加上註解就可以獲得完全的事務支援。和程式設計式事務相比,宣告式事務唯一不足地方是,後者的最細粒度只能作用到方法級別,無法做到像程式設計式事務那樣可以作用到程式碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的程式碼塊獨立為方法等等。

         宣告式事務管理也有兩種常用的方式,一種是基於tx和aop名字空間的xml配置檔案,另一種就是基於@Transactional註解。顯然基於註解的方式更簡單易用,更清爽。

spring事務特性
spring所有的事務管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager介面
其中TransactionDefinition介面定義以下特性:

事務隔離級別

  隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 介面中定義了五個表示隔離級別的常量:

TransactionDefinition.ISOLATION_DEFAULT:這是預設值,表示使用底層資料庫的預設隔離級別。對大部分資料庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的資料。該級別不能防止髒讀,不可重複讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上並沒有此級別。
TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的資料。該級別可以防止髒讀,這也是大多數情況下的推薦值。
TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。該級別可以防止髒讀和不可重複讀。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程式的效能。通常情況下也不會用到該級別。
事務傳播行為

      所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括瞭如下幾個表示傳播行為的常量:

TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。這是預設值。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:建立一個新的事務,如果當前存在事務,則把當前事務掛起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,則把當前事務掛起。
TransactionDefinition.PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。
TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。
事務超時

      所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。

  預設設定為底層事務系統的超時值,如果底層資料庫事務系統沒有設定超時值,那麼就是none,沒有超時限制。

事務只讀屬性

      只讀事務用於客戶程式碼只讀但不修改資料的情形,只讀事務用於特定情景下的優化,比如使用Hibernate的時候。
預設為讀寫事務。

        “只讀事務”並不是一個強制選項,它只是一個“暗示”,提示資料庫驅動程式和資料庫系統,這個事務並不包含更改資料的操作,那麼JDBC驅動程式和資料庫就有可能根據這種情況對該事務進行一些特定的優化,比方說不安排相應的資料庫鎖,以減輕事務對資料庫的壓力,畢竟事務也是要消耗資料庫的資源的。 

但是你非要在“只讀事務”裡面修改資料,也並非不可以,只不過對於資料一致性的保護不像“讀寫事務”那樣保險而已。 

因此,“只讀事務”僅僅是一個性能優化的推薦配置而已,並非強制你要這樣做不可



spring事務回滾規則


     指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內丟擲異常。spring事務管理器會捕捉任何未處理的異常,然後依據規則決定是否回滾丟擲異常的事務。

        預設配置下,spring只有在丟擲的異常為執行時unchecked異常時才回滾該事務,也就是丟擲的異常為RuntimeException的子類(Errors也會導致事務回滾),而丟擲checked異常則不會導致事務回滾。可以明確的配置在丟擲那些異常時回滾事務,包括checked異常。也可以明確定義那些異常丟擲時不回滾事務。還可以程式設計性的通過setRollbackOnly()方法來指示一個事務必須回滾,在呼叫完setRollbackOnly()後你所能執行的唯一操作就是回滾。



myBatis為例   基於註解的宣告式事務管理配置@Transactional
spring.xml
[html] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片
<span style="background-color: rgb(255, 255, 255);"><span style="background-color: rgb(255, 204, 153);"><!-- mybatis config -->  
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="dataSource" />  
        <property name="configLocation">  
            <value>classpath:mybatis-config.xml</value>  
        </property>  
    </bean>  
      
    <!-- mybatis mappers, scanned automatically -->  
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
        <property name="basePackage">  
            <value>  
                com.baobao.persistence.test  
            </value>  
        </property>  
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />  
    </bean>  
      
    <!-- 配置spring的PlatformTransactionManager,名字為預設值 -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  
      
    <!-- 開啟事務控制的註解支援 -->  
    <tx:annotation-driven transaction-manager="transactionManager"/></span></span>  
新增tx名字空間
[html] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片
<span style="background-color: rgb(255, 255, 255);"><span style="background-color: rgb(255, 204, 153);">xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"  
  
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"</span></span>  

        MyBatis自動參與到spring事務管理中,無需額外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的資料來源與DataSourceTransactionManager引用的資料來源一致即可,否則事務管理會不起作用。
@Transactional註解

@Transactional屬性 

屬性	型別	描述
value	String	可選的限定描述符,指定使用的事務管理器
propagation	enum: Propagation	可選的事務傳播行為設定
isolation	enum: Isolation	可選的事務隔離級別設定
readOnly	boolean	讀寫或只讀事務,預設讀寫
timeout	int (in seconds granularity)	事務超時時間設定
rollbackFor	Class物件陣列,必須繼承自Throwable	導致事務回滾的異常類陣列
rollbackForClassName	類名陣列,必須繼承自Throwable	導致事務回滾的異常類名字陣列
noRollbackFor	Class物件陣列,必須繼承自Throwable	不會導致事務回滾的異常類陣列
noRollbackForClassName	類名陣列,必須繼承自Throwable	不會導致事務回滾的異常類名字陣列
用法

       @Transactional 可以作用於介面、介面方法、類以及類方法上。當作用於類上時,該類的所有 public 方法將都具有該型別的事務屬性,同時,我們也可以在方法級別使用該標註來覆蓋類級別的定義。

         雖然 @Transactional 註解可以作用於介面、介面方法、類以及類方法上,但是 Spring 建議不要在介面或者介面方法上使用該註解,因為這隻有在使用基於介面的代理時它才會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 註解,這將被忽略,也不會丟擲任何異常。

        預設情況下,只有來自外部的方法呼叫才會被AOP代理捕獲,也就是,類內部方法呼叫本類內部的其他方法並不會引起事務行為,即使被呼叫方法使用@Transactional註解進行修飾。

[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片
@Autowired  
private MyBatisDao dao;  
  
@Transactional  
@Override  
public void insert(Test test) {  
    dao.insert(test);  
    throw new RuntimeException("test");//丟擲unchecked異常,觸發事物,回滾  
}  
noRollbackFor
[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片
@Transactional(noRollbackFor=RuntimeException.class)  
    @Override  
    public void insert(Test test) {  
        dao.insert(test);  
        //丟擲unchecked異常,觸發事物,noRollbackFor=RuntimeException.class,不回滾  
        throw new RuntimeException("test");  
    }  
類,當作用於類上時,該類的所有 public 方法將都具有該型別的事務屬性
[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片
@Transactional  
public class MyBatisServiceImpl implements MyBatisService {  
  
    @Autowired  
    private MyBatisDao dao;  
      
      
    @Override  
    public void insert(Test test) {  
        dao.insert(test);  
        //丟擲unchecked異常,觸發事物,回滾  
        throw new RuntimeException("test");  
    }  
propagation=Propagation.NOT_SUPPORTED
[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片
@Transactional(propagation=Propagation.NOT_SUPPORTED)  
@Override  
public void insert(Test test) {  
    //事物傳播行為是PROPAGATION_NOT_SUPPORTED,以非事務方式執行,不會存入資料庫  
    dao.insert(test);  
}  
myBatis為例   基於註解的宣告式事務管理配置,xml配置
主要為aop切面配置,只看xml就可以了
[html] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片
<!-- 事物切面配置 -->  
<tx:advice id="advice" transaction-manager="transactionManager">  
    <tx:attributes>  
        <tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>  
        <tx:method name="insert" propagation="REQUIRED" read-only="false"/>  
    </tx:attributes>  
</tx:advice>  
  
<aop:config>  
    <aop:pointcut id="testService" expression="execution (* com.baobao.service.MyBatisService.*(..))"/>  
    <aop:advisor advice-ref="advice" pointcut-ref="testService"/>  
</aop:config>