在两个使用 Transaction 注解的 ServiceA 和 ServiceB 中,ServiceA 引入了 ServiceB 的方法用于更新数据。当 ServiceA 中捕捉到 ServiceB 中有异常时,回滚动作正常执行,但当返回时则出现 org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 异常。
代码示例:
ServiceA
ServiceB
知识回顾:
Spring Boot 默认集成事务,所以无须手动开启,使用 @EnableTransactionManagement 注解进行事务管理。
通过构建必要的代码进行测试,发现报错结果与预期不同。关键信息变为了 Transaction silently rolled back because it has been marked as rollback-only,这里我们暂不讨论错误提示信息为何发生了改变,集中讨论报错原因。
根据基础知识,当我们在 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。
2、进入 processRollback()方法后,首先判断事物是否拥有 savepoint(回滚点),如果有,就回滚到设置的 savepoint;接着判断当前事务是否是新事务,因为这里是内外层事务,其实是同一个事务,所以判断结果为 false;但 hasTransaction()判断为 true,接着进入 if 方法体,isLocalRollbackOnly()为 false,isGlobalRollbackOnParticipationFailure()为 true,那么只能执行 doSetRollbackOnly()方法,此处只是补充打印一下日志;紧接着调用 isFailEarlyOnGlobalRollbackOnly()方法,这里主要是获取 failEarlyOnGlobalRollbackOnly 字段的值,默认情况下 failEarlyOnGlobalRollbackOnly 开关是关闭的,这个开关的作用是如果开启了程序则会尽早抛出异常。最终 unexpectedRollback 字段仍为 false,所以没有抛出 Transaction rolled back because it has been marked as rollback-only 异常。
3、内层事务方法调用结束后,回到外层方法,在事务提交时,即执行 commit()方法,实际上执行的是 processCommit()方法。该方法中的逻辑和 processRollback()方法有些重叠,此时判断当前事务是新事务,所以 unexpectedRollback 就被赋值为 true,最终抛出 Transaction silently rolled back because it has been marked as rollback-only 异常。
通过分析,我们了解到自定义代码时为何只能得到 Transaction silently rolled back because it has been marked as rollback-only 异常,而在项目代码中确实遇到了 Transaction rolled back because it has been marked as rollback-only 异常。