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.
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.
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.
@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.
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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With