Spring中事務傳播行為


1. Spring中七種事務傳播行為

PROPAGATION(蔓延、傳播、傳輸)

事務傳播行為類型 說明
PROPAGATION_REQUIRED 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是默認的事務傳播行為
PROPAGATION_SUPPORTS 支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY 使用當前的事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW 新建事務,如果當前存在事務,把當前事務掛起。(一個新的事務將啟動,而且如果有一個現有的事務在運行的話,則這個方法將在運行期被掛起,直到新的事務提交或者回滾才恢復執行。)
PROPAGATION_NOT_SUPPORTED 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER 以非事務方式執行,如果當前存在事務,則拋出異常。
PROPAGATION_NESTED 如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。(外層事務拋出異常回滾,那么內層事務必須回滾,反之內層事務並不影響外層事務)

 

2. Spring中七種事務定義

Spring在TransactionDefinition接口中規定了7種類型的事務傳播行為。事務傳播行為是Spring框架獨有的事務增強特性。這是Spring為我們提供的強大的工具箱,使用事務傳播行可以為我們的開發工作提供許多便利。

TransactionDefinition接口定義

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;

    int getPropagationBehavior();
    int getIsolationLevel();
    int getTimeout();
    boolean isReadOnly();
    @Nullable
    String getName();
}

Transactional注解的定義

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";
​
    @AliasFor("value")
    String transactionManager() default "";
​
    Propagation propagation() default Propagation.REQUIRED;
​
    Isolation isolation() default Isolation.DEFAULT;
​
    int timeout() default -1;
​
    boolean readOnly() default false;
​
    Class<? extends Throwable>[] rollbackFor() default {};
​
    String[] rollbackForClassName() default {};
​
    Class<? extends Throwable>[] noRollbackFor() default {};
​
    String[] noRollbackForClassName() default {};
}

事務傳播行為用來描述由某一個事務傳播行為修飾的方法被嵌套進另一個方法的時事務如何傳播。

用偽代碼說明:

public void methodA(){

methodB();

//doSomething

}

@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}

代碼中methodA()方法嵌套調用了methodB()方法,methodB()的事務傳播行為@Transaction(Propagation=XXX)設置決定。這里需要注意的是methodA()並沒有開啟事務,某一個事務傳播行為修飾的方法並不是必須要在開啟事務的外圍方法中調用。這里methodA()是外圍方法,並沒有開啟事務。

@Transactional 的事務開啟 ,或者是基於接口的 或者是基於類的代理被創建。所以在同一個類中一個方法調用另一個方法有事務的方法,事務是不會起作用的

2.1 驗證Propagation.REQUIRED

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(UserEntity userEntity){
   userMapper.insertUser(userEntity);
}

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequiredException(UserEntity userEntity){
   userMapper.insertUser(userEntity);
   throw new RuntimeException();
}

2.1.1 場景一

此場景外圍方法沒有開啟事務。

驗證方法1:

@Override
public void noTransactionExceptionRequiredRequired(){
    UserEntity user1=new UserEntity();
    user1.setName("張三001");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四001");
    userService.addRequired(user2);
    throw new RuntimeException();
}

驗證方法2:

@Override
public void noTransactionRequiredRequiredException(){
    UserEntity user1=new UserEntity();
    user1.setName("張三002");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四002");
    userService.addRequiredException(user2);
}

驗證結果:

驗證方法序號 數據庫結果 結果分析
1 “張三”、“李四”均插入。 外圍方法未開啟事務,插入“張三”、“李四”方法在自己的事務中獨立運行,外圍方法異常不影響內部插入“張三”、“李四”方法獨立的事務。
2 “張三”插入,“李四”未插入。 外圍方法沒有事務,插入“張三”、“李四”方法都在自己的事務中獨立運行,所以插入“李四”方法拋出異常只會回滾插入“李四”方法,插入“張三”方法不受影響。

2.1.2 場景二

此場景外圍方法開啟事務。

驗證方法1:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionExceptionRequiredRequired(){
    UserEntity user1=new UserEntity();
    user1.setName("張三003");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四003");
    userService.addRequired(user2);

    throw new RuntimeException();
}

驗證方法2:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionRequiredRequiredException(){
    UserEntity user1=new UserEntity();
    user1.setName("張三004");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四004");
    userService.addRequiredException(user2);
}

驗證方法3:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionRequiredRequiredExceptionTry(){
    UserEntity user1=new UserEntity();
    user1.setName("張三005");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四005");
    try {
        userService.addRequiredException(user2);
    } catch (Exception e) {
        System.out.println("方法回滾");
    }
}

