使用@Transactional遇见的问题,子方法回滚如何让父方法不回滚

老徐聊技术 2019-12-16 14:49:50

项目开发中不知道大家碰没碰见过这种情况。在方法1中调用方法2(2个方法都是有事务的)。方法2出现异常要回滚,但是方法1不能回滚,并且要记录方法2抛出的异常到数据库中。看一个例子:

使用@Transactional遇见的问题,子方法回滚如何让父方法不回滚_第1张图片

例子1

使用@Transactional遇见的问题,子方法回滚如何让父方法不回滚

例子1结果

从代码中可以看出,我都加上了Transactional,但是没有一个事务生效的。2个方法的事务都没起作用。先要知道Transaction的底层。

注解@Transaction的底层实现是Spring AOP技术,而Spring AOP技术使用的是动态代理。这就意味着对于静态(static)方法和非public方法,注解@Transactional是失效的。还有一个更为隐秘的,而且在使用过程中极其容易犯错误的——自调用(也就是我现在代码中出现的问题,不明白动态代理的可以上网百度下)。

使用@Transactional遇见的问题,子方法回滚如何让父方法不回滚_第2张图片

例子2

修改为上图所示,事务都好使了,但是不符合我的需求。方法1回滚了。如何才能不让方法1回滚。这里就要从事务的传播行为说起了。

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。如下几个表示传播行为的常量:

  • PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • PROPAGATION_REQUIRES_NEW:创建一个新的事务。如果当前存在事务,则把当前事务挂起。
  • PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。

由于Springboot中默认是PROPAGATION_REQUIRED,也就说insertData2会使用insertData的事务,这样当insertData2抛出异常,被insertData的事务捕获,就会认为是insertData整个方法异常了,就会事务回滚,也就造成了2个方法都没有插入进去数据。

所以这里需要将insertData2的事务传播行为修改下,让它不去使用insertData的事务,而是自己创建一个。

使用@Transactional遇见的问题,子方法回滚如何让父方法不回滚_第3张图片

insertData2方法

使用@Transactional遇见的问题,子方法回滚如何让父方法不回滚_第4张图片

再次执行结果

insertData2回滚,没有插入id为101的数据,insertData正常执行。那么这里是否有人会质疑我在事务中使用了try catch呢。正常在事务中都是建议直接抛出的,不然事务会捕获不到异常的。但是在insertData方法中,如果不捕获insertData2的异常,就会直接抛出一个java.lang.ArithmeticException: / by zero异常。insertData方法的事务是会捕获到这个异常的,就会认为insertData出现错误了(而事实是insertData2出现错误),就会回滚insertData数据。就会造成insertData插入不进去数据。

总结:

  1. 对于静态(static)方法和非public方法,自调用,注解@Transactional是失效的。
  2. spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(spring默认取决与是否抛出runtime异常),如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。
  3. 一般不需要在业务方法中catch异常,如果非要catch,在做完你想做的工作后一定要抛出runtime exception,否则spring会将你的操作commit, 这样就会产生脏数据。

你可能感兴趣的:(使用@Transactional遇见的问题,子方法回滚如何让父方法不回滚)