Spring 使用事务重试

Spring Retry with Transactional

Spring 重试是否保证与 Spring 的 @Transactional 注释一起工作?

具体来说,我正在尝试使用 @Retryable 进行乐观锁定。它似乎取决于所创建的 AOP 代理的顺序。例如,如果调用如下所示:

调用代码 -> 重试代理 -> 事务代理 -> 实际数据库代码

那么它会正常工作,但如果代理的结构如下:

调用代码 -> 事务代理 -> 重试代理 -> 实际数据库代码

然后重试将不起作用,因为关闭事务的行为会引发乐观锁定异常。

在测试中,它似乎生成了第一个案例(重试,然后是交易),但我无法判断这是一个有保证的行为还是只是幸运。

如果您想独立测试它并确定它的行为方式,那么您可能有 @Transactional @Service,然后是另一个使用事务 1 并仅添加重试的服务。

在这种情况下,无论您测试多少,您都依赖于未记录的行为(注释处理的确切排序方式)。这可能会在次要版本之间发生变化,具体取决于创建独立 Spring bean 的顺序等等。简而言之,当您在同一方法上混合使用 @Transactional 和 @Retry 时,您会遇到问题。

编辑:有类似的已回答问题 代码

@Retryable(StaleStateException.class)
@Transactional
public void doSomethingWithFoo(Long fooId){
    // read your entity again before changes!
    Foo foo = fooRepository.findOne(fooId);
    foo.setStatus(REJECTED)  // <- sample foo modification
} // commit on method end

在那种情况下似乎没问题,因为无论顺序是什么(重试然后交易,或交易或重试)可观察到的行为都是相同的。

在这里找到答案: https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html#tx-decl-explained Table 2表示对Transactional注解的advice的顺序是Ordered.LOWEST_PRECEDENCE,也就是说RetryableTransactional组合起来是安全的,只要您没有覆盖这些注释中任何一个的建议顺序。换句话说,您可以放心地使用这种形式:

@Retryable(StaleStateException.class)
@Transactional
public void performDatabaseActions() {
    //Database updates here that may cause an optimistic locking failure 
    //when the transaction closes
}

默认情况下 Spring 重试使用相同的 LOWEST_PRECEDENCE 顺序构建建议 - 查看 RetryConfiguration。 但是,有一种非常简单的方法可以覆盖此顺序:

@Configuration
public class MyRetryConfiguration extends RetryConfiguration {
   @Override
   public int getOrder() {
      return Ordered.HIGHEST_PRECEDENCE;
   }
}

确保省略 @EnableRetry 注释以避免默认 RetryConfiguration 被考虑在内。

如果您正在使用 Spring 启动并且您想要使用 @Retryable,这就是您需要做的:

  1. 将依赖添加到 pom:
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
  1. 在您的 Spring 引导应用程序中启用重试:
@EnableRetry // <-- Add This
@SpringBootApplication
public class SomeApplication {

    public static void main(String[] args) {
        SpringApplication.run(SomeApplication.class, args);
    }

}
  1. @Retryable 注释你的方法:
@Retryable(value = CannotAcquireLockException.class,
        backoff = @Backoff(delay = 100, maxDelay = 300))
@Transactional(isolation = Isolation.SERIALIZABLE)
public boolean someMethod(String someArg, long otherArg) {
    ...
}

您可以使用 @Retryable@Transactional 注释相同的方法,它将按预期工作。