Spring Retry与Transactional的结合

37

Spring Retry 能够保证与 Spring 的 @Transactional 注解一起使用吗?

具体来说,我想在乐观锁中使用 @Retryable。这似乎取决于创建的 AOP 代理的顺序。例如,如果调用看起来像这样:

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

那么它将正常工作,但是如果代理结构像这样:

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

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

在测试中,它似乎生成了第一种情况(重试,然后事务),但我无法确定这是否是一种保证的行为,还是仅仅是一种幸运。


我认为更有趣的问题是,如果使用@Recover注释的方法,@Transacional是否会起作用...看起来它不会! - pakman
5个回答

22

在这里找到了答案:https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html#tx-decl-explained。表2表明Transactional注解的建议具有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
}

1
但是,@Retryable注释的建议也有一个Ordered.LOWEST_PRECEDENCE的顺序!看起来你的结论(关于使用的安全性)是不正确的。如果@Retryable@Transactional的两个建议具有相同的顺序,则结果顺序未定义(即不能信任)。因此,为了使其可靠,需要更改@Retryable建议的顺序。有多种方法可以做到这一点,但从spring-retry 2.0.1(尚未发布)开始,有一个新的@EnableRetry注释的order参数。 - Ruslan Stelmachenko

5
默认情况下,Spring Retry构建的建议具有与LOWEST_PRECEDENCE相同的顺序 - 请查看RetryConfiguration。但是,有一种非常简单的方法可以覆盖此顺序:
@Configuration
public class MyRetryConfiguration extends RetryConfiguration {
   @Override
   public int getOrder() {
      return Ordered.HIGHEST_PRECEDENCE;
   }
}

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

4
如果您想要独立测试并确保其行为,则可以使用@Transactional @Service,然后再创建另一个服务来使用事务服务并添加重试。
在这种情况下,无论您进行多少次测试,都依赖于未记录的行为(注释处理的确切顺序)。这可能会在小版本之间更改,基于创建独立Spring Bean的顺序等等。简而言之,当您混合在同一方法中使用@Transactional和@Retry时,会引发问题。
编辑:有一个类似的回答问题https://dev59.com/qHA75IYBdhLWcg3wuLjD#45514794与代码
@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

在这种情况下,似乎没问题,因为无论是重试然后事务,还是事务或重试的顺序,可观察的行为都将是相同的。

1
这大致是我正在测试的代码,但如果两个代理被颠倒,行为不会不同吗?我认为Retryable本质上创建了一个try/catch(即“around”advice)。因此,如果事务在该try/catch之外关闭,则异常不会被重试代理捕获。(也许我只是不理解Spring Retry在幕后的工作方式...) - Cobra1117

4

目前(spring-retry版本< 2.0.1),默认情况下,对于@Retryable@Transactional注释的两个建议,顺序是未定义的,因为它们的顺序都是order = Ordered.LOWEST_PRECEDENCE

发生这种情况是因为RetryConfiguration建议实现了IntroductionAdvisor接口。 还有一个方法org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply,它使用2个连续循环将所有找到的建议添加到列表中:首先添加实现 IntroductionAdvisor 接口的所有建议,然后添加所有其他advisors(这就是为什么RetryConfiguration advisor总是出现在此列表中的BeanFactoryTransactionAttributeSourceAdvisor之前)。 然后调用 org.springframework.core.annotation.AnnotationAwareOrderComparator.sort 方法,但由于两个advisors具有相同的顺序,该方法保留了advisors在列表中的顺序。

所以,基本上,@Retryable 通知器目前在 @Transactional 通知器之前应用的原因只是“偶然”或“实现细节”。通过更改 AopUtils.findAdvisorsThatCanApply 方法或 AnnotationAwareOrderComparator 的实现,它可以随时更改。因此,在您的代码中最好不要依赖于这个实现细节。 :)

spring-retry 版本 2.0.1(尚未发布)开始,有一个新的 @EnableRetry 注释的 order 属性,默认情况下等于 Ordered.LOWEST_PRECEDENCE - 1,以确保 @Retryable 通知器始终在 @Transactional 通知器之前排序(当 @Transactional 通知器顺序为默认值时)。

根据您的需求,order 属性也可以设置为任何其他值,例如 Ordered.LOWEST_PRECEDENCE - 4

@EnableRetry(order = Ordered.LOWEST_PRECEDENCE - 4)
@Configuration
public class MyAppConfiguration {}

使用此配置(默认情况下从spring-retry 2.0.1开始),应用建议的顺序如下:

@Retryable
  @Transactional
    Your method body
  End of @Transactional
End of @Retryable

3

如果您正在使用Spring Boot并且希望使用@Retryable,请按照以下步骤操作:

  1. 将依赖项添加到pom文件中:
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
  1. 在您的Spring Boot应用程序中启用重试:
@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对同一个方法进行注释,这将按预期工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接