我們可以看到, 在 spring 中一共定義了六種事務(wù)傳播屬性, 如果你覺得看起來不夠直觀, 那么我來轉(zhuǎn)貼一個滿大街都有的翻譯
在我所見過的誤解中, 最常見的是下面這種:
/**
* 事務(wù)屬性配置為 PROPAGATION_REQUIRED
*/
void methodA() {
// 調(diào)用 ServiceB 的方法
ServiceB.methodB();
}
那么如果 ServiceB 的 methodB 如果配置了事務(wù), 就必須配置為 PROPAGATION_NESTED
這種想法可能害了不少人, 認(rèn)為 Service 之間應(yīng)該避免互相調(diào)用, 其實根本不用擔(dān)心這點,PROPAGATION_REQUIRED 已經(jīng)說得很明白,
如果當(dāng)前線程中已經(jīng)存在事務(wù), 方法調(diào)用會加入此事務(wù), 果當(dāng)前沒有事務(wù),就新建一個事務(wù), 所以 ServiceB#methodB() 的事務(wù)只要遵循最普通的規(guī)則配置為 PROPAGATION_REQUIRED 即可, 如果 ServiceB#methodB (我們稱之為內(nèi)部事務(wù), 為下文打下基礎(chǔ)) 拋了異常, 那么 ServiceA#methodA(我們稱之為外部事務(wù)) 如果沒有特殊配置此異常時事務(wù)提交 (即 +MyCheckedException的用法), 那么整個事務(wù)是一定要 rollback 的, 什么 Service 只能調(diào) Dao 之類的言論純屬無稽之談, spring 只負(fù)責(zé)配置了事務(wù)屬性方法的攔截, 它怎么知道你這個方法是在 Service 還是 Dao 里 ?
說了這么半天, 那到底什么是真正的事務(wù)嵌套呢, 解釋之前我們來看一下 Juergen Hoeller 的原話
Such independent inner transactions are for example used for id generation through manual sequences, where the access to the sequence table should happen in its own transactions, to keep the lock there as short as possible. The goal there is to avoid tying the sequence locks to the (potentially much longer running) outer transaction, with the sequence lock not getting released before completion of the outer transaction.
PROPAGATION_NESTED on the other hand starts a "nested" transaction, which is a true subtransaction of the existing one. What will happen is that a savepoint will be taken at the start of the nested transaction. íf the nested transaction fails, we will roll back to that savepoint. The nested transaction is part of of the outer transaction, so it will only be committed at the end of of the outer transaction.
Nested transactions essentially allow to try some execution subpaths as subtransactions: rolling back to the state at the beginning of the failed subpath, continuing with another subpath or with the main execution path there - all within one isolated transaction, and not losing any previous work done within the outer transaction.
For example, consider parsing a very large input file consisting of account transfer blocks: The entire file should essentially be parsed within one transaction, with one single commit at the end. But if a block fails, its transfers need to be rolled back, writing a failure marker somewhere. You could either start over the entire transaction every time a block fails, remembering which blocks to skip - or you mark each block as a nested transaction, only rolling back that specific set of operations, keeping the previous work of the outer transaction. The latter is of course much more efficient, in particular when a block at the end of the file fails.
So if an inner transaction throws an exception and is supposed to be rolled back (according to the rollback rules), the transaction will get rolled back to the savepoint taken at the start of the inner transaction. The immediate calling code can then decide to catch the exception and proceed down some other path within the outer transaction.
If the code that called the inner transaction lets the exception propagate up the call chain, the exception will eventually reach the demarcation code of the outer transaction. At that point, the rollback rules of the outer transaction decide whether to trigger a rollback. That would be a rollback of the entire outer transaction then.
So essentially, it depends on your exception handling. If you catch the exception thrown by the inner transaction, you can proceed down some other path within the outer transaction. If you let the exception propagate up the call chain, it‘s eventually gonna cause a rollback of the entire outer transaction.
也就是說, 最容易弄混淆的其實是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED, 那么這兩種方式又有何區(qū)別呢? 我簡單的翻譯一下 Juergen Hoeller 的話 :
PROPAGATION_REQUIRES_NEW 啟動一個新的, 不依賴于環(huán)境的 "內(nèi)部" 事務(wù). 這個事務(wù)將被完全 commited 或 rolled back 而不依賴于外部事務(wù), 它擁有自己的隔離范圍, 自己的鎖, 等等. 當(dāng)內(nèi)部事務(wù)開始執(zhí)行時, 外部事務(wù)將被掛起, 內(nèi)務(wù)事務(wù)結(jié)束時, 外部事務(wù)將繼續(xù)執(zhí)行.
另一方面, PROPAGATION_NESTED 開始一個 "嵌套的" 事務(wù), 它是已經(jīng)存在事務(wù)的一個真正的子事務(wù). 潛套事務(wù)開始執(zhí)行時, 它將取得一個 savepoint. 如果這個嵌套事務(wù)失敗, 我們將回滾到此 savepoint. 潛套事務(wù)是外部事務(wù)的一部分, 只有外部事務(wù)結(jié)束后它才會被提交.
由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區(qū)別在于, PROPAGATION_REQUIRES_NEW 完全是一個新的事務(wù), 而 PROPAGATION_NESTED 則是外部事務(wù)的子事務(wù), 如果外部事務(wù) commit, 潛套事務(wù)也會被 commit, 這個規(guī)則同樣適用于 roll back.
那么外部事務(wù)如何利用嵌套事務(wù)的 savepoint 特性呢, 我們用代碼來說話
這種情況下, 因為 ServiceB#methodB 的事務(wù)屬性為 PROPAGATION_REQUIRES_NEW, 所以兩者不會發(fā)生任何關(guān)系, ServiceA#methodA 和 ServiceB#methodB 不會因為對方的執(zhí)行情況而影響事務(wù)的結(jié)果, 因為它們根本就是兩個事務(wù), 在 ServiceB#methodB 執(zhí)行時 ServiceA#methodA 的事務(wù)已經(jīng)掛起了 (關(guān)于事務(wù)掛起的內(nèi)容已經(jīng)超出了本文的討論范圍, 有時間我會再寫一些掛起的文章) .
那么 PROPAGATION_NESTED 又是怎么回事呢? 繼續(xù)看代碼
現(xiàn)在的情況就變得比較復(fù)雜了, ServiceB#methodB 的事務(wù)屬性被配置為 PROPAGATION_NESTED, 此時兩者之間又將如何協(xié)作呢? 從 Juergen Hoeller 的原話中我們可以找到答案, ServiceB#methodB 如果 rollback, 那么內(nèi)部事務(wù)(即 ServiceB#methodB) 將回滾到它執(zhí)行前的 SavePoint(注意, 這是本文中第一次提到它, 潛套事務(wù)中最核心的概念), 而外部事務(wù)(即 ServiceA#methodA) 可以有以下兩種處理方式:
1. 改寫 ServiceA 如下
這種方式也是潛套事務(wù)最有價值的地方, 它起到了分支執(zhí)行的效果, 如果 ServiceB.methodB 失敗, 那么執(zhí)行 ServiceC.methodC(), 而 ServiceB.methodB 已經(jīng)回滾到它執(zhí)行之前的 SavePoint, 所以不會產(chǎn)生臟數(shù)據(jù)(相當(dāng)于此方法從未執(zhí)行過), 這種特性可以用在某些特殊的業(yè)務(wù)中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都沒有辦法做到這一點. (題外話 : 看到這種代碼, 似乎似曾相識, 想起了 prototype.js 中的 Try 函數(shù) )
2. 代碼不做任何修改, 那么如果內(nèi)部事務(wù)(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滾到它執(zhí)行之前的 SavePoint(在任何情況下都會如此),
外部事務(wù)(即 ServiceA#methodA) 將根據(jù)具體的配置決定自己是 commit 還是 rollback (+MyCheckedException).
上面大致講述了潛套事務(wù)的使用場景, 下面我們來看如何在 spring 中使用 PROPAGATION_NESTED, 首先來看 AbstractPlatformTransactionManager
1. 我們要設(shè)置 transactionManager 的 nestedTransactionAllowed 屬性為 true, 注意, 此屬性默認(rèn)為 false!!!
再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法
可以看到 Savepoint 是 SavepointManager.createSavepoint 實現(xiàn)的, 再看 SavepointManager 的層次結(jié)構(gòu), 發(fā)現(xiàn)
其 Template 實現(xiàn)是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager
中的 TransactonObject 都是它的子類 :