Why It Is Impossible To Automatically Handle javax.persistence.OptimisticLockException

A field denoted with the javax.persistence.Version annotation represents the state of the database at read (select) time. At the end of transaction the actual value of the entity is compared with the current value in the database and the entity is only going to be written back to the database in case both values are matching. If both are not equal the update "fails" (no rows are going to be updated) and javax.persistence.OptimisticLockException is thrown at the end of transactions. The unchecked OptimisticLockException causes transaction's rollback.


@Entity
public class SomeEntity {

    @Id
    private String name;

    private String description;

    @Version
    private long version;


Each database write operation also changes the version column in the DB, entity manipulations on the other hand, do not change the @Version JPA-entity field. Optimistic locking effectively prevents overriding changed columns in the database with stale values.

An occurrence of OptimisticLockException usually is an indicator of "process bottlenecks". Several users compete for the same data set, the first update will succeed, all other attempts will raise exceptions. This is very similar to merge conflicts in a source code management tool like svn, git or hg. Although there is a strategy to automatically resolve conflicts called "Override and Commit", it is not considered as best practice.

The only feasible strategy in the SCM case is manual merging and testing. Use cases in enterprise applications have a more narrow scope, so you could choose from several strategies:

  1. First update wins
  2. Last update wins
  3. Manual merging

Each choice has a big impact on the business logic, so domain experts, end users or someone with business knowledge needs to be involved. Handling javax.persistence.OptimisticLockException generically (with e.g. repeating and reloading) is only viable for simple technical use cases (e.g. primary key generation).

Retrying business transactions as a generic recovery strategy for the OptimisticLockException is only feasible for simplistic cases. Sophisticated use cases usually require merging and so User Interface adjustments.

[See also an in-depth discussion in the "Real World Java EE Patterns--Rethinking Best Practices" book (Second Iteration, "Green Book"), page 21 in, chapter "Locking for Consistency"]

See you at Java EE Workshops at MUC Airport (particularly the Effective and Architecture Days)!

Thanks Johny Newald for his comment and so idea for this post.

Comments:

Totally agreeing.

My simplistic case is a "self made" concurrency issue: two Threads are updating the same entity accidently triggered by external events. In this case, an automatic retry after the OptimisticLockException in one thread is valid, because the changed datasets of the two threads are completely disjoint, so it doesn't lead to a data inconsistency. The actual problem is the design of the entity structure, which is probably to large and containing parts having completely different responsibilities.

Thanks, Adam, for paying attention!

Posted by Jonny Newald on May 31, 2013 at 03:44 AM CEST #

@Jonny,

your comment is a perfect summary: "Automatic recovery strategies are only viable for entirely disjoint datasets." In such a case "Lost Updates" cannot happen.

thanks!,

adam

Posted by Adam Bien on May 31, 2013 at 05:46 AM CEST #

Adam,

Thank you for taking the time to answer these vexing EJB questions.

Posted by Bailey on October 08, 2013 at 05:35 AM CEST #

As with many problems, the answer really is 'it depends'. If you have a deep understanding of the biz logic involved in the concurrent entity modification which is generating the OLEX then you can put in appropriate logic to automatically handle the OLEX without bubbling it back out to the client (be it either the ui, a service endpoint client etc).

This is true even if the modification is not disjoint. Take for example a thread that updates the state of a task to 'In Progress' and then fires off a non-TX call to a node which actually performs the task and then closes out the TX when it successfully returned from the hand off. The remote node then contacts the server via a REST call to update the state of the task to finished. If the client responds quickly its possible it races the original thread and updates the state to Finished before the TX updating it to 'In Progress' is committed. In that case an OLEX will be generated on the caller thread. If the caller refreshes it can test the state to see if it should be updated, for example if it is in the same state it was in originally. If the refresh shows its state is now 'Complete' it can skip the update and party on.

There are lots of ways to handle OLEXs depending on the biz logic involved and even the persistence provider. For example, Eclipselink has a proprietary feature that will only generate an OLEX if non-disjointed properties are concurrently modified - essentially column level detection vs. generic JPA row level detection.

Posted by Noah White on June 11, 2014 at 07:44 PM CEST #

I had even more finding on optimisticLockException. I had encounter this exception and my solution was pretty straight forward which was just retry and it works fine with my design and the data were good.

The catch is, recently I added a new table which has a relationship to the row which was throwing the optimisticLockException. I noticed that the entry in the second table was not created because of this exception. However the record data consistency was maintained because of the retry. But I loose an important information in my related table.

Does anyone have suggestion on what to do with this kind of scenario?

Posted by Anuj on October 18, 2014 at 10:35 PM CEST #

@Anuj,

your question was too good to be answered shortly, so it was "air hacked" : https://gist.github.com/AdamBien/b65cad58e783b5df9b16

See you soon: http://www.ustream.tv/channel/adambien

cheers,

adam

Posted by Adam Bien on October 20, 2014 at 08:33 AM CEST #

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