Spring 事務機制詳解


原文出處: 陶邦仁

Spring事務機制主要包括聲明式事務和編程式事務,此處側重講解聲明式事務,編程式事務在實際開發中得不到廣泛使用,僅供學習參考。

Spring聲明式事務讓我們從復雜的事務處理中得到解脫。使得我們再也無需要去處理獲得連接、關閉連接、事務提交和回滾等這些操作。再也無需要我們在與事務相關的方法中處理大量的try…catch…finally代碼。我們在使用Spring聲明式事務時,有一個非常重要的概念就是事務屬性。事務屬性通常由事務的傳播行為,事務的隔離級別,事務的超時值和事務只讀標志組成。我們在進行事務划分時,需要進行事務定義,也就是配置事務的屬性。

下面分別詳細講解,事務的四種屬性,僅供諸位學習參考:

Spring在TransactionDefinition接口中定義這些屬性,以供PlatfromTransactionManager使用, PlatfromTransactionManager是spring事務管理的核心接口。

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

1. TransactionDefinition接口中定義五個隔離級別:

ISOLATION_DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別.另外四個與JDBC的隔離級別相對應;

ISOLATION_READ_UNCOMMITTED 這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生臟讀,不可重復讀和幻像讀。

ISOLATION_READ_COMMITTED  保證一個事務修改的數據提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據。這種事務隔離級別可以避免臟讀出現,但是可能會出現不可重復讀和幻像讀。

ISOLATION_REPEATABLE_READ  這種事務隔離級別可以防止臟讀,不可重復讀。但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下面的情況產生(不可重復讀)。

ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。除了防止臟讀,不可重復讀外,還避免了幻像讀。

1: Dirty reads(臟讀)。也就是說,比如事務A的未提交(還依然緩存)的數據被事務B讀走,如果事務A失敗回滾,會導致事務B所讀取的的數據是錯誤的。
2: non-repeatable reads(數據不可重復讀)。比如事務A中兩處讀取數據-total-的值。在第一讀的時候,total是100,然后事務B就把total的數據改成200,事務A再讀一次,結果就發現,total竟然就變成200了,造成事務A數據混亂。
3: phantom reads(幻象讀數據),這個和non-repeatable reads相似,也是同一個事務中多次讀不一致的問題。但是non-repeatable reads的不一致是因為他所要取的數據集被改變了(比如total的數據),但是phantom reads所要讀的數據的不一致卻不是他所要讀的數據集改變,而是他的條件數據集改變。比如Select account.id where account.name=”ppgogo*”,第一次讀去了6個符合條件的id,第二次讀取的時候,由於事務b把一個帳號的名字由”dd”改成”ppgogo1″,結果取出來了7個數據。

2. 在TransactionDefinition接口中定義了七個事務傳播行為:

(1)PROPAGATION_REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啟一個新的事務。

Java代碼:

1
2
3
4
5
6
7
8
9
10
11
//事務屬性 PROPAGATION_REQUIRED
methodA{
……
methodB();
……
}
 
//事務屬性 PROPAGATION_REQUIRED
methodB{
    ……
}

使用spring聲明式事務,spring使用AOP來支持聲明式事務,會根據事務屬性,自動在方法調用之前決定是否開啟一個事務,並在方法執行之后決定事務提交或回滾事務。

單獨調用methodB方法:

Java代碼

1
2
3
4
5
main{
 
metodB();
 
}

相當於

Java代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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.

執行效果相當於:

Java代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
          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與不使用事務有少許不同。

Java代碼:

1
2
3
4
5
6
7
8
9
//事務屬性 PROPAGATION_REQUIRED
methodA(){
   methodB();
}
 
//事務屬性 PROPAGATION_SUPPORTS
methodB(){
   ……
}

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

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

Java代碼:

1
2
3
4
5
6
7
8
9
//事務屬性 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 總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。

Java代碼:

1
2
3
4
5
6
7
8
9
10
11
//事務屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
methodB();
doSomeThingB();
}
 
//事務屬性 PROPAGATION_REQUIRES_NEW
methodB(){
    ……
}

Java代碼:

1
2
3
main(){
   methodA();
}

相當於

Java代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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;

Java代碼:

1
2
3
4
5
6
7
8
9
10
11
//事務屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}
 
//事務屬性 PROPAGATION_NESTED
methodB(){
   ……
}

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

Java代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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應該是我們首先的事務傳播行為。它能夠滿足我們大多數的事務需求。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM