JPA Optimistic and Pessimistic Locking

Jakarta Persistence (JPA) supports both optimistic locking and pessimistic locking. Locking is essential to avoid update collisions when concurrent users simultaneously update the same data. In ObjectDB each entity is locked separately, there is no table locking.

Optimistic locking is applied at transaction commit. ObjectDB checks any database object that is being updated or deleted. An exception is thrown if the check finds that another transaction has already updated the object after the current transaction retrieved it, so the current transaction is attempting to modify a stale version.

When you use ObjectDB, optimistic locking is enabled by default and is fully automatic. Optimistic locking should be the first choice for most applications because, compared to pessimistic locking, it is easier to use and more efficient.

In the rare cases where update collisions must be detected before transaction commit, you can use pessimistic locking. When you use pessimistic locking, database objects are locked during the transaction, and any lock conflicts are detected immediately.

Optimistic Locking

ObjectDB maintains a version number for every entity. The initial version of a new entity is 1 when it is first stored in the database. In every transaction where an entity is modified, its version number is automatically incremented by one. Version numbers are managed internally but can be exposed by defining a version field.

During commitjakarta.persistence.EntityTransaction.commit()Commit the current resource transaction, writing any unflushed changes to the database. (and flushjakarta.persistence.EntityManager.flush()Synchronize changes held in the persistence context to the underlying database.), ObjectDB checks every database object that is being updated or deleted and compares the version number of that object in the database to the version number of the in-memory object being updated. If the version numbers do not match, the transaction fails and an OptimisticLockExceptionjakarta.persistence.OptimisticLockExceptionThrown by the persistence provider when an optimistic locking conflict occurs. is thrown. A mismatch indicates that another user (using another EntityManagerjakarta.persistence.EntityManagerInterface used to interact with the persistence context.) has modified the object since the current transaction retrieved it.

Optimistic locking is completely automatic and enabled by default in ObjectDB, regardless of whether a version field (which is required by some ORM JPA providers) is defined in the entity class.

Pessimistic Locking

The main supported pessimistic lock modes are:

Setting a Pessimistic Lock

An entity can be locked explicitly with the lockjakarta.persistence.EntityManager.lock(Object,LockModeType)Lock an entity instance belonging to the persistence context, obtaining the specified lock mode . method:

  em.lockjakarta.persistence.EntityManager.lock(Object,LockModeType)Lock an entity instance belonging to the persistence context, obtaining the specified  lock mode .(employee, LockModeTypejakarta.persistence.LockModeTypeEnumerates the kinds of optimistic or pessimistic lock which may be obtained on an entity instance..PESSIMISTIC_WRITEjakarta.persistence.LockModeType.PESSIMISTIC_WRITEPessimistic write lock.);

The first argument is an entity. The second argument is the requested lock mode.

A TransactionRequiredExceptionjakarta.persistence.TransactionRequiredExceptionThrown by the persistence provider when a transaction is required but is not active. is thrown if there is no active transaction when lockjakarta.persistence.EntityManager.lock(Object,LockModeType)Lock an entity instance belonging to the persistence context, obtaining the specified lock mode . is called because explicit locking requires an active transaction.

A LockTimeoutExceptionjakarta.persistence.LockTimeoutExceptionThrown by the persistence provider when a pessimistic locking conflict occurs that does not result in transaction rollback. is thrown if the requested pessimistic lock cannot be granted:

  • A PESSIMISTIC_READ lock request fails if another user (from another EntityManager instance) currently holds a PESSIMISTIC_WRITE lock on that database object.
  • A PESSIMISTIC_WRITE lock request fails if another user currently holds either a PESSIMISTIC_WRITE lock or a PESSIMISTIC_READ lock on that database object.

For example, consider the following code fragment:

  em1.lockjakarta.persistence.EntityManager.lock(Object,LockModeType)Lock an entity instance belonging to the persistence context, obtaining the specified  lock mode .(e1, lockMode1);
  em2.lockjakarta.persistence.EntityManager.lock(Object,LockModeType)Lock an entity instance belonging to the persistence context, obtaining the specified  lock mode .(e2, lockMode2);

Here, em1 and em2 are two EntityManager instances that manage the same Employee database object. The object is referenced as e1 by em1 and as e2 by em2. Note that e1 and e2 are two in-memory entities that represent the same database object.

If both lockMode1 and lockMode2 are PESSIMISTIC_READ, both lock requests succeed. Any other combination of pessimistic lock modes involving PESSIMISTIC_WRITE causes a LockTimeoutExceptionjakarta.persistence.LockTimeoutExceptionThrown by the persistence provider when a pessimistic locking conflict occurs that does not result in transaction rollback. on the second lock request.

Pessimistic Lock Timeout

By default, when a pessimistic lock conflict occurs, a LockTimeoutExceptionjakarta.persistence.LockTimeoutExceptionThrown by the persistence provider when a pessimistic locking conflict occurs that does not result in transaction rollback. is thrown immediately. The "jakarta.persistence.lock.timeout" hint can be set to wait for a pessimistic lock for a specified number of milliseconds. The hint can be set in several scopes:

For the entire persistence unit, use a persistence.xml property:

    <properties>
       <property name="jakarta.persistence.lock.timeout" value="1000"/>
    </properties>

For an EntityManagerFactory, use the createEntityManagerFactory method:

  Map<String,Object> properties = new HashMap();
  properties.put("jakarta.persistence.lock.timeout", 2000);
  EntityManagerFactory emf =
      Persistence.createEntityManagerFactoryjakarta.persistence.Persistence.createEntityManagerFactory(String,Map)Create and return an   EntityManagerFactory   for the named persistence unit, using the given properties.("pu", properties);

