详解事务的7种传播行为

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

  事务传播行为是指多个拥有事务的方法在嵌套调用时的事务控制方式
比如XML中配置:XML:<tx:method name="..." propagation="REQUIRED"/>
注解配置:@Transactional(propagation=Propagation.REQUIRED)

二、事务传播行为的七种类型

三、Propagation.REQUIRED(默认)

  如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。

例子如下,后续各种情况将此例子展开讲解。

	......
	// ===========测试类
    @Transactional(propagation = Propagation.REQUIRED)
    public void testPropagationTrans() {
    	saveParent();
        saveChildren();
    }
    ......


    // ===========service实现类
    public void saveParent() {
        Stu stu = new Stu();
        stu.setName("parent");
        stu.setAge(19);
        stuMapper.insert(stu); // 数据库插入一条parent记录
    }
    // @Transactional(propagation = Propagation.REQUIRED)
    public void saveChildren() {
        saveChild1();
        int a = 1 / 0;
        saveChild2();
    }

    public void saveChild1() {
        Stu stu1 = new Stu();
        stu1.setName("child-1");
        stu1.setAge(11);
        stuMapper.insert(stu1); // 数据库插入一条child-1记录
    }
    public void saveChild2() {
        Stu stu2 = new Stu();
        stu2.setName("child-2");
        stu2.setAge(22);
        stuMapper.insert(stu2); // 数据库插入一条child-2记录
    }

结果是除以0错误java.lang.ArithmeticException: / by zero

这个例子中,有一个子方法saveParent()是没写事务注解的,用于对照比较有事务的子方法saveChildren()

情况一:
父方法testPropagationTrans()开启事务,子方法saveChildren()没有开启事务。

子方法saveChildren()saveParent()同处在父方法的事务中,saveChildren()除以0异常导致事务中对数据库的操作都回滚。所以没有记录插入。

在这里插入图片描述
情况二:
父方法testPropagationTrans()开启事务,只有saveChildren()开启事务

	......
	// ==========测试类
	// @Transactional(propagation = Propagation.REQUIRED)
    public void testPropagationTrans() {
    	saveParent();
        saveChildren();
    }
    ......
    
	// ===========service实现类
    public void saveParent() {
        Stu stu = new Stu();
        stu.setName("parent");
        stu.setAge(19);
        stuMapper.insert(stu); // 数据库插入一条parent记录
    }
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveChildren() {
        saveChild1();
        int a = 1 / 0;
        saveChild2();
    }

    public void saveChild1() {
        Stu stu1 = new Stu();
        stu1.setName("child-1");
        stu1.setAge(11);
        stuMapper.insert(stu1); // 数据库插入一条child-1记录
    }
    public void saveChild2() {
        Stu stu2 = new Stu();
        stu2.setName("child-2");
        stu2.setAge(22);
        stuMapper.insert(stu2); // 数据库插入一条child-2记录
    }

在这里插入图片描述
只有saveChildren()开启事务,saveChildren()发生异常就回滚该方法里面的数据库操作,所以数据库插入一条parent记录还是可以成功的。

情况三:
父方法testPropagationTrans()和子方法saveChildren()均开启事务。这里不拉出多余代码,文字解释+结果图即可。
在这里插入图片描述
saveChildren()发生异常,开始回滚,异常继续往上抛,父方法testPropagationTrans()也知道发生了异常,父方法里面所有数据库操作都回滚,所以saveParent()数据库插入的parent记录也回滚,最后数据库没有数据插入。


疑问:异常继续往上抛父方法才知道发生了异常,导致方法里所有数据库操作回滚,那么我把这个异常try-catch,是不是就只有saveChildren()回滚呢?那可不一定,跟着我继续来看。

四、将异常try-catch捕获,事务是否还会回滚?(这个总结很重要)

所有的讲解都是围绕开头第一段代码,依然是情况一二三,只不过此时多了try-catch

