问题
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
EntityManager
throws an exception (including anySQLException
), you should immediately rollback the database transaction, callEntityManager.close()
(ifcreateEntityManager()
has been called) and discard theEntityManager
instance. Certain methods ofEntityManager
will not leave the persistence context in a consistent state. No exception thrown by an entity manager can be treated as recoverable. Ensure that theEntityManager
will 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 theRuntimeException
propagate 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