深入淺出事務之傳播屬性

本文參考《java Transaction design strategies》

大部分時候,我們都習慣了spring容器默認的配置,但有時候,我們需要知道更多……

當使用聲明式事務模型時,您必須告訴容器如何去管理事務,例如,何時開啓一個事務?哪些方法需要事務?當前不存在事務的情況下,容器是否需要爲其添加事務控制?事實上,Spring提供了一個bean ——TransactionAttributSource,通過配置其事務(傳播)屬性(transactionattribute)來達到精確控制事務行爲的目的。事務的屬性總共有六種:

    Required
    Mandato
    RequiresNew
    Supports
    NotSupported
    Never 


Spring又添加了一種新的事務屬性,PROPAGATION_NESTED,用於實現真正的嵌套事務,前提條件是外部環境必須提供相應的實現支持。儘管可以通過配置的方式在bean(類)級別指定事務屬性,一般來說,還是應該將事務屬性應用於方法級別。爲整個bean配置某個事務屬性意味着其內部所有的方法都採用它,而方法級別的事務屬性可以將其覆蓋。

REQUIRED
Required屬性告訴容器某個特定的方法需要一個事務,如果上下文中已經存在事務,則加入;否則,開啓一個事務。這是一種使用最頻繁的事務屬性,適用於大多數情況;但是,並不絕對,下面我們將會看到對於某些場景,總會有更好的理由去使用Mandatory屬性。

MANDATORY
Mandatory屬性告訴容器某個特定的方法需要一個事務。但是,不同於Required屬性,它無論如何都不會開啓新的事務;相反的,它會“強制”要求該方法被調用時上下文中必須存在事務,否則會拋出TransactionRequiredException異常,提示需要一個事務但沒有找到。何時選擇Mandatory,後面將會專門抽出一節來分析。

REQUIRESNEW
RequiresNew屬性告訴容器某個特定的方法需要一個新事務的支持。如果上下文中已經存在事務A,則該事務A掛起,並啓動一個新的事務B。當事務B結束後,事務A被喚醒並繼續執行。事實上,使用RequiresNew違反了事務的ACID原則,因爲新事務會導致原有事務的掛起。
該屬性在某個行爲必須被完成(提交或回滾)而不受外部事務結果影響時十分有用,例如記錄日誌。大多數交易系統的每一個操作都必須寫進日誌,無論其執行結果如何(成功或失敗)。假設某個股票交易的方法placement()啓動了一個事務,並且調用了一個通用的方法audit()來記錄日誌。由於audit()和placement()處於同一個事務的管轄範圍之內,因此一旦placement()回滾,audit()記錄的日誌也會相應的進行回滾;這就違背了“任何成功或失敗的操作都必須記錄日誌”這一業務邏輯。這個時候,如果將audit()的事務屬性設作RequiresNew,則確保了audit()在一個新的、單獨的事務中記錄日誌,因此不受placement()中外部事務的影響。

SUPPORTS
Supports屬性告訴容器,該方法不需要事務支持,但如果當前上下文中已經存在了一個事務,則加入其中。Supports這是一個相當強大、相當有用的事務屬性。考慮這樣一個場景,業務方法A用來查詢某個交易者特定時期的交易總量。由於是查詢操作,因此這個時候事務並不是必須的,因此我們將其事務屬性設爲Supports,來告訴容器在調用方法時不要開啓新的事務。但是,如果方法A被某個已經存在事務控制的方法B所調用,那麼它就會加入當前事務;那麼,在該事務提交之前,方法B中對數據的任何修改對於方法A來說,都是可見的。
下面我們舉個更具體的例子來說明Supports的作用。假設某位交易者一天的最大交易額是一百萬,如果採用Supports作爲事務屬性,一次超額交易的具體處理流程如下:

    目前爲止,當天總共的交易量是900,000
    事務啓動
    交易者又進行了一筆200,000的交易
    調用事務屬性爲Supports的查詢方法,因爲是同一個事務,因此得到結果爲1,100,000
    業務邏輯判斷,已經超過最大交易限額,拋出異常,事務回滾 



如果使用NotSupported會發生什麼呢?我們不會得到任何異常,因爲查詢方法不會加入當前事務,因此它看不見當前事務中對數據庫的任何修改。

    目前爲止,當天總共的交易量是900,000
    事務啓動
    交易者又進行了一筆200,000的交易
    調用事務屬性爲SNotupported的查詢方法,因爲不屬於當前事務,因此得到結果爲900,000
    業務邏輯判斷,沒有超過最大交易限額,事務提交(實際已超過當天限額) 



