zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    详细分析MySQL事务日志(redo log和undo log)
    详细分析MySQL的日志(一)
    MySQL/MariaDB中的事务和事务隔离级别
    详细介绍MySQL/MariaDB的锁
    MariaDB/MySQL用户和权限管理
    (MariaDB)开窗函数用法
    翻译:window function(已提交到MariaDB官方手册)
    翻译:group_concat()函数(已提交到MariaDB官方手册)
    (MariaDB/MySQL)之DML(1):数据插入
    (MariaDB/MySQL)MyISAM存储引擎读、写操作的优先级
  • 原文地址:https://www.cnblogs.com/weixiaotao/p/10538158.html
Copyright © 2011-2022 走看看