1. 程式人生 > >Spring事務管理(詳解+例項)

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

寫這篇部落格之前我首先讀了《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等都提供了對應的事務管理器,但是具體的實現就是各個平臺自己的事情了。此介面的內容如下:

PublicinterfacePlatformTransactionManager()...{// 由TransactionDefinition得到TransactionStatus物件TransactionStatus getTransaction(TransactionDefinition definition)throwsTransactionException;// 提交Void commit(TransactionStatus status)throwsTransactionException;// 回滾Void rollback(TransactionStatus status)throwsTransactionException;}

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

2.1.1 JDBC事務

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

<beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><propertyname="dataSource"ref="dataSource"/></bean>

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

2.1.2 Hibernate事務

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

<beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><propertyname="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:

<beanid="transactionManager"class="org.springframework.orm.jpa.JpaTransactionManager"><propertyname="sessionFactory"ref="sessionFactory"/></bean>

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

2.1.4 Java原生API事務

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

<beanid="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManager"><propertyname="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介面內容如下:

publicinterfaceTransactionDefinition{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_REQUIREDmethodA(){methodB();}//事務屬性 PROPAGATION_SUPPORTSmethodB(){……}

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

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

//事務屬性 PROPAGATION_REQUIREDmethodA(){methodB();}//事務屬性 PROPAGATION_MANDATORYmethodB(){……}

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

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

//事務屬性 PROPAGATION_REQUIREDmethodA(){doSomeThingA();methodB();doSomeThingB();}//事務屬性 PROPAGATION_REQUIRES_NEWmethodB(){……}

呼叫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_REQUIREDmethodA(){