spring事務(wù)傳播屬性

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

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

下面,用一個(gè)例子來(lái)解釋下各個(gè)傳播屬性的不同:
在數(shù)據(jù)庫(kù)中新建了一張產(chǎn)品表,兩條數(shù)據(jù),一條產(chǎn)品id為1,產(chǎn)品名稱為IPhone6S,另一條產(chǎn)品id為2,產(chǎn)品名稱為MAC PRO。

PROPAGATION_REQUIRED

兩個(gè)服務(wù)serviceA和serviceB,serviceA的methodA方法先減IPhone6S的庫(kù)存,然后調(diào)用serviceB中methodB方法扣減MAC Pro庫(kù)存,為了觀察,methodA最后手動(dòng)拋出異常,模擬回滾場(chǎng)景。

  • 支持當(dāng)前事務(wù)
    在傳播屬性為PROPAGATION_REQUIRED時(shí),事務(wù)加入方會(huì)使用當(dāng)前事務(wù),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的減庫(kù)存操作后,調(diào)用methodB,methodB的傳播屬性是PROPAGATION_REQUIRED,此時(shí)methodA已經(jīng)起了事務(wù),methodB會(huì)使用現(xiàn)存的事務(wù),在methodA拋出異常后,兩個(gè)方法的減庫(kù)存操作都會(huì)回滾。

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

PROPAGATION_SUPPORTS

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

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

PROPAGATION_MANDATORY

  • 支持當(dāng)前事務(wù)
    和PROPAGATION_REQUIRED行為相同;
  • 當(dāng)前沒(méi)有事務(wù),則拋出異常
    methodB的傳播屬性設(shè)置為PROPAGATION_MANDATORY
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY);

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

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

PROPAGATION_REQUIRES_NEW

在當(dāng)前沒(méi)有事務(wù)的情況下,行為和PROPAGATION_REQUIRED一致,如果當(dāng)前已有事務(wù),則將當(dāng)前事務(wù)掛起,開(kāi)啟一個(gè)新的事務(wù)。


tx_prop_requires_new.png

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

  • 獨(dú)立的物理事務(wù)
    要理解這一點(diǎn)可以用下面的例子來(lái)說(shuō)明:
    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是內(nèi)部事務(wù),在serviceB的傳播行為是PROPAGATION_REQUIRES_NEW時(shí),那么serviceB將開(kāi)啟一個(gè)獨(dú)立的物理事務(wù),并不受外部事務(wù)的影響,因此不會(huì)回滾,結(jié)果可以看到,serviceA中IPhone6S庫(kù)存不變,serviceB中MAC Pro庫(kù)存減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

同樣,內(nèi)部事務(wù)的回滾狀態(tài)也不會(huì)影響外部事務(wù),可以用以下的例子來(lái)說(shuō)明:

    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設(shè)置rollbackOnly,結(jié)果是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會(huì)拋出UnexpectedRollbackException,并進(jìn)行回滾。

PROPAGATION_NOT_SUPPORTED & PROPAGATION_NEVER

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

PROPAGATION_NESTED

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

關(guān)于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保存點(diǎn),因此,它只適用于JDBC資源事務(wù)。

筆者在實(shí)際中也未使用過(guò)PROPAGATION_NESTED屬性,因此,如果有讀者使用碰到問(wèn)題,歡迎來(lái)信一起探討wgy@live.cn。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • @Transactional中可以設(shè)置以下7種事務(wù)傳播行為 REQUIRED:支持當(dāng)前事務(wù),如果當(dāng)前沒(méi)有事務(wù),就新...
    Blue_hr閱讀 23,479評(píng)論 0 14
  • 事務(wù)的嵌套概念 所謂事務(wù)的嵌套就是兩個(gè)事務(wù)方法之間相互調(diào)用。spring事務(wù)開(kāi)啟 ,或者是基于接口的或者是基于類的...
    jackcooper閱讀 1,445評(píng)論 0 10
  • 事務(wù),是為了保障邏輯處理的原子性、一致性、隔離性、永久性。 通過(guò)事務(wù)控制,可以避免因?yàn)檫壿嬏幚硎《鴮?dǎo)致產(chǎn)生臟數(shù)據(jù)...
    uzip柚子皮閱讀 4,709評(píng)論 3 16
  • 一、事務(wù)的基本原理 Spring事務(wù)的本質(zhì)其實(shí)就是數(shù)據(jù)庫(kù)對(duì)事務(wù)的支持,沒(méi)有數(shù)據(jù)庫(kù)的事務(wù)支持,spring是無(wú)法提供...
    芭蕾武閱讀 1,702評(píng)論 3 12
  • 我走在戈壁 戈壁并非荒蕪 總有倔強(qiáng)地野草 期盼著春天 盡管石礪遍布 盡管土地貧瘠 有一份堅(jiān)持 便有一份希望 我從沙...
    Chi李學(xué)生閱讀 337評(píng)論 2 4