For an EntityManagerjakarta.persistence.EntityManagerInterface used to interact with the persistence context., use the createEntityManagerjakarta.persistence.EntityManagerFactory.createEntityManager(Map)Create a new application-managed EntityManager with the given Map<K,V> specifying property settings. method:

  Map<String,Object> properties = new HashMap();
  properties.put("jakarta.persistence.lock.timeout", 3000);
  EntityManager em = emf.createEntityManagerjakarta.persistence.EntityManagerFactory.createEntityManager(Map)Create a new application-managed   EntityManager   with the given   Map<K,V>   specifying property settings.(properties);

Or, use the setPropertyjakarta.persistence.EntityManager.setProperty(String,Object)Set an entity manager property or hint. method:

  em.setPropertyjakarta.persistence.EntityManager.setProperty(String,Object)Set an entity manager property or hint.("jakarta.persistence.lock.timeout", 4000);

In addition, the hint can be set for a specific retrieval operation or query.

Releasing a Pessimistic Lock

Pessimistic locks are automatically released at the end of the transaction (by either commitjakarta.persistence.EntityTransaction.commit()Commit the current resource transaction, writing any unflushed changes to the database. or rollbackjakarta.persistence.EntityTransaction.rollback()Roll back the current resource transaction.).

ObjectDB also supports releasing a lock explicitly while the transaction is active, as follows:

  em.lockjakarta.persistence.EntityManager.lock(Object,LockModeType)Lock an entity instance belonging to the persistence context, obtaining the specified  lock mode .(employee, LockModeTypejakarta.persistence.LockModeTypeEnumerates the kinds of optimistic or pessimistic lock which may be obtained on an entity instance..NONEjakarta.persistence.LockModeType.NONENo lock.);

Other Explicit Lock Modes

In addition to the two main pessimistic modes, PESSIMISTIC_WRITEjakarta.persistence.LockModeType.PESSIMISTIC_WRITEPessimistic write lock. and PESSIMISTIC_READjakarta.persistence.LockModeType.PESSIMISTIC_READPessimistic read lock. (which are discussed above), JPA defines additional lock modes that you can specify as arguments for the lockjakarta.persistence.EntityManager.lock(Object,LockModeType)Lock an entity instance belonging to the persistence context, obtaining the specified lock mode . method to obtain special effects:

Because optimistic locking is applied automatically by ObjectDB to every entity, the OPTIMISTIC lock mode has no effect and, if specified, is silently ignored.

The OPTIMISTIC_FORCE_INCREMENT mode affects only clean (non-dirty) entities. An explicit lock with this mode marks the clean entity as modified (dirty) and increments its version number by 1.

The PESSIMISTIC_FORCE_INCREMENT mode is equivalent to PESSIMISTIC_WRITE, but it also marks a clean entity as dirty and increments its version number by one. In other words, it combines PESSIMISTIC_WRITE with OPTIMISTIC_FORCE_INCREMENT.

Locking during Retrieval

JPA provides various methods for locking entities when they are retrieved from the database. In addition to improving efficiency (relative to a retrieval followed by a separate lock), these methods perform retrieval and locking as a single atomic operation.

For example, the findjakarta.persistence.EntityManager.find(Class,Object,LockModeType)Find by primary key and obtain the given lock type for the resulting entity. method has a form that accepts a lock mode:

  Employee employee = em.findjakarta.persistence.EntityManager.find(Class,Object,LockModeType)Find by primary key and obtain the given lock type for the resulting entity.(
      Employee.class, 1, LockModeTypejakarta.persistence.LockModeTypeEnumerates the kinds of optimistic or pessimistic lock which may be obtained on an entity instance..PESSIMISTIC_WRITEjakarta.persistence.LockModeType.PESSIMISTIC_WRITEPessimistic write lock.);

Similarly, the refreshjakarta.persistence.EntityManager.refresh(Object,LockModeType)Refresh the state of the given managed entity instance from the database, overwriting unflushed changes made to the entity, if any, and obtain the given lock mode . method can also receive a lock mode:

  em.refreshjakarta.persistence.EntityManager.refresh(Object,LockModeType)Refresh the state of the given managed entity instance from the database, overwriting unflushed changes made to the entity, if any, and obtain the given  lock mode .(employee, LockModeTypejakarta.persistence.LockModeTypeEnumerates the kinds of optimistic or pessimistic lock which may be obtained on an entity instance..PESSIMISTIC_WRITEjakarta.persistence.LockModeType.PESSIMISTIC_WRITEPessimistic write lock.);

A lock mode can also be set for a query to lock all the query result objects.

When a retrieval operation includes pessimistic locking, a timeout can be specified as a property. For example:

  Map<String,Object> properties = new HashMap();
  properties.put("jakarta.persistence.lock.timeout", 2000);
  Employee employee = em.findjakarta.persistence.LockModeType.PESSIMISTIC_WRITEPessimistic write lock.(
      Employee.class, 1, LockModeTypejakarta.persistence.LockModeTypeEnumerates the kinds of optimistic or pessimistic lock which may be obtained on an entity instance..PESSIMISTIC_WRITEjakarta.persistence.LockModeType.PESSIMISTIC_WRITEPessimistic write lock., properties);
  ...
  em.refreshjakarta.persistence.LockModeType.PESSIMISTIC_WRITEPessimistic write lock.(employee, LockModeTypejakarta.persistence.LockModeTypeEnumerates the kinds of optimistic or pessimistic lock which may be obtained on an entity instance..PESSIMISTIC_WRITEjakarta.persistence.LockModeType.PESSIMISTIC_WRITEPessimistic write lock., properties);

Setting a timeout at the operation level overrides settings in higher scopes.