Spring 事務的傳播行為

語言: CN / TW / HK

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應該是我們首先的事務傳播行為。它能夠滿足我們大多數的事務需求。

參考---Spring 事務機制詳解