处理事务回滚
Posted shinl00
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了处理事务回滚相关的知识,希望对你有一定的参考价值。
处理事务回滚
参考文献:《极客时间-Java业务开发常见错误100例》https://time.geekbang.org/column/article/213295
大多数Spring Boot项目只需要在方法上标记@Transactional注解,即可一键开启方法的事务性配置。
保证事务生效
-
务必确认调用 @Transactional 注解标记的方法是 public 的
除非特殊配置(比如使用 AspectJ 静态织入实现 AOP),否则只有定义在 public 方法上的 @Transactional 才能生效。原因是,Spring 默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到,Spring 自然也无法动态增强事务处理逻辑。
如果要针对 private 方法启用事务,动态代理方式的 AOP 不可行,需要使用静态织入方式的 AOP,也就是在编译期间织入事务增强代码,可以配置 Spring 框架使用 AspectJ 来实现 AOP。你能否参阅 Spring 的文档“Using @Transactional with AspectJ”试试呢?注意:AspectJ 配合 lombok 使用,还可能会踩一些坑。 https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative-aspectj
-
通过 Spring 注入的 Bean 进行调用的。使用 try…catch…来包裹标记了 @Transactional 注解的方法,必须通过代理过的类从外部调用目标方法才能生效。
Spring 通过 AOP 技术对方法进行增强,要调用增强过的方法必然是调用代理后的对象。 - CGLIB 通过继承方式实现代理类,private 方法在子类不可见,自然也就无法进行事务增强; - this 指针代表对象自己,Spring 不可能注入 this,所以通过 this 访问方法必然不是代理。
正确使用:在 Service 内部注入自己调用自己的 方法() 可以正确实现事务,但更合理的实现方式是,让 Controller 直接调用之前定义的 Service 的 方法() ,因为注入自己调用自己很奇怪,也不符合分层实现的规范
保证回滚
默认情况下,出现 RuntimeException(非受检异常)或 Error 的时候,Spring 才会回滚事务。
修复方式:
-
自己捕捉,手动设置让当前事务处于回滚状态
@Transactionalpublic void createUserRight1(String name) { try { userRepository.save(new UserEntity(name)); throw new RuntimeException("error"); } catch (Exception ex) { log.error("create user failed", ex); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
-
在注解中声明
rollbackFor
,期望遇到所有的 Exception 都回滚事务(来突破默认不回滚受检异常的限制)@Transactional(rollbackFor = Exception.class)
子方法回滚,主方法不回滚
虽然捕获了子方法的异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚。
解决方法:想办法让子逻辑在独立事务中运行
-
为注解加上
propagation = Propagation.REQUIRES_NEW
来设置 REQUIRES_NEW 方式的事务传播策略,也就是执行到这个方法时需要开启新的事务,并挂起当前事务。@Transactional(propagation = Propagation.REQUIRES_NEW)
主方法没什么变化,同样需要try catch捕获异常,防止异常漏出去导致主事务回滚
以上是关于处理事务回滚的主要内容,如果未能解决你的问题,请参考以下文章
关于调用方有事务,被调用的SP中也有事务,在嵌套SP中回滚代码的报错处理,好文推荐
SpringBoot设置 @Transactional ,并在异常处理中调用setRollbackOnly()事务不回滚