......
    public void testPropagationTrans() {
    	saveParent();
    	try {
            saveChildren();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
......

情况一:
父方法testPropagationTrans()开启事务,子方法saveChildren()没有开启事务。
在这里插入图片描述
saveChildren()产生的异常被捕获,没有继续上抛,父方法开启的事务不会回滚,故插入2条数据。

情况二:
父方法testPropagationTrans()开启事务,只有saveChildren()开启事务.
在这里插入图片描述
saveChildren()发生异常,回滚数据,parent记录插入不受影响。

情况三:
父方法testPropagationTrans()和子方法saveChildren()均开启事务。
在这里插入图片描述
结果发现即使saveChildren()产生的异常被try-catch,父事务也回滚。

综上:
  1. 父方法和子方法都开启事务,异常发生让子事务回滚,父事务一定回滚(子事务没将父事务挂起的情况下),不管是否被try-catch包裹,第四节的情况三就是最好的例子。
  2. 只要try-catch在内层,@Transactional在外层,异常被try-catch住,事务就不会回滚。
  3. 但是如果@Transactional在内层,try-catch在外层,那try-catch还没来得及处理异常就在@Transactional注解作用下回滚了,第四节的情况二就是最好的例子。

五、Propagation.SUPPORTS

  如果当前有事务,则使用事务,如果当前没有事务,就以非事务方式执行

情况一:
父方法testPropagationTrans()开启事务,子方法saveChildren()事务传播类型改为Propagation.SUPPORTS
在这里插入图片描述
外层父方法没有事务,子方法saveChildren()也就以非事务方式执行,这里不会回滚,所以有2条数据。

情况二:
父方法testPropagationTrans()开启事务,传播类型为Propagation.REQUIRED,子方法saveChildren()事务传播类型改为Propagation.SUPPORTS
在这里插入图片描述
子方法saveChildren()当前父方法开启了事务,故使用事务,saveChildren()发生异常回滚,这里子事务没将父事务挂起,子事务回滚,父事务一定回滚,正好验证了前面说过的结论,所以这里没有记录。

六、Propagation.MANDATORY

  支持当前的事务,如果当前没有事务,就抛出异常。
情况一:
父方法testPropagationTrans()开启事务,子方法saveChildren()事务传播类型改为Propagation.MANDATORY
在这里插入图片描述
此时saveChildren()直接抛出异常,和之前的除以0异常不同, No existing transaction found for transaction marked with propagation 'mandatory',外层没有事务,就会抛异常。
在这里插入图片描述
异常导致saveChildren()方法没执行,没有child-1记录插入,但是数据库会插入parent记录,因为父方法没开启事务,不影响saveParent()的执行。

情况二:
父方法testPropagationTrans()开启事务,传播类型为Propagation.REQUIRED,子方法saveChildren()事务传播类型改为Propagation.MANDATORY
在这里插入图片描述
子方法saveChildren()支持父事务,故使用事务,saveChildren()发生异常回滚,这里子事务没将父事务挂起,子事务回滚,父事务一定回滚,正好验证了前面说过的结论,所以这里没有记录。

七、Propagation.REQUIRES_NEW

  新建事务,如果当前存在事务,把当前事务挂起。

这里说得多一些,所以代码整个给出来

	......
	// ==========测试类
    @Transactional(propagation = Propagation.REQUIRED)
    public void testPropagationTrans() {
    	saveParent();
        saveChildren();
    }
    ......

	// ===========service实现类

    public void saveParent() {
        Stu stu = new Stu();
        stu.setName("parent");
        stu.setAge(19);
        stuMapper.insert(stu); // 数据库插入一条parent记录
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveChildren() {
        saveChild1();
        int a = 1 / 0;
        saveChild2();
    }

    public void saveChild1() {
        Stu stu1 = new Stu();
        stu1.setName("child-1");
        stu1.setAge(11);
        stuMapper.insert(stu1); // 数据库插入一条child-1记录
    }
    public void saveChild2() {
        Stu stu2 = new Stu();
        stu2.setName("child-2");
        stu2.setAge(22);
        stuMapper.insert(stu2); // 数据库插入一条child-2记录
    }

在这里插入图片描述

有人会疑问了,saveChildren() 都新建事务将原事务挂起了,为什么子事务回滚父事务也会回滚?原来的事务都挂起了,子事务回滚和父事务回滚没有必然联系了。 其实这里原因是因为异常抛给了父事务,导致回滚。我们可以将saveChildren()try-catch包裹,就会发现,testPropagationTrans()所在的事务并没有回滚,因为parent记录插入成功了。
在这里插入图片描述
为了不混淆,我们将异常提出来

	......
	// ==========测试类
    @Transactional(propagation = Propagation.REQUIRED)
    public void testPropagationTrans() {
    	saveParent();
        saveChildren();
        int a = 1 / 0;  // =======将saveChildren的异常提到外面=============
    }
    ......

	// ===========service实现类
    public void saveParent() {
        Stu stu = new Stu();
        stu.setName("parent");
        stu.setAge(19);
        stuMapper.insert(stu); // 数据库插入一条parent记录
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveChildren() {
        saveChild1();
        saveChild2();
    }

    public void saveChild1() {
        Stu stu1 = new Stu();
        stu1.setName("child-1");
        stu1.setAge(11);
        stuMapper.insert(stu1); // 数据库插入一条child-1记录
    }
    public void saveChild2() {
        Stu stu2 = new Stu();
        stu2.setName("child-2");
        stu2.setAge(22);
        stuMapper.insert(stu2); // 数据库插入一条child-2记录
    }

在这里插入图片描述
两条child记录插入成功,而parent记录没插入,说明子事务执行完就commit了,父事务所有相关数据库的操作全部回滚,parent记录的插入被撤销,但这也影响不了已经commit的子事务。

举个形象的例子,小区的人都用小区网,我觉得小区网太慢了,自己拉了一根光纤,某天因施工小区网断掉了,大家都受到影响,但是我自己的网不受影响。

八、Propagation.NOT_SUPPORTED

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

	......
	// ==========测试类
    @Transactional(propagation = Propagation.REQUIRED)
    public void testPropagationTrans() {
    	saveParent();
        saveChildren();
    }
    ......

	// ===========service实现类
    public void saveParent() {
        Stu stu = new Stu();
        stu.setName("parent");
        stu.setAge(19);
        stuMapper.insert(stu); // 数据库插入一条parent记录
    }
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void saveChildren() {
        saveChild1();
        int a = 1 / 0;  // ===============这里有异常===========
        saveChild2();
    }

    public void saveChild1() {
        Stu stu1 = new Stu();
        stu1.setName("child-1");
        stu1.setAge(11);
        stuMapper.insert(stu1); // 数据库插入一条child-1记录
    }
    public void saveChild2() {
        Stu stu2 = new Stu();
        stu2.setName("child-2");
        stu2.setAge(22);
        stuMapper.insert(stu2); // 数据库插入一条child-2记录
    }

在这里插入图片描述
结果是child-1记录插入成功,parent没有插入成功。原因是saveChildren()以非事务方式执行,并将父事务挂起,执行之后发生异常,但是child-1插入成功,异常抛到父事务后数据库操作全部回滚,所以parent没有插入成功。

这种情况主要用在查询操作,比如在类上开启了事务,类里面的所有方法都开启了事务,插入删除更新是需要的,但是查询就没必要了,所以可以用这个Propagation.NOT_SUPPORTED将查询方法以非事务的方式执行。

九、Propagation.NEVER

  以非事务方式执行,如果当前存在事务,则抛出异常。与Propagation.MANDATORY正好相反。

	......
	// ==========测试类
    @Transactional(propagation = Propagation.REQUIRED)
    public void testPropagationTrans() {
    	saveParent();
        saveChildren();
    }
    ......


	// ===========service实现类
    public void saveParent() {
        Stu stu = new Stu();
        stu.setName("parent");
        stu.setAge(19);
        stuMapper.insert(stu); // 数据库插入一条parent记录
    }
    @Transactional(propagation = Propagation.NEVER)
    public void saveChildren() {
        saveChild1();
        int a = 1 / 0;  // ===============这里有异常===========
        saveChild2();
    }

    public void saveChild1() {
        Stu stu1 = new Stu();
        stu1.setName("child-1");
        stu1.setAge(11);
        stuMapper.insert(stu1); // 数据库插入一条child-1记录
    }
    public void saveChild2() {
        Stu stu2 = new Stu();
        stu2.setName("child-2");
        stu2.setAge(22);
        stuMapper.insert(stu2); // 数据库插入一条child-2记录
    }

结果异常Existing transaction found for transaction marked with propagation 'never'
在这里插入图片描述
在这里插入图片描述
数据库也没有记录,因为parent记录插入后,收到saveChildren()的异常导致父事务回滚,而saveChildren()因为注解检查到异常,内容就没执行。如果去掉testPropagationTrans()事务,那么执行如下,方法都是以非事务方式执行。
在这里插入图片描述

十、Propagation.NESTED

如果当前有事务,则开启子事务(嵌套事务),嵌套事务是独立提交或者回滚,如果当前没有事务,就新建事务运行。

运行结果和原因与Propagation.REQUIRED一模一样。几乎没区别,这种情况用得少。


欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

砖业洋__

你的鼓励是我持续写作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值