Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data: rollback transaction on retry

There is an entity:

@Entity
class A {
    ...
    @Version
    int version; 
}

A instances update implemented in optimistic manner:

@Transactional(rollbackFor = {StaleStateException.class})
@Retryable(value = {StaleStateException.class})
public void updateA() {
    A a = findA();
    B b = new B();
    // Update "a" somehow
    a.update();
    // "b" is saved on each retry!
    save(b);
}

As stated in comments, seems that transaction is not rollbacked when StaleStateException occurs, so B instance is saved on each retry.

Is it possible to rollback transaction on retry?

The desired behaviour is that b is saved only on successfull a update.

like image 369
Aliaxander Avatar asked Dec 10 '17 21:12

Aliaxander


People also ask

How do I rollback a transaction in Spring?

Transaction Rollback. The @Transactional annotation is the metadata that specifies the semantics of the transactions on a method. We have two ways to rollback a transaction: declarative and programmatic. In the declarative approach, we annotate the methods with the @Transactional annotation.

What is backoff in Spring Retry?

Spring Uniform Random Backoff The above logic means that the delay period will be random based on initial delay and maxDelay variables set from the retry configuration that you implement.

Does @transactional throw exception?

@Transactional only rolls back transactions for unchecked exceptions. For checked exceptions and their subclasses, it commits data. So although an exception is raised here, because it's a checked exception, Spring ignores it and commits the data to the database, making the system inconsistent.

What is the use of Spring Retry?

Spring Retry provides an ability to automatically re-invoke a failed operation. This is helpful where the errors may be transient (like a momentary network glitch).


1 Answers

I think it may be something related to the @Retryable configuration.

As the doc says https://docs.spring.io/spring-batch/trunk/reference/html/retry.html#statelessRetry a stateless retryable is nothing more than a cycle that keeps calling the same method until it succedes.

The problem is that everytime it fails the first interceptor called is the retryable that will not rethrow the exception, so it does never reach the @Transactional one.

So what happens is that every retry will follow the default transaction propagation which will reuse the same opened transaction with a new B() in the context.

You can check if i'm on the right lead by debugging: if you enter a second retry and find that A is already updated before the update block then i should be right.

You can fix in 2 ways:

Either divide the two block (retry first with nested transaction)

@Retryable(value = {StaleStateException.class})
public void retryableUpdate() {
   updateA();
}

@Transactional(rollbackFor = {StaleStateException.class})
public void updateA() {
    A a = findA();
    B b = new B();
    // Update "a" somehow
    a.update();
    // "b" is saved on each retry!
    save(b);
}

So that the transaction is rolled back first.

Or you can follow the docs and use a stateful retry https://docs.spring.io/spring-batch/trunk/reference/html/retry.html#statefulRetry

like image 171
Zeromus Avatar answered Oct 17 '22 05:10

Zeromus