Spring Retry是否能与@Transactional注解可靠协同?乐观锁场景分析
Spring Retry与@Transactional的协同工作问题
这个问题的核心确实在于Spring AOP代理的执行顺序,直接决定了乐观锁场景下重试是否能正常工作。我来拆解两种代理顺序的影响,以及怎么解决:
两种代理顺序的差异
1. 正确的顺序:Retry Proxy → Transaction Proxy → 实际DB代码
这种情况下,每次重试都会触发一个全新的事务:
- 当乐观锁冲突(比如版本号不匹配抛出
OptimisticLockingFailureException)时,当前事务会回滚,然后Retry代理会重新发起调用,开启新的事务去执行DB操作。 - 新事务会重新读取最新的版本号,这时候如果其他线程已经修改了数据,重试时就能基于最新状态尝试更新,乐观锁的逻辑能正常生效,重试才有实际意义。
2. 错误的顺序:Transaction Proxy → Retry Proxy → 实际DB代码
这种情况会导致重试完全失效:
- 整个重试流程都包裹在同一个事务里,第一次乐观锁冲突抛出异常后,Spring会把当前事务标记为“需要回滚”。
- 后续的重试操作虽然执行了,但由于事务已经处于回滚标记状态,不管重试是否成功,最终整个事务都会回滚。而且同一个事务内读取的版本号是快照,不会更新,重试时依然用旧版本号去更新,必然再次触发乐观锁失败,陷入无效循环。
如何控制代理顺序确保正常工作
你需要让Retry代理的优先级高于Transactional代理(也就是让Retry代理在外层),可以通过以下两种方式实现:
方式一:调整@EnableRetry的order属性
在配置类上开启Retry时,设置更小的order值(Spring AOP中order值越小,代理越先执行,也就是外层代理):
@Configuration @EnableRetry(order = 1) // 比Transactional的默认order(2147483647)小很多 public class RetryConfig { // 重试相关配置 }
方式二:调整@Transactional的order属性
如果需要更精细控制,也可以给@Transactional设置更大的order值,让Retry代理先执行:
@Service public class YourService { @Retryable(value = OptimisticLockingFailureException.class, maxAttempts = 3) @Transactional(order = 10) // 比Retry的order大,确保Retry在外层 public void updateDataWithOptimisticLock(Long id) { // 你的DB更新逻辑,带@Version注解的实体操作 } }
额外注意事项
- 确保重试的异常类型是
OptimisticLockingFailureException(Spring Data JPA抛出的乐观锁异常),或者你自定义的对应异常,避免重试无关异常。 - 乐观锁实体必须正确使用
@Version注解(JPA)或者对应的版本字段逻辑,确保数据库层面能检测到版本冲突。
内容的提问来源于stack exchange,提问作者Cobra1117
相关产品推荐
相关产品推荐

