• 一个99%的人都说不清楚知识点——Spring 事务传播行为


    面试过很多人,大部分都能把事务的四个特性及隔离级别说得七七八八,但当问到 Spring 的传播行为时,就基本上没人能说出个一二三了。

    我们都知道,一个事务要么成功,要么失败。但当若干个事务配合完成一个复杂任务时,就不能简单的这样一刀切了。我们需要根据任务之间的亲疏关系来指定哪些任务需要联动回滚,哪些任务即使失败也不会影响其他任务。要解决这个问题,就需要了解事务的传播行为了。Spring 中有七种事务的传播行为,如下表所示:

    事务传播行为类型说明
    PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
    PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
    PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
    PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
    PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
    PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

    Spring 可以通过 @Transactional 注解的 propagation 属性来设置不同的传播行为策略。Spring 为此提供了一个枚举类 Propagation,源码如下:

    package org.springframework.transaction.annotation;
    
    import org.springframework.transaction.TransactionDefinition;
    
    public enum Propagation {
    
        /**
         * 需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,
         * 否则新建一个事务运行内部方法
         */
        REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
    
        /**
         * 支持事务,如果当前存在事务,就沿用当前事务,
         * 如果不存在,则继续采用无事务的方式运行内部方法
         */
        SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
    
        /**
         * 必须使用事务,如果当前没有事务,则会抛出异常,
         * 如果存在当前事务,则沿用当前事务
         */
        MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
    
        /**
         * 无论当前事务是否存在,都会创建新事务运行方法,
         * 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
         */
        REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
    
        /**
         * 不支持事务,当前存在事务时,将挂起事务,运行方法
         */
        NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
    
        /**
         * 不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
         */
        NEVER(TransactionDefinition.PROPAGATION_NEVER),
    
        /** 
         * 在当前方法调用内部方法时,如果内部方法发生异常,
         * 只回滚内部方法执行过的 SQL ,而不回滚当前方法的事务
         */
        NESTED(TransactionDefinition.PROPAGATION_NESTED);
    
    		......
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    接下来我们通过对其中三种最常用的(REQUIRED、REQUIRES_NEW、NESTED)策略进行对比来更深入的理解。以下测试均在外部方法开启事务的情况下进行,因为在外部没有事务的情况下,三者都会新建事务,效果一样。

    REQUIRED

    当内部方法的事务传播行为设置为 REQUIRED 时,内部方法会加入外部方法的事务。我们在 UserServiceImpl 中添加如下方法:

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
        @Autowired
        private UserMapper mapper;
        
        @Override
        @Transactional(propagation = Propagation.REQUIRED)
        public void addWithRequired(User user) {
            mapper.insert(user);
        }
    
        @Override
        @Transactional(propagation = Propagation.REQUIRED)
        public void addWithRequiredAndException(User user) {
            mapper.insert(user);
            throw new RuntimeException();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    创建 TransactionServiceImpl 类,并添加如下方法:

    @Slf4j
    @Service
    public class TransactionServiceImpl implements TransactionService {
    
        @Autowired
        private UserService userService;
    
        @Override
        public void noTransaction_required_required_externalException() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            userService.addWithRequired(xiaoShui);
            userService.addWithRequired(xiaoJing);
            throw new RuntimeException();
            
        }
    
        @Override
        public void noTransaction_required_requiredException() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            userService.addWithRequired(xiaoShui);
            userService.addWithRequiredAndException(xiaoJing);
            
        }
    
        @Override
        @Transactional
        public void transaction_required_required_externalException() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            userService.addWithRequired(xiaoShui);
            userService.addWithRequired(xiaoJing);
            throw new RuntimeException();
            
        }
    
        @Override
        @Transactional
        public void transaction_required_requiredException() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            userService.addWithRequired(xiaoShui);
            userService.addWithRequiredAndException(xiaoJing);
            
        }
    
    
        @Override
        @Transactional
        public void transaction_required_requiredException_try() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            userService.addWithRequired(xiaoShui);
            try {
                userService.addWithRequiredAndException(xiaoJing);
            } catch (Exception e) {
                log.error("发生异常,事务回滚!");
            }
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    结果分析如下表所示:

    方法结果分析
    noTransaction_required_required_externalException小水和小镜均成功入库外部方法未开启事务,所以所有插入操作均未受到外部异常影响
    noTransaction_required_requiredException小水入库,小镜未入库外部方法未开启事务,内部方法事务各自独立,互不影响,「小镜」的插入方法发生异常回滚,但「小水」的插入方法不受影响
    transaction_required_required_externalException小水和小镜均未入库外部方法开启事务,所有内部方法均加入外部方法的事务中。而外部方法发生异常,所以导致所有操作都发生回滚
    transaction_required_requiredException小水和小镜均未入库外部方法开启事务,所有内部方法均加入外部方法的事务中。由于「小镜」的插入方法发生异常,此时所有方法都处于同一个事务中,所以导致所有操作都发生回滚
    transaction_required_requiredException_try小水和小镜均未入库外部方法开启事务,所有内部方法均加入外部方法的事务中。由于「小镜」的插入方法发生异常,此时所有方法都处于同一个事务中,即使发生异常的部分被 try-catch 住,所有操作仍然会回滚

    前面四种情况都比较好理解,很多人不能理解最后一种情况:我都 try-catch 了你还想怎样?这里的关键点在于所有方法都处于同一个事务中,此时「小镜」的插入方法发生异常,那么这个方法所在的事务就会被 Spring 设置为 rollback 状态。因为异常被 catch 了,所以外部方法执行完要进行 commit 操作,这时却发现当前事务已经处于 rollback 状态了,虽然它不知道哪里出了问题,但也只能听从指挥,回滚所有操作了。

    PS:由于外部方法不开启事务的情况,在每种传播行为下结果都是类似的,所以后面不再给出示例。

    REQUIRES_NEW

    当内部方法的传播行为设置为 REQUIRES_NEW 时,内部方法会先将外部方法的事务挂起,然后开启一个新的事务 。在 UserServiceImpl 中添加如下方法:

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
        ...
    
        @Override
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void addWithRequiredNew(User user) {
            
            mapper.insert(user);
            
        }
    
        @Override
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void addWithRequiredNewAndException(User user) {
            
            mapper.insert(user);
            throw new RuntimeException();
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在 TransactionServiceImpl 中添加如下方法:

    @Slf4j
    @Service
    public class TransactionServiceImpl implements TransactionService {
    
        ...
    
        @Override
        @Transactional
        public void transaction_required_requiredNew_externalException() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            userService.addWithRequired(xiaoShui);
            userService.addWithRequiredNew(xiaoJing);
            throw new RuntimeException();
            
        }
    
        @Override
        @Transactional
        public void transaction_required_requiredNew_requiredNewException() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            User shuiJing = new User().setName("水镜");
            userService.addWithRequired(xiaoShui);
            userService.addWithRequiredNew(xiaoJing);
            userService.addWithRequiredNewAndException(shuiJing);
            
        }
    
        @Override
        @Transactional
        public void transaction_required_requiredNewException_try() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            User shuiJing = new User().setName("水镜");
            userService.addWithRequired(xiaoShui);
            userService.addWithRequiredNew(xiaoJing);
            try {
                userService.addWithRequiredNewAndException(shuiJing);
            } catch (Exception e) {
                log.error("发生异常,事务回滚!");
            }
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    结果分析如下表所示:

    方法结果分析
    transaction_required_requiredNew_externalException小水未入库,小镜入库外部方法开启事务,「小水」的插入方法和外部方法在同一个事务中,跟随外部方法发生回滚;「小镜」的插入方法开启一个独立的新事务,不受外部方法异常的影响
    transaction_required_requiredNew_requiredNewException小水未入库,小镜入库,水镜未入库外部方法开启事务,「水镜」的插入方法开启一个独立的新事务,因为发生异常,所以自己回滚了;「水镜」的异常没有做处理,因此会被外部方法感知到,「小水」的插入方法和外部方法在同一个事务中,跟随外部方法发生回滚;「小镜」的插入方法也会开启一个独立的新事务,因此不会受到任何方法的影响,成功入库
    transaction_required_requiredNewException_try小水和小镜入库,水镜未入库外部方法开启事务,「水镜」的插入方法开启一个独立的新事务,因为发生异常,所以自己回滚了;「水镜」的异常被 try-catch 处理了,其他方法正常提交「小水」和「小镜」成功入库

    NESTED

    当内部方法的传播行为设置为 NESTED 时,内部方法会开启一个新的嵌套事务(子事务)。在 UserServiceImpl 中添加如下方法:

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
        ...
    
        @Override
        @Transactional(propagation = Propagation.NESTED)
        public void addWithNested(User user) {
            
            mapper.insert(user);
            
        }
    
        @Override
        @Transactional(propagation = Propagation.NESTED)
        public void addWithNestedAndException(User user) {
            
            mapper.insert(user);
            throw new RuntimeException();
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在 TransactionServiceImpl 中添加如下方法:

    @Slf4j
    @Service
    public class TransactionServiceImpl implements TransactionService {
    
        ...
    
        @Override
        @Transactional
        public void transaction_nested_nested_externalException() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            userService.addWithNested(xiaoShui);
            userService.addWithNested(xiaoJing);
            throw new RuntimeException();
            
        }
    
        @Override
        @Transactional
        public void transaction_nested_nestedException() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            userService.addWithNested(xiaoShui);
            userService.addWithNestedAndException(xiaoJing);
            
        }
    
        @Override
        @Transactional
        public void transaction_nested_nestedException_try() {
            
            User xiaoShui = new User().setName("小水");
            User xiaoJing = new User().setName("小镜");
            User shuiJing = new User().setName("水镜");
            userService.addWithRequired(xiaoShui);
            userService.addWithNested(xiaoJing);
            try {
                userService.addWithNestedAndException(shuiJing);
            } catch (Exception e) {
                log.error("发生异常,事务回滚!",e);
            }
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    结果分析如下表所示:

    方法结果分析
    transaction_nested_nested_externalException小水和小镜均未入库外部方法开启事务,内部方法开启各自的子事务,外部方法发生异常,主事务回滚,子事务跟随主事务回滚
    transaction_nested_nestedException小水和小镜均未入库外部方法开启事务,内部方法开启各自的子事务,「小镜」的插入方法发生异常回滚自己的子事务;「小镜」的异常没有做处理,因此会被外部方法感知到,「小水」的插入方法在外部方法的子事务中,所以跟随主事务回滚
    transaction_nested_nestedException_try小水和小镜入库,水镜未入库外部方法开启事务,「小镜」和「水镜」开启各自的子事务,「小水」加入外部方法的事务。「水镜」的插入方法发生异常回滚自己的子事务;「水镜」的异常被 try-catch 处理了,其他方法正常提交「小水」和「小镜」成功入库

    每个 NESTED 事务执行前会将当前操作保存下来,叫做 savepoint (保存点),如果当前 NESTED 事务执行失败,则回滚到之前的保存点,保存点使得子事务的回滚不对主事务造成影响。NESTED 事务在外部事务提交以后自己才会提交。

    总结

    REQUIRES_NEW 最为简单,不管当前有无事务,它都会开启一个全新事务,既不影响外部事务,也不会影响其他内部事务,真正的井水不犯河水,坚定而独立。

    REQUIRED 在没有外部事务的情况下,会开启一个独立的新事务,且不会对其他同级事务造成影响;而当存在外部事务的情况下,则会与外部事务同生共死。

    NESTED 在没有外部事务的情况下与 REQUIRED 效果相同;而当存在外部事务的情况下,当外部事务回滚时,它会创建一个嵌套事务(子事务)。外部事务回滚时,子事务会跟着回滚;但子事务的回滚不会对外部事务和其他同级事务造成影响。

  • 相关阅读:
    Hyperf微服务搭建
    这几款文档笔记工具,你习惯用哪个?
    前端开发常用哪些工具软件?
    不同的度量调整效果
    Vue项目devServer.proxy代理配置详解
    2024-3-13-C++day3作业
    技术团队要小心,那些技术过早优化的迹象
    观测云产品更新|新增观测云、SLS 联合解决方案;新增 3 个智能巡检配置文档;新增链路错误追踪查看器等
    科研教育「双目视觉技术」首选!维视MV-VS220双目立体视觉系统开发平台
    美容行业怎样利用自动化程序减少积压索赔案件
  • 原文地址:https://blog.csdn.net/liushuijinger/article/details/125509051