驗證結果:

驗證方法序號 數據庫結果 結果分析
1 “張三”、“李四”均未插入。 外圍方法開啟事務,內部方法加入外圍方法事務,外圍方法回滾,內部方法也要回滾。
2 “張三”、“李四”均未插入。 外圍方法開啟事務,內部方法加入外圍方法事務,內部方法拋出異常回滾,外圍方法感知異常致使整體事務回滾。
3 “張三”、“李四”均未插入。 外圍方法開啟事務,內部方法加入外圍方法事務,內部方法拋出異常回滾,即使方法被catch不被外圍方法感知,整個事務依然回滾。

結論:以上試驗結果我們證明在外圍方法開啟事務的情況下Propagation.REQUIRED修飾的內部方法會加入到外圍方法的事務中,所有Propagation.REQUIRED修飾的內部方法和外圍方法均屬於同一事務,只要一個方法回滾,整個事務均回滾。

 

2.2. 驗證Propagation.PROPAGATION_REQUIRES_NEW

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNew(UserEntity userEntity){
   userMapper.insertUser(userEntity);
}

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNewException(UserEntity userEntity){
   userMapper.insertUser(userEntity);
   throw new RuntimeException();
}

2.2.1 場景一

此場景外圍方法沒有開啟事務。

驗證方法1:

