Adam Bien's Weblog

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

See you at Java EE Workshops at MUC Airport (Effective + Architectures -- usually transactions are heavily discussed at these days)!


NEW workshop: Microservices with Java EE 7 and Java 8, January 26th, 2015, Airport Munich

A book about rethinking Java EE Patterns

Comments:

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 #

Post a Comment:
  • HTML Syntax: NOT allowed
realworldpatterns.com
...the last 150 posts
...the last 10 comments
License