JPA @Transactional(REQUIRES_NEW)捕获异常仍回滚问题排查
核心问题拆解
你遇到的情况是:在UserService.createUser执行userRepository.save(user)时,JPA审计回调触发BaseEntityAuditAware获取用户信息,该方法标记了@Transactional(propagation = Propagation.REQUIRES_NEW)且捕获所有异常,但父事务依然因UnexpectedRollbackException回滚;提前判断callerId为null跳过异常逻辑后操作正常,即使给父事务加noRollBackFor = Exception.class也无效。
具体原因解析
REQUIRES_NEW事务的回滚标记会向上传递
虽然你在BaseEntityAuditAware的方法里捕获了所有异常,但如果这个新开启的REQUIRES_NEW事务在执行过程中,内部出现了导致事务被标记为回滚的操作(比如JPA持久化上下文抛出未被彻底处理的RuntimeException,或者代码隐性调用了TransactionStatus.setRollbackOnly()),即使异常没透出到父方法,这个子事务的回滚状态依然会被Spring事务管理器记录。当子事务结束后,Spring会检查其状态,一旦发现子事务被标记回滚,就会强制父事务也回滚,并抛出UnexpectedRollbackException——这是Spring事务的保护机制,防止父事务提交依赖于已失败子事务的数据。JPA审计回调与事务的交互冲突
BaseEntityAuditAware是在JPA的审计回调(如prePersist)中执行的,这个回调本身依附于当前持久化上下文的事务边界。即使你给AuditAware方法加了REQUIRES_NEW,JPA回调的特殊执行时机可能导致子事务的状态被同步到父事务的上下文里,使得子事务的回滚标记直接影响父事务。而当你提前判断callerId为null跳过异常逻辑时,子事务没有被标记回滚,父事务自然能正常提交。noRollbackFor对UnexpectedRollbackException无效
noRollbackFor = Exception.class只能控制业务异常是否触发事务回滚,但UnexpectedRollbackException是Spring事务管理器在事务协调阶段主动抛出的事务管理异常,不属于业务异常范畴,不在noRollbackFor的生效范围内。它的作用就是强制终止父事务,所以配置这个属性无法阻止回滚。
解决思路参考
- 排查
BaseEntityAuditAware的REQUIRES_NEW事务内部,是否存在隐性触发回滚的操作:比如JPA操作失败、未彻底处理的RuntimeException(注意:即使你捕获了Exception,有些RuntimeException可能在事务边界被Spring捕获并标记回滚,需确保所有异常都在方法内部处理,不透出到事务管理器)。 - 避免在JPA审计回调中使用
REQUIRES_NEW事务:审计回调本身依赖当前持久化上下文,强行开启新事务容易导致上下文不一致,建议提前在父事务中准备好审计所需的用户信息,或者将用户信息获取逻辑移到事务外执行。
内容的提问来源于stack exchange,提问作者sju3358 dev

