05.19.2016 | Andreas Günzel | comment icon 0 Comment

Intermediate commit within transaction

Imagine following situation: We have a service that polls the database for entities in a certain state, for example new. The found entities are passed to a worker that executes a certain task. If execution takes longer than the database polling, an entity will be found and passed to the worker for a second time. This could cause a lot of trouble.

So, a simple solution to this problem is to change the entity’s state to let’s say executing. For different reasons we were not able to switch state before starting execution, so we had to do this as first step within the task. Now, we need to commit the changed state to database in place which is very contrary to the aim of a self-contained transaction for the executed task.

Again, solution sounds simple: Pause current transaction (T1), start a new intermediate transaction (T2), commit entity with changed state and continue main transaction (T1). However, this will lead to a classical deadlock. T1 reads our entity E from database with state new. We start T2 and pass E to it. Within T2 we set state to executing and write this update to database. We continue T1 and perform our actual task including some updates on E. Now, we try to commit T2 and run into a deadlock since E was updated by another process (using a pessimistic lock level, yet T2 could run into trouble).

To solve the deadlock we need to bring T1 (or at least entity E) back in sync with database. Using JPA this can be achieved using EntityManager#refresh(Object entity). Listing 1 shows a CDI Observer performing our intermediate commit:

Method observe receives an event containing the task entity to update. We set the new state and call flushToDatabase which runs within a new transaction and writes the updated entity to database (since we need a new transaction, we cannot call flushToDatabase directly. Paulin’s post describes this problem and solution in detail: start new transaction inside single ejb). Afterwards we call em.refresh(task) to reload the entity to bring the surrounding transaction in sync with underlying database again. By the way, for most persistence providers actually a reload does not result in a database query rather than a cache lookup.

With this observer in mind we finally just need to fire an appropriate event from our business code as shown in listing 2:

Happy Coding,
Andreas

deadlock ejb hibernate java ee jpa

Leave a Comment