• 手把手带你实战下Spring的七种事务传播行为


    本文介绍Spring的七种事务传播行为并通过代码演示下。

    一、什么是事务传播行为?

    事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。

    例如:methodA方法调用methodB方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

    二、事务的7种传播行为

    Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。事务传播行为是Spring框架独有的事务增强特性。这是Spring为我们提供的强大的工具箱,使用事务传播行为可以为我们的开发工作提供许多便利。

    7种事务传播行为如下:

    1.PROPAGATION_REQUIRED

    如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,这是最常见的选择,也是Spring默认的事务传播行为。

    2.PROPAGATION_SUPPORTS

    支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

    3.PROPAGATION_MANDATORY

    支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

    4.PROPAGATION_REQUIRES_NEW

    创建新事务,无论当前存不存在事务,都创建新事务。

    5.PROPAGATION_NOT_SUPPORTED

    以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    6.PROPAGATION_NEVER

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

    7.PROPAGATION_NESTED

    如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

    其实这7中我也没看懂,不过不急,咱们接下来直接看效果。

    三、7种传播行为实战

    演示前先建两个表,用户表和用户角色表,一开始两个表里没有数据。

    需要注意下,为了数据更直观,每次执行代码时 先清空下user和user_role表的数据。

    user表:

    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      `password` varchar(255) DEFAULT NULL,
      `sex` int(11) DEFAULT NULL,
      `des` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    user_role表:

    CREATE TABLE `user_role` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` int(11) DEFAULT NULL,
      `role_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    1.PROPAGATION_REQUIRED测试

    如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,这是最常见的选择,也是Spring默认的事务传播行为。

    场景一:

    此场景外围方法没有开启事务。

    1.验证方法

    两个实现类UserServiceImpl和UserRoleServiceImpl制定事物传播行为propagation=Propagation.REQUIRED,然后在测试方法中同时调用两个方法并在调用结束后抛出异常。

    2.主要代码

    外层调用方法代码:

    /**
         * 测试 PROPAGATION_REQUIRED
         *
         * @Author: java_suisui
         */
        @Test
        void test_PROPAGATION_REQUIRED() {
            // 增加用户表
            User user = new User();
            user.setName("Java碎碎念");
            user.setPassword("123456");
            userService.add(user);
            // 增加用户角色表
            UserRole userRole = new UserRole();
            userRole.setUserId(user.getId());
            userRole.setRoleId(200);
            userRoleService.add(userRole);
            //抛异常
            throw new RuntimeException();
        }
    

    UserServiceImpl代码:

    /**
         * 增加用户
         */
        @Transactional(propagation = Propagation.REQUIRED)
        @Override
        public int add(User user) {
            return userMapper.add(user);
        }
    

    UserRoleServiceImpl代码:

        /**
         * 增加用户角色
         */
        @Transactional(propagation = Propagation.REQUIRED)
        @Override
        public int add(UserRole userRole) {
            return userRoleMapper.add(userRole);
        }
    

    3.代码执行后数据库截图

    两张表数据都新增成功,截图如下:

    新增成功截图

    4.结果分析

    外围方法未开启事务,插入用户表和用户角色表的方法在自己的事务中独立运行,外围方法异常不影响内部插入,所以两条记录都新增成功。

    场景二:

    此场景外围方法开启事务。

    1.主要代码

    测试方法代码如下:

    /**
         * 测试 PROPAGATION_REQUIRED
         *
         * @Author: java_suisui
         */
        @Transactional
        @Test
        void test_PROPAGATION_REQUIRED() {
            // 增加用户表
            User user = new User();
            user.setName("Java碎碎念");
            user.setPassword("123456");
            userService.add(user);
            // 增加用户角色表
            UserRole userRole = new UserRole();
            userRole.setUserId(user.getId());
            userRole.setRoleId(200);
            userRoleService.add(userRole);
            //抛异常
            throw new RuntimeException();
        }
    

    2.代码执行后数据库截图

    两张表数据都为空,截图如下:

    数据为空截图

    3.结果分析

    外围方法开启事务,内部方法加入外围方法事务,外围方法回滚,内部方法也要回滚,所以两个记录都插入失败。

    结论:以上结果证明在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所以Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

    2.PROPAGATION_SUPPORTS测试

    支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

    场景一:

    此场景外围方法没有开启事务。

    1.验证方法

    两个实现类UserServiceImpl和UserRoleServiceImpl制定事物传播行为propagation=Propagation.SUPPORTS,然后在测试方法中同时调用两个方法并在调用结束后抛出异常。

    2.主要代码

    外层调用方法代码:

        /**
         * 测试 PROPAGATION_SUPPORTS
         *
         * @Author: java_suisui
         */
        @Test
        void test_PROPAGATION_SUPPORTS() {
            // 增加用户表
            User user = new User();
            user.setName("Java碎碎念");
            user.setPassword("123456");
            userService.add(user);
            // 增加用户角色表
            UserRole userRole = new UserRole();
            userRole.setUserId(user.getId());
            userRole.setRoleId(200);
            userRoleService.add(userRole);
            //抛异常
            throw new RuntimeException();
        }
    

    UserServiceImpl代码:

    /**
         * 增加用户
         */
        @Transactional(propagation = Propagation.SUPPORTS)
        @Override
        public int add(User user) {
            return userMapper.add(user);
        }
    

    UserRoleServiceImpl代码:

        /**
         * 增加用户角色
         */
        @Transactional(propagation = Propagation.SUPPORTS)
        @Override
        public int add(UserRole userRole) {
            return userRoleMapper.add(userRole);
        }
    

    3.代码执行后数据库截图

    两张表数据都新增成功,截图如下:

    新增成功截图

    4.结果分析

    外围方法未开启事务,插入用户表和用户角色表的方法以非事务的方式独立运行,外围方法异常不影响内部插入,所以两条记录都新增成功。

    场景二:

    此场景外围方法开启事务。

    1.主要代码

    test_PROPAGATION_SUPPORTS方法添加注解@Transactional即可。

    2.代码执行后数据库截图

    两张表数据都为空,截图如下:

    数据为空截图

    3.结果分析

    外围方法开启事务,内部方法加入外围方法事务,外围方法回滚,内部方法也要回滚,所以两个记录都插入失败。

    结论:以上结果证明在外围方法开启事务的情况下Propagation.SUPPORTS修饰的内部方法会加入到外围方法的事务中,所以Propagation.SUPPORTS修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

    3.PROPAGATION_MANDATORY测试

    支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

    通过上面的测试,“支持当前事务,如果当前存在事务,就加入该事务”,这句话已经验证了,外层添加@Transactional注解后两条记录都新增失败,所以这个传播行为只测试下外层没有开始事务的场景。

    场景一:

    此场景外围方法没有开启事务。

    1.验证方法

    两个实现类UserServiceImpl和UserRoleServiceImpl制定事物传播行为propagation = Propagation.MANDATORY,主要代码如下。

    2.主要代码

    外层调用方法代码:

        /**
         * 测试 PROPAGATION_MANDATORY
         *
         * @Author: java_suisui
         */
        @Test
        void test_PROPAGATION_MANDATORY() {
            // 增加用户表
            User user = new User();
            user.setName("Java碎碎念");
            user.setPassword("123456");
            userService.add(user);
            // 增加用户角色表
            UserRole userRole = new UserRole();
            userRole.setUserId(user.getId());
            userRole.setRoleId(200);
            userRoleService.add(userRole);
            //抛异常
            throw new RuntimeException();
        }
    

    UserServiceImpl代码:

    /**
         * 增加用户
         */
        @Transactional(propagation = Propagation.MANDATORY)
        @Override
        public int add(User user) {
            return userMapper.add(user);
        }
    

    UserRoleServiceImpl代码:

        /**
         * 增加用户角色
         */
        @Transactional(propagation = Propagation.MANDATORY)
        @Override
        public int add(UserRole userRole) {
            return userRoleMapper.add(userRole);
        }
    

    3.代码执行后数据库截图

    两张表数据都为空,截图如下:

    数据为空截图

    4.结果分析

    运行日志如下,可以发现在调用userService.add()时候已经报错了,所以两个表都没有新增数据,验证了“如果当前不存在事务,就抛出异常”。

    at com.example.springboot.mybatisannotation.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$50090f18.add(<generated>)
    	at com.example.springboot.mybatisannotation.SpringBootMybatisAnnotationApplicationTests.test_PROPAGATION_MANDATORY(SpringBootMybatisAnnotationApplicationTests.java:78)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    

    4.PROPAGATION_REQUIRES_NEW测试

    创建新事务,无论当前存不存在事务,都创建新事务。

    这种情况每次都创建事务,所以我们验证一种情况即可。

    场景一:

    此场景外围方法开启事务。

    1.验证方法

    两个实现类UserServiceImpl和UserRoleServiceImpl制定事物传播行为propagation = Propagation.REQUIRES_NEW,主要代码如下。

    2.主要代码

    外层调用方法代码:

        /**
         * 测试 REQUIRES_NEW
         *
         * @Author: java_suisui
         */
        @Test
        @Transactional
        void test_REQUIRES_NEW() {
            // 增加用户表
            User user = new User();
            user.setName("Java碎碎念");
            user.setPassword("123456");
            userService.add(user);
            // 增加用户角色表
            UserRole userRole = new UserRole();
            userRole.setUserId(user.getId());
            userRole.setRoleId(200);
            userRoleService.add(userRole);
            //抛异常
            throw new RuntimeException();
        }
    

    UserServiceImpl代码:

    /**
         * 增加用户
         */
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        @Override
        public int add(User user) {
            return userMapper.add(user);
        }
    

    UserRoleServiceImpl代码:

        /**
         * 增加用户角色
         */
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        @Override
        public int add(UserRole userRole) {
            return userRoleMapper.add(userRole);
        }
    

    3.代码执行后数据库截图

    两张表数据都新增成功,截图如下:

    新增成功截图

    4.结果分析

    无论当前存不存在事务,都创建新事务,所以两个数据新增成功。

    5.PROPAGATION_NOT_SUPPORTED测试

    以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    场景一:

    此场景外围方法不开启事务。

    1.验证方法

    两个实现类UserServiceImpl和UserRoleServiceImpl制定事物传播行为propagation = Propagation.NOT_SUPPORTED,主要代码如下。

    2.主要代码

    外层调用方法代码:

        /**
         * 测试 PROPAGATION_NOT_SUPPORTED
         *
         * @Author: java_suisui
         */
        @Test
        void test_PROPAGATION_NOT_SUPPORTED() {
            // 增加用户表
            User user = new User();
            user.setName("Java碎碎念");
            user.setPassword("123456");
            userService.add(user);
            // 增加用户角色表
            UserRole userRole = new UserRole();
            userRole.setUserId(user.getId());
            userRole.setRoleId(200);
            userRoleService.add(userRole);
            //抛异常
            throw new RuntimeException();
        }
    

    UserServiceImpl代码:

    /**
         * 增加用户
         */
        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        @Override
        public int add(User user) {
            return userMapper.add(user);
        }
    

    UserRoleServiceImpl代码:

        /**
         * 增加用户角色
         */
        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        @Override
        public int add(UserRole userRole) {
            return userRoleMapper.add(userRole);
        }
    

    3.代码执行后数据库截图

    两张表数据都新增成功,截图如下:

    新增成功截图

    4.结果分析

    以非事务方式执行,所以两个数据新增成功。

    场景二:

    此场景外围方法开启事务。

    1.主要代码

    test_PROPAGATION_NOT_SUPPORTED方法添加注解@Transactional即可。

    2.代码执行后数据库截图

    两张表数据都新增成功,截图如下:

    新增成功截图

    3.结果分析

    如果当前存在事务,就把当前事务挂起,相当于以非事务方式执行,所以两个数据新增成功。

    6.PROPAGATION_NEVER测试

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

    上面已经有类似情况,外层没有事务会以非事务的方式运行,两个表新增成功;有事务则抛出异常,两个表都都没有新增数据。

    7.PROPAGATION_NESTED测试

    如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

    上面已经有类似情况,外层没有事务会以REQUIRED属性的方式运行,两个表新增成功;有事务但是用的是一个事务,方法最后抛出了异常导致回滚,两个表都都没有新增数据。

    到此Spring的7种事务传播行为已经全部介绍完成了,有问题欢迎留言沟通哦!

    完整源码地址: https://github.com/suisui2019/springboot-study

    推荐阅读

    1.SpringBoot系列-整合Mybatis(注解方式)
    2.SpringBoot系列-整合Mybatis(XML配置方式)
    3.Java中打印日志,这4点很重要!
    4.SpringBoot集成JWT实现权限认证
    5.一分钟带你了解JWT认证!


    限时领取免费Java相关资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo/Kafka、Hadoop、Hbase、Flink等高并发分布式、大数据、机器学习等技术。
    关注下方公众号即可免费领取:

    Java碎碎念公众号

  • 相关阅读:
    关于echarts图表在tab页中width:100%失效的问题
    easyui
    小程序中点击事件传参
    微信小程序实现滚动分页加载更多
    使用jquery如何获取现在时间、并且格式化
    只需两步获取任何微信小程序源码
    怎样修改已经审核通过发布成功的微信小程序
    小程序开发swiper如何实现点击图片自定义跳转
    微信小程序如何提交审核并发布?发布问题:小程序只支持https访问
    小程序填坑之路—读取用户信息、缓存其数据、读取其数据
  • 原文地址:https://www.cnblogs.com/haha12/p/11855001.html
Copyright © 2020-2023  润新知