Spring Retry with Transactional

Found the answer here: https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html#tx-decl-explained Table 2 indicates that the advice for the Transactional annotation has an order of Ordered.LOWEST_PRECEDENCE, which means that it is safe to combine Retryable with Transactional as long as you aren't overriding the order of the advice for either of those annotations. In other words, you can safely use this form:

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

If you want to test it independenty and be sure how it behaves then you may have @Transactional @Service, then another service that uses transaction one and just adds retries.

In this case, no matter how much you test you are relying on undocumented behaviour (how exactly annotations processing is ordered). This may change between minor releases, based on order in which independent Spring beans are created, etc etc. In short, you are asking for problems when you mix @Transactional and @Retry on same method.

edit: There is similar answered question https://stackoverflow.com/a/45514794/1849837 with code

@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

In that case it seems to be fine, because no matter what order is (retry then transaction, or transaction or retry) observable behaviour will be the same.


In case you are using Spring Boot and you want to use @Retryable, this is what you need to do:

  1. Add the dependency to the pom:
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
  1. Enable retries in your Spring Boot application:
@EnableRetry // <-- Add This
@SpringBootApplication
public class SomeApplication {

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

}
  1. Annotate your method with @Retryable:
@Retryable(value = CannotAcquireLockException.class,
        backoff = @Backoff(delay = 100, maxDelay = 300))
@Transactional(isolation = Isolation.SERIALIZABLE)
public boolean someMethod(String someArg, long otherArg) {
    ...
}

You can annotate the same method with both @Retryable and @Transactional and it will work as expected.