本文參考《java Transaction design strategies》
大部分時(shí)候,我們都習慣了spring容器默認的配置,但有時(shí)候,我們需要知道更多……
當使用聲明式事務(wù)模型時(shí),您必須告訴容器如何去管理事務(wù),例如,何時(shí)開(kāi)啟一個(gè)事務(wù)?哪些方法需要事務(wù)?當前不存在事務(wù)的情況下,容器是否需要為其添加事務(wù)控制?事實(shí)上,Spring提供了一個(gè)bean ——TransactionAttributSource,通過(guò)配置其事務(wù)(傳播)屬性(transactionattribute)來(lái)達到精確控制事務(wù)行為的目的。事務(wù)的屬性總共有六種:
- Required
- Mandato
- RequiresNew
- Supports
- NotSupported
- Never
Spring又添加了一種新的事務(wù)屬性,PROPAGATION_NESTED,用于實(shí)現真正的嵌套事務(wù),前提條件是外部環(huán)境必須提供相應的實(shí)現支持。盡管可以通過(guò)配置的方式在bean(類(lèi))級別指定事務(wù)屬性,一般來(lái)說(shuō),還是應該將事務(wù)屬性應用于方法級別。為整個(gè)bean配置某個(gè)事務(wù)屬性意味著(zhù)其內部所有的方法都采用它,而方法級別的事務(wù)屬性可以將其覆蓋。
REQUIREDRequired屬性告訴容器某個(gè)特定的方法需要一個(gè)事務(wù),如果上下文中已經(jīng)存在事務(wù),則加入;否則,開(kāi)啟一個(gè)事務(wù)。這是一種使用最頻繁的事務(wù)屬性,適用于大多數情況;但是,并不絕對,下面我們將會(huì )看到對于某些場(chǎng)景,總會(huì )有更好的理由去使用Mandatory屬性。
MANDATORYMandatory屬性告訴容器某個(gè)特定的方法需要一個(gè)事務(wù)。但是,不同于Required屬性,它無(wú)論如何都不會(huì )開(kāi)啟新的事務(wù);相反的,它會(huì )“強制”要求該方法被調用時(shí)上下文中必須存在事務(wù),否則會(huì )拋出TransactionRequiredException異常,提示需要一個(gè)事務(wù)但沒(méi)有找到。何時(shí)選擇Mandatory,后面將會(huì )專(zhuān)門(mén)抽出一節來(lái)分析。
REQUIRESNEWRequiresNew屬性告訴容器某個(gè)特定的方法需要一個(gè)新事務(wù)的支持。如果上下文中已經(jīng)存在事務(wù)A,則該事務(wù)A掛起,并啟動(dòng)一個(gè)新的事務(wù)B。當事務(wù)B結束后,事務(wù)A被喚醒并繼續執行。事實(shí)上,使用RequiresNew違反了事務(wù)的ACID原則,因為新事務(wù)會(huì )導致原有事務(wù)的掛起。
該屬性在某個(gè)行為必須被完成(提交或回滾)而不受外部事務(wù)結果影響時(shí)十分有用,例如記錄日志。大多數交易系統的每一個(gè)操作都必須寫(xiě)進(jìn)日志,無(wú)論其執行結果如何(成功或失?。?。假設某個(gè)股票交易的方法placement()啟動(dòng)了一個(gè)事務(wù),并且調用了一個(gè)通用的方法audit()來(lái)記錄日志。由于audit()和placement()處于同一個(gè)事務(wù)的管轄范圍之內,因此一旦placement()回滾,audit()記錄的日志也會(huì )相應的進(jìn)行回滾;這就違背了“任何成功或失敗的操作都必須記錄日志”這一業(yè)務(wù)邏輯。這個(gè)時(shí)候,如果將audit()的事務(wù)屬性設作RequiresNew,則確保了audit()在一個(gè)新的、單獨的事務(wù)中記錄日志,因此不受placement()中外部事務(wù)的影響。
SUPPORTSSupports屬性告訴容器,該方法不需要事務(wù)支持,但如果當前上下文中已經(jīng)存在了一個(gè)事務(wù),則加入其中。Supports這是一個(gè)相當強大、相當有用的事務(wù)屬性??紤]這樣一個(gè)場(chǎng)景,業(yè)務(wù)方法A用來(lái)查詢(xún)某個(gè)交易者特定時(shí)期的交易總量。由于是查詢(xún)操作,因此這個(gè)時(shí)候事務(wù)并不是必須的,因此我們將其事務(wù)屬性設為Supports,來(lái)告訴容器在調用方法時(shí)不要開(kāi)啟新的事務(wù)。但是,如果方法A被某個(gè)已經(jīng)存在事務(wù)控制的方法B所調用,那么它就會(huì )加入當前事務(wù);那么,在該事務(wù)提交之前,方法B中對數據的任何修改對于方法A來(lái)說(shuō),都是可見(jiàn)的。
下面我們舉個(gè)更具體的例子來(lái)說(shuō)明Supports的作用。假設某位交易者一天的最大交易額是一百萬(wàn),如果采用Supports作為事務(wù)屬性,一次超額交易的具體處理流程如下:
- 目前為止,當天總共的交易量是900,000
- 事務(wù)啟動(dòng)
- 交易者又進(jìn)行了一筆200,000的交易
- 調用事務(wù)屬性為Supports的查詢(xún)方法,因為是同一個(gè)事務(wù),因此得到結果為1,100,000
- 業(yè)務(wù)邏輯判斷,已經(jīng)超過(guò)最大交易限額,拋出異常,事務(wù)回滾
如果使用NotSupported會(huì )發(fā)生什么呢?我們不會(huì )得到任何異常,因為查詢(xún)方法不會(huì )加入當前事務(wù),因此它看不見(jiàn)當前事務(wù)中對數據庫的任何修改。
- 目前為止,當天總共的交易量是900,000
- 事務(wù)啟動(dòng)
- 交易者又進(jìn)行了一筆200,000的交易
- 調用事務(wù)屬性為SNotupported的查詢(xún)方法,因為不屬于當前事務(wù),因此得到結果為900,000
- 業(yè)務(wù)邏輯判斷,沒(méi)有超過(guò)最大交易限額,事務(wù)提交(實(shí)際已超過(guò)當天限額)
NotSupportedNotSupported屬性告訴容器,該方法不需要事務(wù)支持;如果當前上下文中已經(jīng)存在事務(wù),則該事務(wù)被掛起直到該方法執行完畢。如果當前上下文中不存在事務(wù),該方法則在沒(méi)有事務(wù)支持的環(huán)境下執行。NotSupported適用于“某些方法在事務(wù)控制下有較大可能性會(huì )產(chǎn)生異常”的場(chǎng)合。例如在XA架構的事務(wù)處理過(guò)程中,調用包含DDL語(yǔ)句的存儲過(guò)程往往會(huì )拋出異常,因此比較好的做法是將其設為NotSupported,暫時(shí)掛起當前事務(wù)。
NeverNever屬性告訴容器,該方法必須在無(wú)事務(wù)的上下文中運行。注意,這與NotSupported不同,后者意味著(zhù),如果上下文中存在事務(wù),則將該事務(wù)暫時(shí)掛起并在無(wú)事務(wù)的上下文中運行。但Never卻不同,如果當前上下文中已經(jīng)存在事務(wù),則在調用該方法時(shí)會(huì )拋出一個(gè)異常,提示該方法不能在事務(wù)環(huán)境下運行。因為使用Never會(huì )導致各種意料之外的運行時(shí)異常,因此除非必要,一般不推薦使用
Required vs. MandatoryRequired和Mandatory是兩個(gè)往往容易被混淆的事務(wù)屬性。兩者都可以運行在事務(wù)上下文環(huán)境中,但當上下文中不存在事務(wù)時(shí),Required會(huì )自己開(kāi)啟一個(gè)事務(wù),而Mandatory則不會(huì )。大多數情況下,你可以采用如下的“最佳實(shí)踐”來(lái)區分何時(shí)需要哪種策略:如果某一方法需要運行在具備事務(wù)的環(huán)境,但其本身又不負責事務(wù)的控制(回滾),則該方法應該標志為Mandatory??梢钥闯?,上述最佳實(shí)踐是基于事務(wù)的所有權而得來(lái)的。
Nested vs. RequiresNew vs. Required另外兩個(gè)比較容易的事務(wù)屬性是Spring提供的Nested與RequiresNew。如果當前上下文中存在事務(wù),兩者都是啟動(dòng)一個(gè)新的事務(wù);如果當前上下文中不存在事務(wù),則類(lèi)似于PROPAGATION_REQUIRED,兩者也都新建一個(gè)事務(wù)。那么,它們有什么區別呢?
PROPAGATION_REQUIRES_NEW 啟動(dòng)一個(gè)新的, 不依賴(lài)于環(huán)境的 "內部" 事務(wù). 這個(gè)事務(wù)將被完全 commited 或rolled back 而不依賴(lài)于外部事務(wù), 它擁有自己的隔離范圍, 自己的鎖, 等等。當內部事務(wù)開(kāi)始執行時(shí), 外部事務(wù)將被掛起,內務(wù)事務(wù)結束時(shí), 外部事務(wù)將繼續執行。
另一方面, PROPAGATION_NESTED 開(kāi)始一個(gè) "嵌套的" 事務(wù), 它是已經(jīng)存在事務(wù)的一個(gè)真正的子事務(wù)。嵌套事務(wù)開(kāi)始執行時(shí), 它將取得一個(gè) savepoint。如果這個(gè)嵌套事務(wù)失敗, 我們將回滾到此savepoint。嵌套事務(wù)是外部事務(wù)的一部分, 只有外部事務(wù)結束后它才會(huì )被提交。
總結一下,使用嵌套事務(wù)的場(chǎng)景總共有兩個(gè),如下圖所示:
- 需要事務(wù)BC與事務(wù)AD一起提交,即:作為事務(wù)AD的子事務(wù),事務(wù)BC只有在事務(wù)AD成功提交時(shí)(階段3成功)才提交。這個(gè)需求簡(jiǎn)單稱(chēng)之為“聯(lián)合提交”。這一點(diǎn)PROPAGATION_REQUIRED可以做到。
- 需要事務(wù)BC的回滾不會(huì )影響事務(wù)AD的提交。這個(gè)需求簡(jiǎn)單稱(chēng)之為“隔離回滾”。這一點(diǎn),可以通過(guò)設置事務(wù)屬性為PROPAGATION_REQUIRES_NEW做到。
使用PROPAGATION_REQUIRED滿(mǎn)足需求1,但子事務(wù)BC的rollback會(huì )迫使父事務(wù)AD也回滾,不能滿(mǎn)足需求2。使用PROPAGATION_REQUIRES_NEW滿(mǎn)足需求2,但子事務(wù)(嚴格意義上說(shuō),這時(shí)不應該稱(chēng)之為子事務(wù))BC是一個(gè)全新的事務(wù),父事務(wù)(嚴格意義上說(shuō),這時(shí)也不應該稱(chēng)之為父事務(wù))AD的成功與否完全不影響B(tài)C的提交,不能滿(mǎn)足需求1。
同時(shí)滿(mǎn)足上述兩條需求就要用到PROPAGATION_NESTED了。PROPAGATION_NESTED在事務(wù)AD執行到B點(diǎn)時(shí),設置了savePoint。當BC事務(wù)成功提交時(shí),PROPAGATION_NESTED的行為與PROPAGATION_REQUIRED一樣。只有當事務(wù)AD在D點(diǎn)成功提交時(shí),事務(wù)BC才真正提交; 如果階段3執行異常,導致事務(wù)AD 回滾,事務(wù)BC也將一起回滾,從而滿(mǎn)足了“聯(lián)合提交”。當階段2執行異常,導致BC事務(wù)rollback時(shí),因為設置了savePoint的緣故,AD事務(wù)可以選擇與BC一起rollback或繼續階段3的執行并保留階段1的執行結果,從而滿(mǎn)足了“隔離回滾”。例如,可以把執行BC事務(wù)的方法try-catch起來(lái),在catch中選擇是否繼續向上拋出異常(是,則回滾到A處;否,則回滾到B處)。而是用PROPAGATION_REQUIRED時(shí),即使try-catch住BC的異常,AD事務(wù)也一定會(huì )被無(wú)條件rollback。