NotSupported
NotSupported屬性告訴容器,該方法不需要事務支持;如果當前上下文中已經存在事務,則該事務被掛起直到該方法執行完畢。如果當前上下文中不存在事務,該方法則在沒有事務支持的環境下執行。NotSupported適用於“某些方法在事務控制下有較大可能性會產生異常”的場合。例如在XA架構的事務處理過程中,調用包含DDL語句的存儲過程往往會拋出異常,因此比較好的做法是將其設爲NotSupported,暫時掛起當前事務。

Never
Never屬性告訴容器,該方法必須在無事務的上下文中運行。注意,這與NotSupported不同,後者意味着,如果上下文中存在事務,則將該事務暫時掛起並在無事務的上下文中運行。但Never卻不同,如果當前上下文中已經存在事務,則在調用該方法時會拋出一個異常,提示該方法不能在事務環境下運行。因爲使用Never會導致各種意料之外的運行時異常,因此除非必要,一般不推薦使用

Required vs. Mandatory
Required和Mandatory是兩個往往容易被混淆的事務屬性。兩者都可以運行在事務上下文環境中,但當上下文中不存在事務時,Required會自己開啓一個事務,而Mandatory則不會。大多數情況下,你可以採用如下的“最佳實踐”來區分何時需要哪種策略:如果某一方法需要運行在具備事務的環境,但其本身又不負責事務的控制(回滾),則該方法應該標誌爲Mandatory。可以看出,上述最佳實踐是基於事務的所有權而得來的。

Nested vs. RequiresNew vs. Required
另外兩個比較容易的事務屬性是Spring提供的Nested與RequiresNew。如果當前上下文中存在事務,兩者都是啓動一個新的事務;如果當前上下文中不存在事務,則類似於PROPAGATION_REQUIRED,兩者也都新建一個事務。那麼,它們有什麼區別呢?
PROPAGATION_REQUIRES_NEW 啓動一個新的, 不依賴於環境的 "內部" 事務. 這個事務將被完全 commited 或rolled back 而不依賴於外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等。當內部事務開始執行時, 外部事務將被掛起,內務事務結束時, 外部事務將繼續執行。

另一方面, PROPAGATION_NESTED 開始一個 "嵌套的" 事務, 它是已經存在事務的一個真正的子事務。嵌套事務開始執行時,  它將取得一個 savepoint。如果這個嵌套事務失敗, 我們將回滾到此savepoint。嵌套事務是外部事務的一部分, 只有外部事務結束後它纔會被提交。
總結一下,使用嵌套事務的場景總共有兩個,如下圖所示:

[點擊查看原始大小圖片]


    需要事務BC與事務AD一起提交,即:作爲事務AD的子事務,事務BC只有在事務AD成功提交時(階段3成功)才提交。這個需求簡單稱之爲“聯合提交”。這一點PROPAGATION_REQUIRED可以做到。
    需要事務BC的回滾不會影響事務AD的提交。這個需求簡單稱之爲“隔離回滾”。這一點,可以通過設置事務屬性爲PROPAGATION_REQUIRES_NEW做到。 



使用PROPAGATION_REQUIRED滿足需求1,但子事務BC的rollback會迫使父事務AD也回滾,不能滿足需求2。使用PROPAGATION_REQUIRES_NEW滿足需求2,但子事務(嚴格意義上說,這時不應該稱之爲子事務)BC是一個全新的事務,父事務(嚴格意義上說,這時也不應該稱之爲父事務)AD的成功與否完全不影響BC的提交,不能滿足需求1。

同時滿足上述兩條需求就要用到PROPAGATION_NESTED了。PROPAGATION_NESTED在事務AD執行到B點時,設置了savePoint。當BC事務成功提交時,PROPAGATION_NESTED的行爲與PROPAGATION_REQUIRED一樣。只有當事務AD在D點成功提交時,事務BC才真正提交; 如果階段3執行異常,導致事務AD 回滾,事務BC也將一起回滾,從而滿足了“聯合提交”。當階段2執行異常,導致BC事務rollback時,因爲設置了savePoint的緣故,AD事務可以選擇與BC一起rollback或繼續階段3的執行並保留階段1的執行結果,從而滿足了“隔離回滾”。例如,可以把執行BC事務的方法try-catch起來,在catch中選擇是否繼續向上拋出異常(是,則回滾到A處;否,則回滾到B處)。而是用PROPAGATION_REQUIRED時,即使try-catch住BC的異常,AD事務也一定會被無條件rollback。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章