Spring事務傳播機制
使用AOP 代理後的方法呼叫執行流程,如圖所示
也就是說我們首先呼叫的是AOP代理物件而不是目標物件,首先執行事務切面,事務切面內部通過TransactionInterceptor環繞增強進行事務的增強,即進入目標方法之前開啟事務,退出目標方法時提交/回滾事務。
當我們呼叫一個基於Spring的Service介面方法(如UserService#addUser())時,它將運行於Spring管理的事務 環境中,Service介面方法可能會在內部呼叫其它的Service介面方法以共同完成一個完整的業務操作,因此就會產生服務介面方法巢狀呼叫的情況, Spring通過事務傳播行為控制當前的事務如何傳播到被巢狀呼叫的目標服務介面方法中。
事務傳播是Spring進行事務管理的重要概念,其重要性怎麼強調都不為過。但是事務傳播行為也是被誤解最多的地方,在本文裡,我們將詳細分析不同事務傳播行為的表現形式,掌握它們之間的區別。
事務傳播行為種類
Spring在TransactionDefinition介面中規定了7種類型的事務傳播行為,它們規定了事務方法和事務方法發生巢狀呼叫時事務如何進行傳播:
表1事務傳播行為型別
事務傳播行為型別 |
說明 |
PROPAGATION_REQUIRED |
如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。 |
PROPAGATION_SUPPORTS |
支援當前事務,如果當前沒有事務,就以非事務方式執行。 |
PROPAGATION_MANDATORY |
使用當前的事務,如果當前沒有事務,就丟擲異常。 |
PROPAGATION_REQUIRES_NEW |
新建事務,如果當前存在事務,把當前事務掛起。 |
PROPAGATION_NOT_SUPPORTED |
以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 |
PROPAGATION_NEVER |
以非事務方式執行,如果當前存在事務,則丟擲異常。 |
PROPAGATION_NESTED |
如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。 |
當使用PROPAGATION_NESTED時,底層的資料來源必須基於JDBC 3.0,並且實現者需要支援儲存點事務機制。
幾種容易引起誤解的組合事務傳播行為 ,當服務介面方法分別使用表1中不同的事務傳播行為,且這些介面方法又發生相互呼叫的情況下,大部分組合都是一目瞭然,容易理解的。但是,也存在一些容易引起誤解的組合事務傳播方式。
下面,我們通過兩個具體的服務介面的組合呼叫行為來破解這一難點。這兩個服務介面分別是UserService和ForumService, UserSerice有一個addCredits()方法,ForumSerivce#addTopic()方法呼叫了 UserSerice#addCredits()方法,發生關聯性服務方法的呼叫:
public class ForumService {
private UserService userService;
public void addTopic(){①呼叫其它服務介面的方法
//add Topic…
userService.addCredits();②被關聯呼叫的業務方法
}
(巢狀呼叫的事務方法)
對Spring事務傳播行為最常見的一個誤解是:當服務介面方法發生巢狀呼叫時,被呼叫的服務方法只能宣告為 PROPAGATION_NESTED。這種觀點犯了望文生義的錯誤,誤認為PROPAGATION_NESTED是專為方法巢狀準備的。這種誤解遺害不 淺,執有這種誤解的開發者錯誤地認為:應儘量不讓Service類的業務方法發生相互的呼叫,Service類只能呼叫DAO層的DAO類,以避免產生嵌 套事務。
其實,這種顧慮是完全沒有必要的,PROPAGATION_REQUIRED已經清楚地告訴我們:事務的方法會足夠“聰明”地判斷上下文是否已經存在一個事務中,如果已經存在,就加入到這個事務中,否則建立一個新的事務。
依照上面的例子,假設我們將ForumService#addTopic()和UserSerice#addCredits()方法的事務傳播行為都設定為PROPAGATION_REQUIRED,這兩個方法將運行於同一個事務中。
為了清楚地說明這點,可以將Log4J的日誌設定為DEBUG級別,以觀察Spring事務管理器內部的執行情況。下面將兩個業務方法都設定為PROPAGATION_REQUIRED,Spring所輸出的日誌資訊如下:
Using transaction object
[org.springframework.jdbc.data[email protected]e3849c]
①為ForumService#addTopic()新建一個事務
Creating new transaction with name [com.baobaotao.service.ForumService.addTopic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [[email protected]] for JDBC transaction
Switching JDBC Connection [[email protected]] to manual commit
Bound value [[email protected]] for key [[email protected]] to thread [main]
Initializing transaction synchronization
Getting transaction for [com.baobaotao.service.ForumService.addTopic]
Retrieved value [[email protected]] for key [[email protected]] bound to thread [main]
Using transaction object [org.springframework.jdbc.data[email protected]8b8a47]
②UserService#addCredits()簡單地加入到已存在的事務中(即①處建立的事務)
Participating in existing transaction
Getting transaction for [com.baobaotao.service.UserService.addCredits]
Completing transaction for [com.baobaotao.service.UserService.addCredits]
Completing transaction for [com.baobaotao.service.ForumService.addTopic]
Triggering beforeCommit synchronization
Triggering beforeCompletion synchronization
Initiating transaction commit
③呼叫底層Connection#commit()方法提交事務
Committing JDBC transaction on Connection [[email protected]]
Triggering afterCommit synchronization
Triggering afterCompletion synchronization
Clearing transaction synchronization
1、巢狀事務
將ForumService#addTopic()設定為PROPAGATION_REQUIRED時, UserSerice#addCredits()設定為PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、 PROPAGATION_MANDATORY時,執行的效果都是一致的(當然,如果單獨呼叫addCredits()就另當別論了)。
當addTopic()執行在一個事務下(如設定為PROPAGATION_REQUIRED),而addCredits()設定為 PROPAGATION_NESTED時,如果底層資料來源支援儲存點,Spring將為內部的addCredits()方法產生的一個內嵌的事務。如果 addCredits()對應的內嵌事務執行失敗,事務將回滾到addCredits()方法執行前的點,並不會將整個事務回滾。內嵌事務是內層事務的一 部分,所以只有外層事務提交時,巢狀事務才能一併提交。
巢狀事務不能夠提交,它必須通過外層事務來完成提交的動作,外層事務的回滾也會造成內部事務的回滾。
2、巢狀事務和新事務
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED也是容易混淆的兩個傳播行為。PROPAGATION_REQUIRES_NEW 啟動一個新的、和外層事務無關的“內部”事務。該事務擁有自己的獨立隔離級別和鎖,不依賴於外部事務,獨立地提交和回滾。當內部事務開始執行時,外部事務 將被掛起,內務事務結束時,外部事務才繼續執行。
由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於:PROPAGATION_REQUIRES_NEW 將建立一個全新的事務,它和外層事務沒有任何關係,而 PROPAGATION_NESTED 將建立一個依賴於外層事務的子事務,當外層事務提交或回滾時,子事務也會連帶提交和回滾。
其它需要注意問題
以下幾個問題值得注意:
1) 當業務方法被設定為PROPAGATION_MANDATORY時,它就不能被非事務的業務方法呼叫。如將ForumService#addTopic ()設定為PROPAGATION_MANDATORY,如果展現層的Action直接呼叫addTopic()方法,將引發一個異常。正確的情況是: addTopic()方法必須被另一個帶事務的業務方法呼叫(如ForumService#otherMethod())。所以 PROPAGATION_MANDATORY的方法一般都是被其它業務方法間接呼叫的。
2) 當業務方法被設定為PROPAGATION_NEVER時,它將不能被擁有事務的其它業務方法呼叫。假設UserService#addCredits ()設定為PROPAGATION_NEVER,當ForumService# addTopic()擁有一個事務時,addCredits()方法將丟擲異常。所以PROPAGATION_NEVER方法一般是被直接呼叫的。
3)當方法被設定為PROPAGATION_NOT_SUPPORTED時,外層業務方法的事務會被掛起,當內部方法執行完成後,外層方法的事務重新執行。如果外層方法沒有事務,直接執行,不需要做任何其它的事。
小結
在Spring宣告式事務管理的配置中,事務傳播行為是最容易被誤解的配置項,原因在於事務傳播行為名稱(如 PROPAGATION_NESTED:巢狀式事務)和程式碼結構的類似性上(業務類方法巢狀呼叫另一個業務類方法)。這種誤解在很多Spring開發者中 廣泛存在,本文深入講解了Spring事務傳播行為對業務方法巢狀呼叫的真實影響,希望能幫助讀者化解對事務傳播行為的困惑。