Весенняя повторная попытка с транзакцией

Гарантированно ли Spring Retry работает с аннотацией Spring @Transactional?

В частности, я пытаюсь использовать @Retryable для оптимистичной блокировки. Похоже, это будет зависеть от порядка создания прокси-серверов AOP. Например, если вызовы выглядят так:

Код вызова -> Прокси-сервер повторной попытки -> Прокси-сервер транзакции -> Фактический код БД

Тогда бы работало корректно, но если бы прокси были устроены так:

Код вызова -> Прокси-сервер транзакции -> Прокси-сервер повторной попытки -> Фактический код БД

Тогда повторная попытка не сработает, потому что акт закрытия транзакции вызывает исключение оптимистической блокировки.

При тестировании он, похоже, сгенерировал первый случай (повторная попытка, затем транзакция), но я не мог сказать, было ли это гарантированным поведением или просто повезло.


person Cobra1117    schedule 05.04.2018    source источник
comment
Я думаю, что более интересный вопрос заключается в том, будет ли @ Transacional работать с аннотированным методом @ Recover .... кажется, что это не так!   -  person pakman    schedule 08.02.2020


Ответы (4)


Нашел ответ здесь: https://docs.spring.io/spring/docs/5.0.6.BUILD-SNAPSHOT/spring-framework-reference/data-access.html#transaction-declarative-annotations Таблица 2 указывает, что совет для аннотации Transactional имеет порядок Ordered.LOWEST_PRECEDENCE, что означает, что можно безопасно комбинировать Retryable с Transactional, если вы не переопределяете порядок рекомендаций для любой из этих аннотаций. Другими словами, вы можете смело использовать эту форму:

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

Если вы хотите протестировать его независимо и убедиться, как он себя ведет, у вас может быть @Transactional @Service, а затем другой сервис, который использует транзакцию один и просто добавляет повторные попытки.

В этом случае, сколько бы вы ни тестировали, вы полагаетесь на недокументированное поведение (как именно упорядочена обработка аннотаций). Это может меняться между второстепенными выпусками в зависимости от порядка создания независимых компонентов Spring и т. д. Короче говоря, вы спрашиваете о проблемах, когда смешиваете @Transactional и @Retry в одном и том же методе.

редактировать: есть аналогичный ответ на вопрос https://stackoverflow.com/a/45514794/1849837 с кодом

@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

В этом случае кажется, что все в порядке, потому что независимо от того, какой порядок (повторите попытку, затем транзакцию, или транзакцию, или повторите попытку), наблюдаемое поведение будет одинаковым.

person Bartosz Bilicki    schedule 05.04.2018
comment
Это примерно тот код, который я тестировал, но не изменится ли поведение, если два прокси-сервера поменяются местами? Я думал, что Retryable по сути создает попытку/улов (т.е. вокруг совета). Таким образом, если транзакция закрыта ВНЕ этой попытки/поймать, исключение не будет перехвачено прокси-сервером повторной попытки. (Может быть, я просто не понимаю, как Spring Retry работает за кулисами...) - person Cobra1117; 05.04.2018

По умолчанию Spring Retry создает рекомендации с тем же порядком LOWEST_PRECEDENCE — взгляните на RetryConfiguration. Однако есть довольно простой способ переопределить этот порядок:

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

Обязательно опустите аннотацию @EnableRetry, чтобы избежать учета RetryConfiguration по умолчанию.

person oceansize    schedule 06.12.2018

Если вы используете 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, и он будет работать, как и ожидалось.

person Julian Espinel    schedule 26.07.2020