spring事務傳播屬性

spring事務傳播屬性定義在org.springframework.transaction.TransactionDefinition接口,類似于EJB CMT的事務傳播屬性定義,主要有以下幾種類型:

propagation 說明
PROPAGATION_REQUIRED 支持當前事務,如果當前沒有事務,則新建一個事務執(zhí)行
PROPAGATION_SUPPORTS 支持當前事務,如果沒有當前事務,則以非事務的方式執(zhí)行
PROPAGATION_MANDATORY 支持當前事務,如果當前沒有事務,則拋出異常
PROPAGATION_REQUIRES_NEW 創(chuàng)建一個新的事務,如果當前已經有事務了,則將當前事務掛起
PROPAGATION_NOT_SUPPORTED 不支持當前事務,而且總是以非事務方式執(zhí)行
PROPAGATION_NEVER 不支持當前事務,如果存在事務,則拋出異常
PROPAGATION_NESTED 如果當前事務存在,則在嵌套事務中執(zhí)行,否則行為類似于PROPAGATION_REQUIRED。 EJB中沒有類似的功能。

下面,用一個例子來解釋下各個傳播屬性的不同:
在數據庫中新建了一張產品表,兩條數據,一條產品id為1,產品名稱為IPhone6S,另一條產品id為2,產品名稱為MAC PRO。

PROPAGATION_REQUIRED

兩個服務serviceA和serviceB,serviceA的methodA方法先減IPhone6S的庫存,然后調用serviceB中methodB方法扣減MAC Pro庫存,為了觀察,methodA最后手動拋出異常,模擬回滾場景。

  • 支持當前事務
    在傳播屬性為PROPAGATION_REQUIRED時,事務加入方會使用當前事務,methodA代碼為:
    public void methodA() {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        try {
            transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            transactionTemplate.execute(status -> {
                OrdProduct product = ordProductMapper.selectForUpdate(1);
                System.out.println("before update " + product.getProductName() + ", inventory is: " + product.getInventory());
                product.setInventory(product.getInventory() - 1);
                int result = ordProductMapper.updateInventoryByProductId(1, product.getInventory());
                serviceB.methodB();
                if (result == 1) {
                     throw new RuntimeException("test");
                 }
                return result;
            });
        } finally {
            OrdProduct product1 = ordProductMapper.selectForUpdate(1);
            OrdProduct product2 = ordProductMapper.selectForUpdate(2);
            System.out.println("after update " + product1.getProductName() + ", inventory is: " + product1.getInventory());
            System.out.println("after update " + product2.getProductName() + ", inventory is: " + product2.getInventory());
        }
    }

methodB的代碼為:

    public void methodB(){
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        transactionTemplate.execute(transactionStatus -> {
            OrdProduct product = ordProductMapper.selectForUpdate(2);
            System.out.println("before update " + product.getProductName() +  ", inventory is: " + product.getInventory());
            product.setInventory(product.getInventory() -1 );
            int result = ordProductMapper.updateInventoryByProductId(2, product.getInventory());
            /*if (result == 1) {
                throw new RuntimeException("test");
            }*/
            return result;
        });
    }

methodA在完成6s的減庫存操作后,調用methodB,methodB的傳播屬性是PROPAGATION_REQUIRED,此時methodA已經起了事務,methodB會使用現(xiàn)存的事務,在methodA拋出異常后,兩個方法的減庫存操作都會回滾。

  • 當前沒有事務,則新建一個事務
    methodA代碼邏輯不用transactionTemplate.execute()方式執(zhí)行,methodB方法去除注釋拋出異常,methodB會新建一個事務,執(zhí)行后methodB會回滾,methodA不會,即mac庫存不變,6s庫存減1;

PROPAGATION_SUPPORTS

  • 支持當前事務
    和PROPAGATION_REQUIRED行為相同;
  • 沒有當前事務,則以非事務的方式執(zhí)行
    methodB的傳播屬性設置為PROPAGATION_SUPPORTS
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);

methodA代碼邏輯不用transactionTemplate.execute()方式執(zhí)行,methodB方法去除注釋拋出異常,methodB不會新建事務,執(zhí)行后兩個方法都不會回滾,兩個產品的庫存均減1.

PROPAGATION_MANDATORY

  • 支持當前事務
    和PROPAGATION_REQUIRED行為相同;
  • 當前沒有事務,則拋出異常
    methodB的傳播屬性設置為PROPAGATION_MANDATORY
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY);

methodA代碼邏輯不用transactionTemplate.execute()方式執(zhí)行,執(zhí)行時則會拋出如下異常

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

PROPAGATION_REQUIRES_NEW

在當前沒有事務的情況下,行為和PROPAGATION_REQUIRED一致,如果當前已有事務,則將當前事務掛起,開啟一個新的事務。


tx_prop_requires_new.png

