EJB: How To Catch javax.persistence.OptimisticLockException
Exceptions like javax.persistence.OptimisticLockException
may occur at commit time and so after the execution of an EJB method. Therefore some occurrences are impossible to catch with the convenient "Container Managed Transactions" configuration.
The method DataStore#update
fails, because SomeEntity
changes are going to be recognized at commit time and so after the execution of this method.
@Stateless
public class DataStore {
@PersistenceContext
EntityManager em;
public void update(String id) {
SomeEntity forUpdate = this.em.find(SomeEntity.class, id);
forUpdate.makeDirty();
}
}
The exception: "javax.ejb.EJBException: Transaction marked for rollback
" is raised after the execution of the EJB and can be only caught in the presentation layer.
However, transactions can be started and committed, and so handled, in an interceptor:
public class TXEnforcer {
@Resource
UserTransaction tx;
private final static Logger LOG = Logger.getLogger(TXEnforcer.class.getName());
@AroundInvoke
public Object beginAndCommit(InvocationContext ic) throws Exception {
try {
tx.begin();
Object retVal = ic.proceed();
tx.commit();
return retVal;
} catch (RollbackException e) {
LOG.severe("-----------------Caught (in interceptor): " + e.getCause());
throw e;
} catch (RuntimeException e) {
tx.rollback();
throw e;
}
}
}
The EJB needs to use the TXEnforcer
interceptor to handle transactions and has to switch to the "Bean Managed Transactions" strategy (otherwise you get a: Caused by: javax.naming.NameNotFoundException: Lookup of java:comp/UserTransaction not allowed for Container managed Transaction beans
):
@Stateless
@Interceptors(TXEnforcer.class)
@TransactionManagement(TransactionManagementType.BEAN)
public class DataStore {
}
In this example the DataStore EJB is a boundary which always initiates a new transaction. Therefore there is no need to handle existing transactions.
Thanks Marian S. for asking the question!
[See also Boundary pattern in the "Real World Java EE Patterns--Rethinking Best Practices" book (Second Iteration, "Green Book"), page 57 in, chapter "Boundary"]
The project catchemall was checked in into http://kenai.com/projects/javaee-patterns
Interesting and pragmatic approach, but what about a generic retry mechanism? I think it's legal to handle OptimisticLockException just by giving the transaction at least one more try by automatism...
Posted by Jonny Newald on May 28, 2013 at 09:31 PM CEST #
@Jonny,
thanks for your comment! I answered it with a new post: http://www.adam-bien.com/roller/abien/entry/why_it_is_impossible_to
--adam
Posted by Adam Bien on May 30, 2013 at 08:48 PM CEST #
If rest services are used it is more comfortable to write a servlet filter and catch/map these exceptions there.
Imho more readable and easier. I used this trick in my game.
greetings,
Markus
Posted by Markus on June 02, 2013 at 03:25 PM CEST #
Hi Adam,
What about invoking em.flush() inside interceptor?
I never tried and just coming out of my head just now.
Regards,
Rock
Posted by Rock Ching on November 13, 2013 at 09:24 AM CET #
@Rock,
I am currently using em.flush inside the interceptor and it works greatly.
My only concern is about performance: I recently discovered that Hibernate implements dirty checking without tracking setter, so it just check the state of every entity associated with the persistence context. Given this, a flush would cause a complete check of all entities, and the successive commit outside the interceptor would cause another check.
If this is true, I would say it's better to call em.clear() just after em.flush().
I would like to hear something by an expert like Adam Bien ;)
Posted by Alberto Gori on March 04, 2014 at 11:49 AM CET #