前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:聊聊spring的七种事务传播行为?

面试官:聊聊spring的七种事务传播行为?

作者头像
java小杰要加油
发布2021-05-13 16:47:29
5310
发布2021-05-13 16:47:29
举报

spring的七种事务传播行为

❝以下事务传播属性都是打在B方法上的事务注解 ❞

  • 「Propagation.REQUIRED:」 spring默认的事务传播行为,A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己开启一个新事务
  • 「Propagation.SUPPORTS:」 A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己使用非事务方式执行
  • 「Propagation.MANDATORY:」 只能在存在事务的方法中被调用,A方法调用B方法,如果A方法没事务,则B方法会抛出异常
  • 「Propagation.REQUIRES_NEW:」 A方法调用B方法,如果A方法有事务,则B方法把A方法的事务挂起,B方法自己重新开启一个新事务
  • 「Propagation.NOT_SUPPORTED:」 A方法调用B方法,如果A方法有事务,则B方法挂起A方法中的事务中,否则B方法自己使用非事务方式执行
  • 「Propagation.NEVER:」 不支持事务,A方法调用B方法,如果A方法有事务,则B方法会抛出异常
  • 「Propagation.NESTED:」「Propagation.REQUIRED」,不过此传播属性还可以,「保存状态节点,从而避免所有嵌套事务都回滚」

我们看完了每个传播属性的一些解释,脑子里应该是还是蒙蒙的,下面来看下真正的代码

实战

Propagation.REQUIRED

  • spring 默认的事务传播属性,A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己开启一个新事务

A接口

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
        // 调用B接口的insertTest方法事务方法
        BTestService.insertTest(test);
        return i;
    }

我们再来看下B接口

 @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer insertTest(Test updateVO) {
        System.out.println("B insertTest方法");
        int i = testMapper.insert(updateVO);
        //  抛出异常
        int a = 1 / 0 ;
        return i;
    }

假如说,我代码这么写的话,那么这个数据库里最终是会有什么数据呢?

@PostMapping("/update")
    public Object updateTest(@RequestBody Test updateVO){
        Integer flag = ATestService.updateTest(updateVO);
        return flag;
    }

原数据库内容

postman来一发看看

可以看到控制台的结果是这样的,他们共用一个事务(sqlSession是一样的)

此时数据库的内容也并没有发生变化,说明A,B接口都回滚了

❝这个时候就会出现一个常见的面试题:如果B方法抛出的异常被「A方法try catch」 捕获了,那么A方法的操作还会回滚吗? ❞

答案是:「会回滚」

来看下测试代码,我们在A方法中添加了捕获B方法抛出异常的代码

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
          try {
               // 调用insertTest方法事务方法
               BTestService.insertTest(test);
           }catch (Exception e){
               System.out.println("A方法补获了异常"+e.getMessage());
           }
        return i;
    }

再次来一发postman,控制台输出测试结果如下

我们看数据库数据也没有变

❝那么问题又来了,如果「A没有捕获,B方法自己捕获了异常」,那么事务还会回滚吗?答案是:「不会」

把B接口的代码改一下

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer insertTest(Test updateVO) {
        System.out.println("B insertTest方法");
        int i = 0;

        try {
             i = testMapper.insert(updateVO);
            //  抛出异常
            int a = 1 / 0 ;
        }catch (Exception e){
            System.out.println("B方法补获了异常"+e.getMessage());
        }

        return i;
    }

同时把A方法的捕获异常去掉

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
        // 调用insertTest方法事务方法
        BTestService.insertTest(test);
        return i;
    }

这个时候的结果是

数据库的数据是

由此可见,A和B两个接口都生效了都操作数据库了,都没有回滚

A方法捕获和B方法捕获有什么区别吗(指捕获异常)
  • 区别就是,A方法捕获异常的话,B方法的事务注解会感知到异常的发生,从而回滚;
  • 而B方法自己捕获了,那么B方法的事务注解就不会感知到异常了,所以不会回滚

❝只要理解了上面这个例子,我们以后各种异常/传播属性到底回滚不回滚就好分析啦! ❞

Propagation.SUPPORTS

  • A方法调用B方法,如果A方法有事务,则B方法加入到A方法中的事务中,否则B方法自己使用非事务方式执行

我们把B接口的事务传播属性换成 Propagation.SUPPORTS

@Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
    @Override
    public Integer insertTest(Test updateVO) {
        System.out.println("B insertTest方法");
        int i = testMapper.insert(updateVO);
        //  抛出异常
        int a = 1 / 0 ;
        return i;
    }

A方法

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
        // 调用insertTest方法事务方法
        BTestService.insertTest(test);
        return i;
    }

测试结果是

数据库的值也没有被改变 , 所以两个操作都被回滚了 那我们把A方法的事务注解去掉后再看一下

 @Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
        // 调用insertTest方法事务方法
        BTestService.insertTest(test);
        return i;
    }

测试结果是是

数据库的值是

