Optimistic locking retry with JPA

This is the third part of the optimistic locking series, and I will discuss how we can implement the automatic retry mechanism when dealing with JPA repositories. You can find the introductory part here and the MongoDB implementation here.

JPA requires running the Persistence Context code inside a Transaction, and if our Transaction Manager catches a RuntimeException, it initiates the rollback process. This makes the Persistence Context unusable, since we should discard it along with the roll-backed Transaction.

Therefore it’s safer to retry the business logic operation when we are not within a running Transaction.

For this we altered our @Retry annotation as

public @interface Retry {

    Class<? extends Exception>[] on();

    int times() default 1;

    boolean failInTransaction() default true;
}

We here added the failInTransaction property which is set to true by default.

The Aspect was also changed to take into consideration the new annotation property.

private Object proceed(ProceedingJoinPoint pjp, Retry retryAnnotation) throws Throwable {
	int times = retryAnnotation.times();
	Class<? extends Throwable>[] retryOn = retryAnnotation.on();
	Assert.isTrue(times > 0, "@Retry{times} should be greater than 0!");
	Assert.isTrue(retryOn.length > 0, "@Retry{on} should have at least one Throwable!");
	if (retryAnnotation.failInTransaction() && TransactionSynchronizationManager.isActualTransactionActive()) {
		throw new IllegalTransactionStateException(
				"You shouldn't retry an operation from withing an existing Transaction." +
				"This is because we can't retry if the current Transaction was already rollbacked!");
	}
	LOGGER.info("Proceed with {} retries on {}", times, Arrays.toString(retryOn));
	return tryProceeding(pjp, times, retryOn);
}

If we are running inside a Transaction and we don’t suppress the default safe-check we then throw an IllegalTransactionStateException, notifying the caller that’s safer to retry if the Transaction is contained in the proceeding service call, without nesting our intercepting aspect.

This utility is part of my db-util project along with the MongoDB optimistic concurrency control retry mechanism.

Since it’s already available in Maven Central Repository, you can easily use it by just adding this dependency to your pom.xml:

<dependency>
	<groupId>com.vladmihalcea</groupId>
	<artifactId>db-util</artifactId>
	<version>0.0.1</version>
</dependency>

You can configure the optimistic retry as simple as this:

@Retry(times = 10, on = OptimisticLockException.class)
    public Product updateName(final Long id, final String name) {
        return transactionTemplate.execute(new TransactionCallback<Product>() {
            @Override
            public Product doInTransaction(TransactionStatus status) {
                Product product = entityManager.find(Product.class, id);
                product.setName(name);
                LOGGER.info("Updating product {} name to {}", product, name);
                product = entityManager.merge(product);
                entityManager.flush();
                return product;
            }
    });
}

Running a JUnit test which schedules 10 threads to update the same entity will generate optimistic locking exceptions and this what the test outputs.

Line 102: INFO  [Thread-3]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class javax.persistence.OptimisticLockException]
Line 103: INFO  [Thread-12]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class javax.persistence.OptimisticLockException]
Line 104: INFO  [Thread-9]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class javax.persistence.OptimisticLockException]
Line 105: INFO  [Thread-6]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class javax.persistence.OptimisticLockException]
Line 109: INFO  [Thread-9]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class javax.persistence.OptimisticLockException]
Line 110: INFO  [Thread-7]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class javax.persistence.OptimisticLockException]
Line 114: INFO  [Thread-3]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class javax.persistence.OptimisticLockException]
Line 115: INFO  [Thread-5]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class javax.persistence.OptimisticLockException]
Line 117: INFO  [Thread-11]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 9 remaining retries on [class javax.persistence.OptimisticLockException]
Line 118: INFO  [Thread-6]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class javax.persistence.OptimisticLockException]
Line 123: INFO  [Thread-7]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class javax.persistence.OptimisticLockException]
Line 124: INFO  [Thread-5]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class javax.persistence.OptimisticLockException]
Line 126: INFO  [Thread-11]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 8 remaining retries on [class javax.persistence.OptimisticLockException]
Line 129: INFO  [Thread-5]: v.c.a.OptimisticConcurrencyControlAspect - Optimistic locking detected, 7 remaining retries on [class javax.persistence.OptimisticLockException]

So we reused the same optimistic locking retry logic we first implemented for our MongoDB batch processors, proving we can easily implement such behavior even for JPA repositories.

Code available on GitHub.

If you have enjoyed reading my article and you’re looking forward to getting instant email notifications of my latest posts, you just need to follow my blog.

About these ads

2 thoughts on “Optimistic locking retry with JPA

  1. Hi Vlad Mihalcea,

    I am looking for a way to handle Optimistic locking exception and found your blog. Your implementation was designed for MongoDB, is there anyway to do the same with Postgres ? My problem was happened with the scenarios user update an entity by using two different services at the same time, that also happen with bi-directional relationship.

    Thank you very much.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s