Spring事务嵌套引发的问题
事务回滚
构建必要的代码如下:
//UserController.java @GetMapping("/users") public List<User> queryAll() { return userApplication.findAll(); } //UserApplication.java @Service @Transactional public class UserApplication { @Autowired private UserService userService; @Autowired private UserRepository userRepository; public List<User> findAll() { try { userService.query("hresh2"); } catch (Exception e) { } return userRepository.findAll(); } } //UserServiceImpl.java @Override @Transactional public UserResponse query(String name) { if (!name.equals("hresh")) { throw new IllegalArgumentException("name is forbidden"); } return null; } public void validateName(String name) { if (!name.equals("hresh")) { throw new IllegalArgumentException("name is forbidden"); } }
我们利用 postman 来进行测试,发现报错结果和预期不大一样:
关键信息变为了 Transaction silently rolled back because it has been marked as rollback-only
,这里我们暂不讨论错误提示信息为何发生了改变,先集中讨论报错原因。
根据基础知识中介绍的@Transactional 的作用范围和传播机制可知,当我们在 Service 文件类上添加 @Transactional 时,该注解对该类中所有的 public 方法都生效,且传播机制默认为 PROPAGATION_REQUIRED,即如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
在这种情况下,外层事务(UserApplication)和内层事务(UserServiceImpl)就是一个事务,任何一个出现异常,都会在 findAll()执行完毕后回滚。如果内层事务抛出异常 IllegalArgumentException(没有catch,继续向外层抛出),在内层事务结束时,Spring 会把内层事务标记为“rollback-only”;这时外层事务发现了异常 IllegalArgumentException,如果外层事务 catch了异常并处理掉,那么外层事务A的方法会继续执行代码,直到外层事务也结束时,这时外层事务想 commit,因为正常结束没有向外抛异常,但是内外层事务是同一个事务,事务已经被内层方法标记为“rollback-only”,需要回滚,无法 commit,这时 Spring 就会抛出org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
,意思是“事务静默回滚,因为它已被标记为仅回滚”。
具体分析详见:https://www.hreshhao.com/transaction-rollback-and-invalidation/#title-8
解决方案:https://www.dounaite.com/article/62554f8eae87fd3f795bc4bf.html