由此可见,两个操作都没有被回滚,B方法是以非事务方式进行的操作

Propagation.MANDATORY

❝只能在存在事务的方法中被调用,A方法调用B方法,如果A方法没事务,则B方法会抛出异常 ❞

A接口如下

@Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
        // 调用insertTest方法事务方法
        BTestService.insertTest(test);
        return i;
    }

B接口如下

@Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
    @Override
    public Integer insertTest(Test updateVO) {
        System.out.println("B insertTest方法");
        int i = testMapper.insert(updateVO);
        //  抛出异常
        int a = 1 / 0 ;
        return i;
    }

数据库的值也没有变,由此可见,B方法的事务注解为 Propagation.MANDATORY 当A方法没事务时,则直接报错。

Propagation.REQUIRES_NEW

  • A方法调用B方法,如果A方法有事务,则B方法把A方法的事务挂起,B方法自己重新开启一个新事务

A方法

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
        // 调用insertTest方法事务方法
        BTestService.insertTest(test);
        return i;
    }

B方法

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    @Override
    public Integer insertTest(Test updateVO) {
        System.out.println("B insertTest方法");
        int i = testMapper.insert(updateVO);
        //  抛出异常
        int a = 1 / 0 ;
        return i;
    }

结果是

其中可以发现 两个接口的 DefaultSqlSession 不一样,那么就表明,这两个不是一个事务,所以就是,当A接口存在事务的时候,B接口将其挂起并且重新开启一个新的事务

  • B方法抛出了异常,那么A方法没有捕获的话,则A,B方法都会回滚
  • A方法捕获了异常,则A方法不回滚

「还是那句话,如果在方法内捕获了异常,则此方法上的事务注解就感知不到这个异常的存在了,那么此方法的操作就不会回滚!」

Propagation.NOT_SUPPORTED

❝A方法调用B方法,如果A方法有事务,则B方法挂起A方法中的事务中,否则B方法自己使用非事务方式执行 ❞

A接口

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
        // 调用insertTest方法事务方法
        BTestService.insertTest(test);
        return i;
    }

B接口

@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
    @Override
    public Integer insertTest(Test updateVO) {
        System.out.println("B insertTest方法");
        int i = testMapper.insert(updateVO);
        //  抛出异常
        int a = 1 / 0 ;
        return i;
    }

测试结果是

数据库的结果是

❝我们可以看到,B接口生效了,确实插入了一条数据,A接口没有生效,没有更改数据,这是因为,异常在B接口内抛出来了,由于B接口的事务传播行为是 Propagation.NOT_SUPPORTED 则会挂起A接口的事务,B接口以非事务情况操作(所以报错也不回滚),异常刨到了A接口内,A接口是有事务的,则会回滚,所以就没有更改数据 ❞

Propagation.NEVER

  • 不支持事务,A方法调用B方法,如果A方法有事务,则B方法会抛出异常

A接口

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
        // 调用insertTest方法事务方法
        BTestService.insertTest(test);
        return i;
    }

B接口

@Transactional(rollbackFor = Exception.class,propagation = Propagation.NEVER)
    @Override
    public Integer insertTest(Test updateVO) {
        System.out.println("B insertTest方法");
        int i = testMapper.insert(updateVO);
        //  抛出异常
        int a = 1 / 0 ;
        return i;
    }

结果是

数据库也没有被改变, 可见,当A接口有事务的情况下调用B接口,直接报错

Propagation.NESTED

  • 「Propagation.REQUIRED」,不过此传播属性还可以,「保存状态节点,从而避免所有嵌套事务都回滚」

A接口

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public Integer updateTest(Test updateVO) {
        System.out.println("A updateTest方法");
        int i = testMapper.updateByPrimaryKey(updateVO);
        Test test =new Test("小杰",24);
          try {
               // 调用insertTest方法事务方法
               BTestService.insertTest(test);
           }catch (Exception e){
               System.out.println("A方法补获了异常"+e.getMessage());
           }
        return i;
    }

B接口

@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    @Override
    public Integer insertTest(Test updateVO) {
        System.out.println("B insertTest方法");
        int i = testMapper.insert(updateVO);
        //  抛出异常
        int a = 1 / 0 ;
        return i;
    }

结果是

数据库的变化如下

A接口的操作没有回滚,B操作的回滚了,这就是因为“savePoint”安全点,在进行B接口操作时,当前的状态(A接口已经操作完了)被保存至安全点,B接口失败的话,回滚只会回滚到这个安全点

❝注:需要在A接口里try catch B接口的异常 ❞

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java小杰要加油 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • spring的七种事务传播行为
  • 实战
    • Propagation.REQUIRED
      • Propagation.SUPPORTS
        • Propagation.MANDATORY
          • Propagation.REQUIRES_NEW
            • Propagation.NOT_SUPPORTED
              • Propagation.NEVER
                • Propagation.NESTED
                相关产品与服务
                数据库
                云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档