问题
The question I have today is how to retry a method after the @Transactional annotation causes an Optimistic Lock Exception (OLE) and rolls back the transaction.
I have asynchronous calls to a Restful application that are attempting to update a database object based on some business logic. If I get an OLE, I'd like to retry the transaction after a delay of 0.2-0.5 seconds.
@Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED, readOnly = false)
public Response myMethod(Long myParam) throws Exception {
~Call to update db using hibernate after business logic~;
return Response.ok().build();
}
I've tried using AspectJ to intercept my method after it throws the OLE so that I can retry. However, the issue is the @Transactional annotation. My method is not throwing the error message since business logic is not failing. Instead, myMethod returns a 200 response, but the OLE exception is encountered and then thrown in the ResourceJavaMethodDispatcher.java class that is responsible for invoking myMethod.
My aspect class:
@Aspect
public class myAspect {
@AfterThrowing(value = "execution(* com.package.blah.myClass.myMethod(..)) && args(.., myParam)", throwing = "ex")
public Response catchAndRetry(JoinPoint jp, Throwable ex, Long myParam) throws Throwable {
Response response = null;
response = invokeAndRetry(jp, myParam);
return response;
}
}
The invokeAndRetry() method has the logic to call wait on the thread and then retry up to a maximum of three tries.
I can successfully get into myAspect from an exception thrown by business logic; but the OLE thrown from the transaction does not get caught in myAspect.
Having said all of that, is there a way to wrap/encapsulate/intercept the @Transaction annotation in order to run my retry logic?
Side notes:
1) I've looked into creating my own @Retry annotation based on the example here. I've used that dependency to try his @Retry annotation, but to no avail.
2) I'll be looking into Spring's @within to see if that could prove useful.
回答1:
The short answer is: you shouldn't try to reuse an EntityManager after an exception occurs. According to the Hibernate EntityManager User guide on Transactions and concurrency, which most probably applies to all JPA providers:
If the
EntityManagerthrows an exception (including anySQLException), you should immediately rollback the database transaction, callEntityManager.close()(ifcreateEntityManager()has been called) and discard theEntityManagerinstance. Certain methods ofEntityManagerwill not leave the persistence context in a consistent state. No exception thrown by an entity manager can be treated as recoverable. Ensure that theEntityManagerwill be closed by callingclose()in a finally block. Note that a container managed entity manager will do that for you. You just have to let theRuntimeExceptionpropagate up to the container.
You might be able to do the retry the operation in a new transaction with a new instance of an EntityManager though, but that's a different use-case.
回答2:
After doing some research and looking at some more tutorials, I found a way to have my aspect take precedence over @Transactional. Just below the @Aspect tag, I added the annotation @Order(1).
This gives my aspect higher priority since @Transactional is defaulted to Ordered.LOWEST_PRECEDENCE. See Spring documentation for some more details about @Order.
来源:https://stackoverflow.com/questions/36158544/intercepting-transactional-after-optimistic-lock-for-asynchronous-calls-in-rest