@Override
public void noTransactionExceptionRequiresNewRequiresNew(){
    UserEntity user1=new UserEntity();
    user1.setName("張三006");
    userService.addRequiresNew(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四006");
    userService.addRequiresNew(user2);

    throw new RuntimeException();
}

驗證方法2:

@Override
public void noTransactionRequiresNewRequiresNewException(){
    UserEntity user1=new UserEntity();
    user1.setName("張三007");
    userService.addRequiresNew(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四007");
    userService.addRequiresNewException(user2);
}

驗證結果:

驗證方法序號 數據庫結果 結果分析
1 “張三”插入,“李四”插入。 外圍方法沒有事務,插入“張三”、“李四”方法都在自己的事務中獨立運行,外圍方法拋出異常回滾不會影響內部方法。
2 “張三”插入,“李四”未插入 外圍方法沒有開啟事務,插入“張三”方法和插入“李四”方法分別開啟自己的事務,插入“李四”方法拋出異常回滾,其他事務不受影響。

2.2.2 場景2

外圍方法開啟事務。

驗證方法1:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionExceptionRequiredRequiresNewRequiresNew(){
    UserEntity user1=new UserEntity();
    user1.setName("張三008");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四008");
    userService.addRequiresNew(user2);

    UserEntity user3=new UserEntity();
    user3.setName("王五008");
    userService.addRequiresNew(user3);
    throw new RuntimeException();
}

驗證方法2:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionRequiredRequiresNewRequiresNewException(){
    UserEntity user1=new UserEntity();
    user1.setName("張三009");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四009");
    userService.addRequiresNew(user2);

    UserEntity user3=new UserEntity();
    user3.setName("王五009");
    userService.addRequiresNewException(user3);
}

驗證方法3:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionRequiredRequiresNewRequiresNewExceptionTry(){
    UserEntity user1=new UserEntity();
    user1.setName("張三010");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四010");
    userService.addRequiresNew(user2);

    UserEntity user3=new UserEntity();
    user3.setName("王五010");
    try {
        userService.addRequiresNewException(user3);
    } catch (Exception e) {
        System.out.println("回滾");
    }
}

驗證結果:

驗證方法序號 數據庫結果 結果分析
1 “張三”未插入,“李四”插入,“王五”插入。 外圍方法開啟事務,插入“張三”方法和外圍方法一個事務,插入“李四”方法、插入“王五”方法分別在獨立的新建事務中,外圍方法拋出異常只回滾和外圍方法同一事務的方法,故插入“張三”的方法回滾。
2 “張三”未插入,“李四”插入,“王五”未插入。 外圍方法開啟事務,插入“張三”方法和外圍方法一個事務,插入“李四”方法、插入“王五”方法分別在獨立的新建事務中。插入“王五”方法拋出異常,首先插入 “王五”方法的事務被回滾,異常繼續拋出被外圍方法感知,外圍方法事務亦被回滾,故插入“張三”方法也被回滾。
3 “張三”插入,“李四”插入,“王五”未插入。 外圍方法開啟事務,插入“張三”方法和外圍方法一個事務,插入“李四”方法、插入“王五”方法分別在獨立的新建事務中。插入“王五”方法拋出異常,首先插入“王五”方法的事務被回滾,異常被catch不會被外圍方法感知,外圍方法事務不回滾,故插入“張三”方法插入成功。

結論:在外圍方法開啟事務的情況下Propagation.REQUIRES_NEW修飾的內部方法依然會單獨開啟獨立事務,且與外部方法事務也獨立,內部方法之間、內部方法和外部方法事務均相互獨立,互不干擾。

2.3 REQUIRED,REQUIRES_NEW,NESTED異同

NESTED和REQUIRED修飾的內部方法都屬於外圍方法事務,如果外圍方法拋出異常,這兩種方法的事務都會被回滾。但是REQUIRED是加入外圍方法事務,所以和外圍事務同屬於一個事務,一旦REQUIRED事務拋出異常被回滾,外圍方法事務也將被回滾。而NESTED是外圍方法的子事務,有單獨的保存點,所以NESTED方法拋出異常被回滾,不會影響到外圍方法的事務。

NESTED和REQUIRES_NEW都可以做到內部方法事務回滾而不影響外圍方法事務。但是因為NESTED是嵌套事務,所以外圍方法回滾之后,作為外圍方法事務的子事務也會被回滾。而REQUIRES_NEW是通過開啟新的事務實現的,內部事務和外圍事務是兩個事務,外圍事務回滾不會影響內部事務。

2.4 PROPAGATION_MANDATORY,PROPAGATION_NEVER

2.4.1 PROPAGATION_MANDATORY必須在一個事務中運行。也就是說,他只能被一個父事務調用。否則,他就要拋出異常。

使用當前的事務,如果當前沒有事務,就拋出異常。

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

2.4.2 PROPAGATION_NEVER以非事務方式執行,如果當前存在事務,則拋出異常。

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

2.5 PROPAGATION_SUPPORTS

如果當前已經存在事務,那么加入該事務,否則創建一個所謂的空事務(可以認為無事務執行)。

Should roll back transaction but cannot - no transaction available

2.6 PAGATION_NOT_SUPPORTED

以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

Should roll back transaction but cannot - no transaction available

Resuming suspended transaction after completion of inner transaction

3 使用場景

3.1 如果業務需求每接受到一次請求到要記錄日志,如下圖:

因為insertOprLog()的操作不管扣款和創建訂單成功與否都要生成日志,並且日志的操作成功與否不影響充值處理,所以insertOprLog()方法的事務傳播行為可以定義為:PROPAGATION_REQUIRES_NEW。

3.2 在銀行新增銀行卡業務中,需要執行兩個操作,一個是保存銀行卡信息,一個是激活新創建的銀行卡,其中激活銀行卡成功與否不影響銀行卡的創建。

由以上需求,我們可知對於cardActive()方法的事務傳播行為,可以設置為PROPAGATION_NESTED,insertBankCardAndActive()事務的回滾,cardActive()激活的銀行卡就沒意義,也就需要跟着回滾,而cardActive()的回滾不影響insertBankCard()事務;insertBankCard()的事務傳播行為可以設置為PROPAGATION_REQUIRED。

 

3.3 銀行卡新增成功后,發送郵件給用戶,發送郵件不涉及數據庫的操作。sendCardMail()可以設置為PROPAGATION_NOT_SUPPORTED,發送郵件不屬於並且不應當影響主體業務邏輯,即使發送失敗也不應該對主體業務邏輯回滾。

4 代碼解析

TransactionInterceptor.invoke()

TransactionAspectSupport.invokeWithinTransaction()

AbstractPlatformTransactionManager.getTransaction(TransactionDefinition definition)

DataSourceTransactionManager

 

事務控制是通過TransactionInterceptor.invoke()方法來實現的。下面我們來看一下這個方法的大體結構

public Object invoke(MethodInvocation invocation) throws Throwable {
    // 獲取被代理類
    Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
    Method var10001 = invocation.getMethod();
    invocation.getClass();
    // 執行事務方法
    return this.invokeWithinTransaction(var10001, targetClass, invocation::proceed);
}
    TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    result = null;
​
    try {
        result = invocation.proceedWithInvocation();
    } catch (Throwable var17) {
        this.completeTransactionAfterThrowing(txInfo, var17);
        throw var17;
    } finally {
        this.cleanupTransactionInfo(txInfo);
    }
​
    this.commitTransactionAfterReturning(txInfo);
    return result;
    }

 

 相關代碼地址:https://gitee.com/weixiaotao1992/Working/tree/master/technology_code/propagation


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM