• 面试官:说说Spring中的事务传播行为


    前言

    在开发中,相信大家都使用过Spring的事务管理功能。那么,你是否有了解过,Spring的事务传播行为呢?

    Spring中,有7种类型的事务传播行为。事务传播行为是Spring框架提供的一种事务管理方式,它不是数据库提供的。不知道大家是否听说过“不要在service事务方法中嵌套事务方法,这样会提交多个事务”的说法,其实这是不准确的。了解了事务传播行为之后,相信你就会明白!

    原创声明

    本文首发于头条号【Happyjava】。Happy的掘金地址:https://juejin.im/user/5cc2895df265da03a630ddca,Happy的个人博客:(http://blog.happyjava.cn)[http://blog.happyjava.cn]。欢迎转载,但须保留此段声明。

    Spring中七种事务传播行为

    事务的传播行为,默认值为 Propagation.REQUIRED。可以手动指定其他的事务传播行为,如下:

    • Propagation.REQUIRED

    如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。

    • Propagation.SUPPORTS

    如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

    • Propagation.MANDATORY

    如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

    • Propagation.REQUIRES_NEW

    重新创建一个新的事务,如果当前存在事务,延缓当前的事务。

    • Propagation.NOT_SUPPORTED

    以非事务的方式运行,如果当前存在事务,暂停当前的事务。

    • Propagation.NEVER

    以非事务的方式运行,如果当前存在事务,则抛出异常。

    • Propagation.NESTED

    如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。

    准备工作

    数据库表:

    CREATE TABLE `t_user` (
      `id` int(11) NOT NULL,
      `password` varchar(255) DEFAULT NULL,
      `username` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    一个整合了Spring Data JPA的SpringBoot工程,这里就不多说了。

    REQUIRED(默认的事务传播行为)

    默认的事务传播行为是Propagation.REQUIRED,也就是说:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。

    下面,我们就验证下前面说的“不要循环嵌套事务方法”的问题:

    现在有两个Service,如下:

    UserService.java

    @Service
    public class UserService {
    
        @Autowired
        private UserRepo userRepo;
    
        @Transactional(propagation = Propagation.REQUIRED)
        public void insert() {
            UserEntity user = new UserEntity();
            user.setUsername("happyjava");
            user.setPassword("123456");
            userRepo.save(user);
        }
    
    
    }
    

    这里很简单,就一个insert插入用户的方法。

    UserService2.java

    @Service
    public class UserService2 {
    
        @Autowired
        private UserService userService;
    
    
        @Transactional
        public void inserBatch() {
            for (int i = 0; i < 10; i++) {
                if (i == 9) {
                    throw new RuntimeException();
                }
                userService.insert();
            }
        }
    
    }
    

    注入UserService,循环十次调用参数方法。并且第十次抛出异常。调用inserBatch方法,查看结果:

    @Test
    public void insertBatchTest() {
        userService2.inserBatch();
    }
    

    结果如下:

    数据库中没有记录:

    这也证明了“如果当前存在事务,则加入该事务”的概念。如果以后还碰到有人说不要循环嵌套事务的话,可以叫他回去好好看看Spring的事务传播行为。

    SUPPORTS

    如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。也就是说,该模式是否支持事务,看调用它的方法是否有事务支持。测试代码如下:

    UserService

    @Transactional(propagation = Propagation.SUPPORTS)
    public void insert() {
        UserEntity user = new UserEntity();
        user.setUsername("happyjava");
        user.setPassword("123456");
        userRepo.save(user);
        throw new RuntimeException();
    }
    

    UserService2

    public void insertWithoutTx() {
        userService.insert();
    }
    

    调用的方法没有开启事务,运行结果:

    运行报错了,但是数据却没有回滚掉。说明了insert方法是没有在事务中运行的。

    MANDATORY

    如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。mandatory中文是强制性的意思,表明了被修饰的方法,一定要在事务中去调用,否则会抛出异常。

    UserService.java

    @Transactional(propagation = Propagation.MANDATORY)
    public void insert() {
        UserEntity user = new UserEntity();
        user.setUsername("happyjava");
        user.setPassword("123456");
        userRepo.save(user);
    }
    

    UserService2.java

    public void insertWithoutTx() {
    	userService.insert();
    }
    

    调用:

    @Test
    public void insertWithoutTxTest() {
        userService2.insertWithoutTx();
    }
    
    

    运行结果:

    抛出了异常,提示没有存在的事务。

    REQUIRES_NEW

    这个理解起来可能会比较绕,官方的解释是这样子的:

    Create a new transaction, and suspend the current transaction if one exists.
    
    

    大意就是:重新创建一个新的事务,如果当前存在事务,延缓当前的事务。这个延缓,或者说挂起,可能理解起来比较难,下面通过例子来分析:

    UserService.java

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insert() {
        UserEntity user = new UserEntity();
        user.setUsername("happyjava");
        user.setPassword("123456");
        userRepo.save(user);
    }
    
    

    这个insert方法的传播行为改为REQUIRES_NEW。

    UserService2.java

    @Transactional
    public void inserBatch() {
        UserEntity user = new UserEntity();
        user.setUsername("初次调用");
        user.setPassword("123456");
        userRepo.save(user);
        for (int i = 0; i < 10; i++) {
            if (i == 9) {
                throw new RuntimeException();
            }
            userService.insert();
        }
    }
    
    

    inserBatch拥有事务,然后后面循环调用的insert方法也有自己的事务。根据定义,inserBatch的事务会被延缓。具体表现就是:后面的10次循环的事务在每次循环结束之后都会提交自己的事务,而inserBatch的事务,要等循环方法走完之后再提交。但由于第10次循环会抛出异常,则inserBatch的事务会回滚,既数据库中不会存在:“初次调用”的记录:

    测试代码:

    @Test
    public void insertBatchTest() {
        userService2.inserBatch();
    }
    
    

    执行结果:

    这种情况,符合开始说的“不要循环嵌套事务方法”的说话,当然是否需要循环嵌套,还是要看业务逻辑的。

    NOT_SUPPORTED

    Execute non-transactionally, suspend the current transaction if one exists.
    
    

    以非事务的方式运行,如果当前存在事务,暂停当前的事务。这种方式与REQUIRES_NEW有所类似,但是NOT_SUPPORTED修饰的方法其本身是没有事务的。这里就不做代码演示了。

    NEVER

    以非事务的方式运行,如果当前存在事务,则抛出异常。

    @Transactional(propagation = Propagation.NEVER)
    public void insert() {
        UserEntity user = new UserEntity();
        user.setUsername("happyjava");
        user.setPassword("123456");
        userRepo.save(user);
    }
    
    
    @Transactional
    public void insertWithTx() {
        userService.insert();
    }
    
    

    执行结果:

    NESTED

    如果没有事务,就新建一个事务;如果有,就在当前事务中嵌套其他事务。

    这个也是理解起来比较费劲的一个行为。我们一步一步分析。

    外围方法没有事务:这种情况跟REQUIRED是一样的,会新建一个事务。

    外围方法如果存在事务:这种情况就会嵌套事务。所谓嵌套事务,大意就是,外围事务回滚,内嵌事务一定回滚,而内嵌事务可以单独回滚而不影响外围主事务和其他子事务。

    由于本人使用Spring Data JPA 进行的演示代码,使用嵌套事务会提示:

    org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
    
    

    搜索了下,hibernate似乎不支持这种事务传播方式。所以这里就不做演示了

    总结

    事务传播行为,在开发中可能不会特别的留意到它(更多时候,我们可能只是使用默认的方式),但是还是需要对其要有所理解。希望本篇文章能让大家明白Spring的7种事务传播行为。

  • 相关阅读:
    static生命周期
    VS2008 JS调试和Silverlight 后台代码调试 相互影响的问题。自己做实例证明
    思考记跳出以往的自己
    javascript setAttribute使用方法 查缺补漏
    克服浮躁,踏实工作,控制自我
    DES加密GUID+文件名称,关于DES加密后文件长度是否超过WINDOWS文件命名规定长度255个字节。
    阅读WPF揭秘前两章探索Silverlight运行的基本原理和RIA工作流程的密码()
    Silverlight运行原理经典问答。
    HTML 实用标签 (你不知道的HTML)
    ASP.NET项目整合 (Silverlight 和 WEB Service ) 过程及原理
  • 原文地址:https://www.cnblogs.com/happy4java/p/11206804.html
Copyright © 2020-2023  润新知