Skip to main content

JPA 2.0 Concurrency and locking

Posted by caroljmcdonald on July 30, 2009 at 2:39 PM PDT



jpaconcurrency

Optimistic Concurrency



Optimistic locking lets concurrent transactions process simultaneously,
but detects and prevent collisions, this works best for applications
where most concurrent transactions do not conflict. JPA Optimistic
locking allows anyone to read and update an entity, however a version
check is made upon commit and an exception is thrown if the version was
updated in the database since the entity was read.  In JPA for
Optimistic locking you annotate an attribute with @Version as shown
below:


public class Employee {

    @ID int id;

    @Version int version;



The Version attribute will be incremented with a successful commit. The
Version attribute can be an int, short, long, or timestamp.  This
results in SQL like the following:


“UPDATE Employee SET ..., version = version + 1

     WHERE id = ? AND version = readVersion



The advantages of optimistic locking are that no database locks are
held which can give better scalability. The disadvantages are that the
user or application must refresh and retry failed updates.

Optimistic Locking Example



In the optimistic locking example below, 2 concurrent transactions are updating employee e1. The transaction on the left commits first causing the e1
version attribute to be incremented with the update. The transaction on
the right throws an OptimisticLockException because the e1 version
attribute is higher than when e1 was read, causing the transaction to roll back.

img60.jpg

Additional Locking with JPA Entity Locking APIs



With JPA it is possible to lock an entity, this allows you to control
when, where and which kind of locking to use. JPA 1.0 only supported
Optimistic read or Optimistic write locking.  JPA 2.0 supports
Optimistic and Pessimistic locking, this is layered on top of @Version
checking described above.



JPA 2.0 LockMode values :

  • OPTIMISTIC (JPA 1.0 READ):
    • perform a version check on locked Entity before commit, throw an OptimisticLockException if Entity version mismatch.
  • OPTIMISTIC_FORCE_INCREMENT (JPA 1.0 WRITE)
    • perform
      a version check on locked Entity before commit, throw an
      OptimisticLockException if Entity version mismatch, force an increment
      to the version at the end of the transaction, even if the entity is not
      modified.
  • PESSIMISTIC:
    • lock the database row when reading
  • PESSIMISTIC_FORCE_INCREMENT
    • lock
      the database row when reading, force an increment to the version at the
      end of the transaction, even if the entity is not modified.

There are multiple APIs to specify locking an Entity:

  • EntityManager methods: lock, find, refresh
  • Query methods: setLockMode 
  • NamedQuery annotation: lockMode element


OPTIMISTIC (READ) LockMode Example


In the optimistic locking example below,  transaction1 on the left updates the department name for dep , which causes dep's version attribute to be incremented. Transaction2 on
the right gives an employee
a raise if he's in the "Eng" department. Version checking on the
employee attribute would not throw an exception in this example since
it was the dep
Version attribute that was updated in transaction1. In this example the
employee change should not commit if the department was changed after
reading, so an OPTIMISTIC lock is used : em.lock(dep, OPTIMISTIC).  This will cause a version check on the  dep Entity before committing transaction2  which will throw an OptimisticLockException because the dep version attribute is higher than when dep was read, causing the transaction to roll back.

img62.jpg

OPTIMISTIC_FORCE_INCREMENT (write) LockMode Example



In the OPTIMISTIC_FORCE_INCREMENT locking example below, 
transaction2 on the right wants to be sure that the dep name does not
change during the transaction, so transaction2 locks the dep Entity em.lock(dep, OPTIMISTIC_FORCE_INCREMENT) and then calls em.flush() which causes dep's version attribute to be incremented in the database. This will cause any parallel updates to dep  to throw an OptimisticLockException and roll back. In transaction1 on the left at commit time when the dep version attribute is checked and found to be stale, an OptimisticLockException is thrown

img63.jpg

Pessimistic Concurrency

Pessimistic concurrency locks the database row when data is read, this
is the equivalent of a (SELECT . . . FOR UPDATE [NOWAIT]) . 
Pessimistic locking ensures that transactions do not update the same
entity at the same time, which can simplify application code, but it
limits concurrent access to the data which can cause bad scalability
and may cause deadlocks. Pessimistic locking is better for applications
with a higher risk of contention among concurrent transactions.

The examples below show:

  1. reading an entity and then locking it later
  2. reading an entity with a lock
  3. reading an entity, then later refreshing it with a lock



The Trade-offs are the longer you hold the lock the greater the risks
of bad scalability and deadlocks. The later you lock the greater the
risk of stale data, which can then cause an optimistic lock exception,
if the entity was updated after reading but before locking.

img66.jpg


img672.jpg



The right locking approach depends on your application:

  • what is
    the risk of risk of contention among concurrent transactions?
  • What are
    the requirements for scalability?
  • What are the requirements for user
    re-trying on failure?

References and More Information:

Preventing Non-Repeatable Reads in JPA Using EclipseLink

Java Persistence API 2.0: What's New ?

What's New and Exciting in JPA 2.0

Beginning Java™ EE 6 Platform with GlassFish™ 3

Pro EJB 3: Java Persistence API (JPA 1.0)

Java Persistence API: Best Practices and Tips












Comments

JPA 2.0 Concurrency and

Hello Carol,
I'm trying to make some points on JPA concurrency clear to me. Thanks, for the article you wrote and the presentation.
I have a question about optimistic locking.
In the "OPTIMISTIC_FORCE_INCREMENT (write) LockMode Example" section of this arcticle in the example of the tx2 there is an explicit call to em.flush() used.
You write that this causes version to updated in the DB, however I never get this field updated in the DB just after em.flush() called, only after tx.commit().
If I call em.flush() in the tx2 before tx1 get commited, tx1 fails to commit. Moreover it seems tx1 get blocked waiting for tx2 (I use OpenJPA 2.0). However without em.flush() the first transaction commits successfully.
Could you clarify when shall I use explicit em.flush() call in a context of JPA locking ?
Thanks,
Nikolay

JPA 2.0 Concurrency and

I tried your second example with hibernate (OPTIMISTIC (READ) LockMode Example)

This does not works. Hibernate seems to ignire the modified (in first txn) dep field as unchanged in the second txn.

Hibernate does not fire any selects to verify the status of dep entity. Probably because it is unchanged, hibernate ignores it.