Spring事務——事務傳播屬性的七種行為
事務傳播行為
什麼叫事務傳播行為?聽起來挺高階的,其實很簡單。
即然是傳播,那麼至少有兩個東西,才可以發生傳播。單體不存在傳播這個行為。
事務傳播行為(propagation behavior)指的就是當一個事務方法被另一個事務方法呼叫時,這個事務方法應該如何進行。
例如:methodA事務方法呼叫methodB事務方法時,methodB是繼續在呼叫者methodA的事務中執行呢,還是為自己開啟一個新事務執行,這就是由methodB的事務傳播行為決定的。
Spring定義了七種傳播行為:
現在具體來看看傳播行為
1、PROPAGATION_REQUIRED
如果存在一個事務,則支援當前事務。如果沒有事務則開啟一個新的事務。
可以把事務想像成一個膠囊,在這個場景下方法B用的是方法A產生的膠囊(事務)。
舉例有兩個方法:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// do something
}
單獨呼叫methodB方法時,因為當前上下文不存在事務,所以會開啟一個新的事務。
呼叫methodA方法時,因為當前上下文不存在事務,所以會開啟一個新的事務。當執行到methodB時,methodB發現當前上下文有事務,因此就加入到當前事務中來。
2、PROPAGATION_SUPPORTS
如果存在一個事務,支援當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。
舉例有兩個方法:
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); // do something } // 事務屬性為SUPPORTS @Transactional(propagation = Propagation.SUPPORTS) public void methodB() { // do something }
單純的呼叫methodB時,methodB方法是非事務的執行的。當呼叫methdA時,methodB則加入了methodA的事務中,事務地執行。
3、PROPAGATION_MANDATORY
如果已經存在一個事務,支援當前事務。如果沒有一個活動的事務,則丟擲異常。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事務屬性為MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// do something
}
當單獨呼叫methodB時,因為當前沒有一個活動的事務,則會丟擲異常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);當呼叫methodA時,methodB則加入到methodA的事務中,事務地執行。
4、PROPAGATION_REQUIRES_NEW
使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager
作為事務管理器。
它會開啟一個新的事務。如果一個事務已經存在,則先將這個存在的事務掛起。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
// do something else
}
// 事務屬性為REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// do something
}
當呼叫
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。(與另一個傳播行為PROPAGATION_NESTED的重要區別)
如果methodA方法在呼叫methodB方法後的doSomeThingB方法失敗了,而methodB方法所做的結果依然被提交。而除了 methodB之外的其它程式碼導致的結果卻被回滾了
5、PROPAGATION_NOT_SUPPORTED
PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager
作為事務管理器。
6、PROPAGATION_NEVER
總是非事務地執行,如果存在一個活動事務,則丟擲異常。
7、PROPAGATION_NESTED
如果一個活動的事務存在,則執行在一個巢狀的事務中。 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行。
這是一個巢狀事務,使用JDBC 3.0驅動時,僅僅支援DataSourceTransactionManager
作為事務管理器。
需要JDBC 驅動的java.sql.Savepoint類。使用PROPAGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設為true
(屬性值預設為false)。
這裡關鍵是巢狀執行。
@Transactional(propagation = Propagation.REQUIRED)
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
@Transactional(propagation = Propagation.NEWSTED)
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版本支援。其它的JTATrasactionManager實現可能有不同的支援方式。
PROPAGATION_REQUIRES_NEW 啟動一個新的, 不依賴於環境的 “內部” 事務. 這個事務將被完全 commited 或 rolled back 而不依賴於外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行。
另一方面, PROPAGATION_NESTED 開始一個 “巢狀的” 事務, 它是已經存在事務的一個真正的子事務. 潛套事務開始執行時, 它將取得一個 savepoint. 如果這個巢狀事務失敗, 我們將回滾到此 savepoint. 潛套事務是外部事務的一部分, 只有外部事務結束後它才會被提交。
由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於, PROPAGATION_REQUIRES_NEW 完全是一個新的事務, 而 PROPAGATION_NESTED 則是外部事務的子事務,
如果外部事務 commit, 巢狀事務也會被 commit, 這個規則同樣適用於 roll back.