Spring 事務的傳播行為
highlight: atom-one-dark theme: vuepress
這是我參與11月更文挑戰的第4天,活動詳情檢視:2021最後一次更文挑戰。
傳播行為
事務的第一個方面是傳播行為(propagation behavior)。當事務方法被另一個事務方法呼叫時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中執行,也可能開啟一個新事務,並在自己的事務中執行。Spring定義了七種傳播行為:
| 傳播行為 | 含義 | | --- | --- | | PROPAGATION_REQUIRED | 當前方法必須執行在事務中,如果當前存在事務,則加入該事務;否則建立一個新的事務。 | | PROPAGATION_SUPPORTS | 不需要事務上下文,如果當前存在事務,則加入該事務;否則以非事務的方式繼續執行。 | | PROPAGATION_MANDATORY | 當前方法必須執行在事務中, 如果當前存在事務,則加入該事務;否則丟擲異常。(mandatory:強制性) | | PROPAGATION_REQUIRES_NEW | 表示當前方法必須執行在它自己的事務中。一個新的事務將被啟動。如果存在當前事務,在該方法執行期間,當前事務會被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager | | PROPAGATION_NOT_SUPPORTED | 表示該方法不應該執行在事務中。如果存在當前事務,在該方法執行期間,當前事務將被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager | | PROPAGATION_NEVER | 表示當前方法不應該執行在事務上下文中。如果當前正有一個事務在執行,則會丟擲異常 | | PROPAGATION_NESTED | 表示如果當前已經存在一個事務,那麼該方法將會在巢狀事務中執行。巢狀的事務可以獨立於當前事務進行單獨地提交或回滾。如果當前事務不存在,那麼其行為與PROPAGATION_REQUIRED一樣。注意各廠商對這種傳播行為的支援是有所差異的。 |
詳解
PROPAGATION_REQUIRED
(1)PROPAGATION_REQUIRED 如果存在一個事務,則支援當前事務。如果沒有事務則開啟一個新的事務。 ```java //事務屬性 PROPAGATION_REQUIRED methodA{ …… methodB(); …… }
//事務屬性 PROPAGATION_REQUIRED
methodB{
……
}
使用spring宣告式事務,spring使用AOP來支援宣告式事務,會根據事務屬性,自動在方法呼叫之前決定是否開啟一個事務,並在方法執行之後決定事務提交或回滾事務。
單獨呼叫methodB方法:
java
main{
methodB();
}
相當於:
java
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
main{
Connection con = null;
try{
con = getConnection();
methodA();
con.commit();
} catch(RuntimeException ex) {
con.rollback();
} finally {
closeCon();
}
}
```
呼叫MethodA時,環境中沒有事務,所以開啟一個新的事務。當在MethodA中呼叫MethodB時,環境中已經有了一個事務,所以methodB就加入當前事務。
PROPAGATION_SUPPORTS
(2)PROPAGATION_SUPPORTS 如果存在一個事務,支援當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。 ```java //事務屬性 PROPAGATION_REQUIRED methodA(){ methodB(); }
//事務屬性 PROPAGATION_SUPPORTS methodB(){ …… }
``` 單純的呼叫methodB時,methodB方法是非事務的執行的。當呼叫methdA時,methodB則加入了methodA的事務中,事務地執行。
PROPAGATION_MANDATORY
(3)PROPAGATION_MANDATORY 如果已經存在一個事務,支援當前事務。如果沒有一個活動的事務,則丟擲異常。 ```java //事務屬性 PROPAGATION_REQUIRED methodA(){ methodB(); }
//事務屬性 PROPAGATION_MANDATORY methodB(){ …… } ``` 當單獨呼叫methodB時,因為當前沒有一個活動的事務,則會丟擲異常
throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
當呼叫methodA時,methodB則加入到methodA的事務中,事務地執行。
PROPAGATION_REQUIRES_NEW
(4)PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。 ```java //事務屬性 PROPAGATION_REQUIRED methodA(){ doSomeThingA(); methodB(); doSomeThingB(); }
//事務屬性 PROPAGATION_REQUIRES_NEW
methodB(){
……
}
呼叫A方法:
java
main(){
methodA();
}
相當於
java
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作為事務管理器。
PROPAGATION_NOT_SUPPORTED
(5)PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作為事務管理器。(程式碼示例同上,可同理推出)
PROPAGATION_NEVER
(6)PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則丟擲異常。
PROPAGATION_NESTED
(7)PROPAGATION_NESTED如果一個活動的事務存在,則執行在一個巢狀的事務中. 如果沒有活動事務,則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行。這是一個巢狀事務,使用JDBC 3.0驅動時,僅僅支援DataSourceTransactionManager作為事務管理器。需要JDBC 驅動的java.sql.Savepoint類。有一些JTA的事務管理器實現可能也提供了同樣的功能。使用PROPAGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設為true;而 nestedTransactionAllowed屬性值預設為false。 ```java //事務屬性 PROPAGATION_REQUIRED methodA(){ doSomeThingA(); methodB(); doSomeThingB(); }
//事務屬性 PROPAGATION_NESTED
methodB(){
……
}
如果單獨呼叫methodB方法,則按REQUIRED屬性執行。如果呼叫methodA方法,相當於下面的效果:
java
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時,外層事務的回滾可以引起內層事務的回滾。而內層事務的異常並不會導致外層事務的回滾,它是一個真正的巢狀事務。
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應該是我們首先的事務傳播行為。它能夠滿足我們大多數的事務需求。