從上圖可以看出傳播屬性是PROPAGATION_REQUIRES_NEW時候的事務執(zhí)行過程。PROPAGATION_REQUIRES_NEW始終對每個受影響的事務范圍采用獨立的物理事務,而不受外圍事務的影響。
在這樣的安排中,底層資源事務是不同的,因此可以獨立地提交或回滾,外部事務不受內部事務的回滾狀態(tài)的影響,并且內部事務的鎖在完成后立即釋放。這樣一個獨立的內部事務也可以聲明它自己的隔離級別,超時和只讀設置,而不是繼承外部事務的特性。

  • 獨立的物理事務
    要理解這一點可以用下面的例子來說明:
    public void methodA() {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        try {
            transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            transactionTemplate.execute(status -> {
                OrdProduct product = ordProductMapper.selectForUpdate(1);
                System.out.println("before update " + product.getProductName() + ", inventory is: " + product.getInventory());
                product.setInventory(product.getInventory() - 1);
                int result = ordProductMapper.updateInventoryByProductId(1, product.getInventory());
                serviceB.methodB();
                if (result == 1) {
                    throw new RuntimeException("test");
                }
                return result;
            });
        } finally {
            OrdProduct product1 = ordProductMapper.selectForUpdate(1);
            OrdProduct product2 = ordProductMapper.selectForUpdate(2);
            System.out.println("after update " + product1.getProductName() + ", inventory is: " + product1.getInventory());
            System.out.println("after update " + product2.getProductName() + ", inventory is: " + product2.getInventory());
        }
    }
    
    
        public void methodB(){
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        transactionTemplate.execute(transactionStatus -> {
            OrdProduct product = ordProductMapper.selectForUpdate(2);
            System.out.println("before update " + product.getProductName() +  ", inventory is: " + product.getInventory());
            product.setInventory(product.getInventory() -1 );
            int result = ordProductMapper.updateInventoryByProductId(2, product.getInventory());
            /*if (result == 1) {
                throw new RuntimeException("test");
            }*/
            return result;
        });

    }

serviceA拋出異常,serviceB是內部事務,在serviceB的傳播行為是PROPAGATION_REQUIRES_NEW時,那么serviceB將開啟一個獨立的物理事務,并不受外部事務的影響,因此不會回滾,結果可以看到,serviceA中IPhone6S庫存不變,serviceB中MAC Pro庫存減1.

before update IPhone6S, inventory is: 9985
before update MAC Pro, inventory is: 9989
after update IPhone6S, inventory is: 9985
after update MAC Pro, inventory is: 9988

同樣,內部事務的回滾狀態(tài)也不會影響外部事務,可以用以下的例子來說明:

    public void methodA() {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        try {
            transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            transactionTemplate.execute(status -> {
                OrdProduct product = ordProductMapper.selectForUpdate(1);
                System.out.println("before update " + product.getProductName() + ", inventory is: " + product.getInventory());
                product.setInventory(product.getInventory() - 1);
                int result = ordProductMapper.updateInventoryByProductId(1, product.getInventory());
                serviceB.methodB();
               /* if (result == 1) {
                    throw new RuntimeException("test");
                }*/
                return result;
            });
        } finally {
            OrdProduct product1 = ordProductMapper.selectForUpdate(1);
            OrdProduct product2 = ordProductMapper.selectForUpdate(2);
            System.out.println("after update " + product1.getProductName() + ", inventory is: " + product1.getInventory());
            System.out.println("after update " + product2.getProductName() + ", inventory is: " + product2.getInventory());
        }
    }
    
        public void methodB(){
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        transactionTemplate.execute(transactionStatus -> {
            OrdProduct product = ordProductMapper.selectForUpdate(2);
            System.out.println("before update " + product.getProductName() +  ", inventory is: " + product.getInventory());
            product.setInventory(product.getInventory() -1 );
            int result = ordProductMapper.updateInventoryByProductId(2, product.getInventory());
            /*if (result == 1) {
                throw new RuntimeException("test");
            }*/
            transactionStatus.setRollbackOnly();
            return result;
        });

    }

methodA不拋出異常,methodB設置rollbackOnly,結果是methodB回滾,methodA不受影響,不回滾。

before update IPhone6S, inventory is: 9985
before update MAC Pro, inventory is: 9988
after update IPhone6S, inventory is: 9984
after update MAC Pro, inventory is: 9988

而在methodB的傳播屬性是PROPAGATION_REQUIRED是,methodA會拋出UnexpectedRollbackException,并進行回滾。

PROPAGATION_NOT_SUPPORTED & PROPAGATION_NEVER

這兩者都比較好理解,此處不做過多贅述。

PROPAGATION_NESTED

PROPAGATION_NESTED 開始一個 "嵌套的" 事務, 它是已經存在事務的一個真正的子事務. 嵌套事務開始執(zhí)行時, 它將取得一個 savepoint. 如果這個嵌套事務失敗, 我們將回滾到此 savepoint. 嵌套事務是外部事務的一部分, 只有外部事務結束后它才會被提交. 如果外部事務 commit, 嵌套事務也會被 commit, 這個規(guī)則同樣適用于 roll back.

關于PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW的區(qū)別,spring的創(chuàng)始人之一Juergen Hoeller有以下解釋:

PROPAGATION_REQUIRES_NEW starts a new, independent "inner" transaction for the given scope. This transaction will be committed or rolled back completely independent from the outer transaction, having its own isolation scope, its own set of locks, etc. The outer transaction will get suspended at the beginning of the inner one, and resumed once the inner one has completed.

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. if 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.

需要注意的是,PROPAGATION_NESTED使用JDBC保存點,因此,它只適用于JDBC資源事務。

筆者在實際中也未使用過PROPAGATION_NESTED屬性,因此,如果有讀者使用碰到問題,歡迎來信一起探討wgy@live.cn

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容