Jump to: navigation, search

EclipseLink/Development/JPA 2.0/pessimistic locking

Lock Modes / Pessimistic Locking

JPA 2.0 Root | bug 248489

Issue Summary

In JPA 2.0 the specification extends Entity Manager lock mechanism to include explicit support for pessimistic locking. EntityManager and Query APIs have been updated with an additional query hint/persistence unit property. The specification has extensive detail on expected behaviour that must be implemented.

See JPA 2.0 ED section 3.4.3, 3.4.4, 3.6.3 and 3.6.4 for details.

General Solution

As EclipseLink includes Pessimistic Locking functionality this feature should be limited to comprehensive implementation of the JPA functionality and behaviour without many core changes. Pessimistic Locking and Optimistic Locking LockModesTypes must be covered, new query hint/persistence unit timeout properties and new Exceptions must be supported. As well, current query and Entity operations support must be updated to comply with new Pessimistic locking constraints.

Open Issues

Will Target table mappings (uni-directional OneToMany, ElementCollections) require that target rows also be locked?

Usage matrix

EntityManager

find(Class<T>, Object)

  1. Uses no locking
  2. Will NOT use a javax.persistence.lock.timeout setting from the EntityManager settings as it does not apply in this case where no pessimistic locking is available.
Internal execution

EclipseLink will use the descriptor's defined query to avoid extra query creation if it exists,

ReadObjectQuery query = descriptor.getQueryManager().getReadObjectQuery();
query.checkPrepare((AbstractSession) session, null);
query = (ReadObjectQuery) query.clone();

Otherwise a new query will create be created:

ReadObjectQuery query = new ReadObjectQuery();
query.setReferenceClass(referenceClass);

Followed with the following internal settings and returns the execution of the query result.

query.setLockMode(ObjectBuildingQuery.NO_LOCK);
query.setIsExecutionClone(true);
query.setSelectionKey(primaryKeyValues);
query.conformResultsInUnitOfWork();
return uow.executeQuery(query);

Note: There are no properties or query hints to apply in this case.

find(Class<T>, Object, LockModeType)

  1. Uses the lock mode specified
  2. A javax.persistence.lock.timeout setting from the EntityManager settings will be used if available on the find query (internally it will set the query timeout value)
  3. If the PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT lock mode type is specified and the javax.persistence.lock.timeout is specified, a javax.persistence.LockTimeoutException will be thrown if an exception is raised when issuing the locking query. If no timeout hint is specified a javax,persistence.PessimisticLockException will be thrown.
  4. Whenever a pessimistic lock is used on an entity that contains a version attribute, that attribute will be updated (incremented)
Internal execution

EclipseLink will use the descriptor's defined query to avoid extra query creation if it exists,

ReadObjectQuery query = descriptor.getQueryManager().getReadObjectQuery();
query.checkPrepare((AbstractSession) session, null);
query = (ReadObjectQuery) query.clone();

Otherwise a new query will create be created:

ReadObjectQuery query = new ReadObjectQuery();
query.setReferenceClass(referenceClass);

Followed with the following internal settings and returns the execution of the query result.

query.setIsExecutionClone(true);
query.setSelectionKey(primaryKeyValues);
query.conformResultsInUnitOfWork();

// If the LockModeType is null or NONE
query.setLockMode(ObjectBuildingQuery.NO_LOCK);
return uow.executeQuery(query);

// If the LockModeType is PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT
// If the javax.persistence.lock.timeout hint has been specified in the EntityManager properties or PersistenceUnit properties
// If javax.persistence.lock.timeout > 0
query.setLockMode(ObjectBuildingQuery.LOCK) 
query.setWaitTimeout(javax.persistence.lock.timeout);
return uow.executeQuery(query);
// Else, javax.persistence.lock.timeout == 0
query.setLockMode(ObjectBuildingQuery.LOCK_NOWAIT);
return uow.executeQuery(query);
// Otherwise just a straight up lock is used
query.setLockMode(ObjectBuildingQuery.LOCK) 
return uow.executeQuery(query);

// If the LockModeType is READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT
Object obj = uow.executeQuery(query);
uow.forceUpdateToVersionField(obj, (lockMode == WRITE || lockMode == OPTIMISTIC_FORCE_INCREMENT));
return obj;

Note: The only property/query hint that can be applied in this case is the javax.persistence.lock.timeout

find(Class<T>, Object, LockModeType, Map)

  1. Uses the lock mode specified
  2. A javax.persistence.lock.timeout setting from the EntityManager settings will be not be used as it is assumed that the properties that are passed in are the strict set to be used with the find query.
  3. If the PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT lock mode type is specified and the javax.persistence.lock.timeout or eclipselink.jdbc.timeout hint is specified, a javax.persistence.LockTimeoutException will be thrown if an exception is raised when issuing the find query. If no timeout hint is specified a javax,persistence.PessimisticLockException will be thrown.
  4. Whenever a pessimistic lock is used on an entity that contains a version attribute, that attribute will be updated (incremented)
Internal execution

EclipseLink will use the descriptor's defined query to avoid extra query creation if it exists,

ReadObjectQuery query = descriptor.getQueryManager().getReadObjectQuery();
query.checkPrepare((AbstractSession) session, null);
query = (ReadObjectQuery) query.clone();

Otherwise a new query will create be created:

ReadObjectQuery query = new ReadObjectQuery();
query.setReferenceClass(referenceClass);

Followed with the following internal settings and returns the execution of the query result.

query.setIsExecutionClone(true);
query.setSelectionKey(primaryKeyValues);

// Apply the properties/query hints.
QueryHintsHandler.apply(properties, query);

// If the eclipselink.cache-usage hint is NOT set in the given properties then
query.conformResultsInUnitOfWork();

// If the LockModeType is null or NONE
query.setLockMode(ObjectBuildingQuery.NO_LOCK);
return uow.executeQuery(query);

// If the LockModeType is PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT
// If the javax.persistence.lock.timeout hint has been specified in the given properties or PersistenceUnit properties
// If javax.persistence.lock.timeout > 0
query.setLockMode(ObjectBuildingQuery.LOCK) 
query.setWaitTimeout(javax.persistence.lock.timeout);
return uow.executeQuery(query);
// Else, javax.persistence.lock.timeout == 0
query.setLockMode(ObjectBuildingQuery.LOCK_NOWAIT);
return uow.executeQuery(query);
// Otherwise just a straight up lock is used
query.setLockMode(ObjectBuildingQuery.LOCK) 
return uow.executeQuery(query);

// If the LockModeType is READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT
Object obj = uow.executeQuery(query);
uow.forceUpdateToVersionField(obj, (lockMode == WRITE || lockMode == OPTIMISTIC_FORCE_INCREMENT));
return obj;

Note: See org.eclipse.persistence.internal.jpa.QueryHintsHandler for the full list of available query hints that may be applied in this case.

lock(Object, LockModeType)

  1. Uses the lock mode specified
  2. A javax.persistence.lock.timeout setting from the EntityManager settings will be used if available on the lock query (internally it will set the query timeout value)
  3. If the PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT lock mode type is specified and the javax.persistence.lock.timeout is also specified, a javax.persistence.LockTimeoutException will be thrown if an exception is raised when issuing the locking query. If no timeout hint is specified a javax,persistence.PessimisticLockException will be thrown.
  4. Whenever a pessimistic lock is used on an entity that contains a version attribute, that attribute will be updated (incremented)
Internal execution

EclipseLink will create the following query:

ReadObjectQuery query = new ReadObjectQuery();
query.setSelectionObject(entity);
query.setIsExecutionClone(true);
query.refreshIdentityMapResult();
query.cascadePrivateParts();

And based on the lock mode type will do the following.

// If the LockModeType is null or NONE
// No query is executed and the selection object is returned.
return query.getSelectionObject();

// If the LockModeType is PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT
// If the javax.persistence.lock.timeout hint has been specified in the EntityManager properties or PersistenceUnit properties
// If javax.persistence.lock.timeout > 0
query.setLockMode(ObjectBuildingQuery.LOCK) 
query.setWaitTimeout(javax.persistence.lock.timeout);
return uow.executeQuery(query);
// Else, javax.persistence.lock.timeout == 0
query.setLockMode(ObjectBuildingQuery.LOCK_NOWAIT);
return uow.executeQuery(query);
// Otherwise just a straight up lock is used
query.setLockMode(ObjectBuildingQuery.LOCK) 
return uow.executeQuery(query);

// If the LockModeType is READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT
// Refresh is on by default so we execute the query.
Object obj = uow.executeQuery(query);
uow.forceUpdateToVersionField(obj, (lockMode == WRITE || lockMode == OPTIMISTIC_FORCE_INCREMENT));
return obj;

Note: The only property/query hint that can be applied in this case is the javax.persistence.lock.timeout

lock(Object, LockModeType, Map)

  1. Uses the lock mode specified
  2. A javax.persistence.lock.timeout setting from the EntityManager settings will be not be used as it is assumed that the properties that are passed in are the strict set to be used with the find query.
  3. If the PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT lock mode type is specified and the javax.persistence.lock.timeout or eclipselink.jdbc.timeout hint is also specified, a javax.persistence.LockTimeoutException will be thrown if an exception is raised when issuing the locking query. If no timeout hint is specified a javax,persistence.PessimisticLockException will be thrown.
  4. Whenever a pessimistic lock is used on an entity that contains a version attribute, that attribute will be updated (incremented)
Internal execution

EclipseLink will create the following query:

ReadObjectQuery query = new ReadObjectQuery();
query.setSelectionObject(entity);
query.setIsExecutionClone(true);

// Apply the properties/query hints.
QueryHintsHandler.apply(properties, query);

// If the eclipselink.refresh hint is NOT set in the given properties, then
query.refreshIdentityMapResult();

// If the eclipselink.refresh.cascade hint is NOT set in the given properties, then
query.cascadePrivateParts();

And based on the lock mode type will do the following.

// If the LockModeType is null or NONE
// No query is executed and the selection object is returned.
return query.getSelectionObject();

// If the LockModeType is PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT
// If the javax.persistence.lock.timeout hint has been specified in the given properties or PersistenceUnit properties
// If javax.persistence.lock.timeout > 0
query.setLockMode(ObjectBuildingQuery.LOCK) 
query.setWaitTimeout(javax.persistence.lock.timeout);
return uow.executeQuery(query);
// Else, javax.persistence.lock.timeout == 0
query.setLockMode(ObjectBuildingQuery.LOCK_NOWAIT);
return uow.executeQuery(query);
// Otherwise just a straight up lock is used
query.setLockMode(ObjectBuildingQuery.LOCK) 
return uow.executeQuery(query);

// If the LockModeType is READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT
// If the eclipselink.refresh query hint is set.
Object obj = uow.executeQuery(query);
uow.forceUpdateToVersionField(obj, (lockMode == WRITE || lockMode == OPTIMISTIC_FORCE_INCREMENT));
return obj;
// Otherwise with no eclipselink.refresh query hint
Object obj = query.getSelectionObject();
uow.forceUpdateToVersionField(obj, (lockMode == WRITE || lockMode == OPTIMISTIC_FORCE_INCREMENT));
return obj;

Note: See org.eclipse.persistence.internal.jpa.QueryHintsHandler for the full list of available query hints that may be applied in this case.

refresh(Object)

  1. Uses no locking
  2. Will NOT use a javax.persistence.lock.timeout setting from the EntityManager settings as it does not apply in this case where no pessimistic locking is available.
Internal execution

EclipseLink will create and execute the following query:

ReadObjectQuery query = new ReadObjectQuery();
query.setSelectionObject(entity);
query.setIsExecutionClone(true);
query.refreshIdentityMapResult();
query.cascadeByMapping();
query.setLockMode(ObjectBuildingQuery.NO_LOCK);
return uow.executeQuery(query);

Note: There are no properties or query hints to apply in this case.

refresh(Object, LockModeType)

  1. Uses the lock mode specified
  2. A javax.persistence.lock.timeout setting from the EntityManager settings will be used if available on the find query (internally it will set the query timeout value)
  3. If the PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT lock mode type is specified and the javax.persistence.lock.timeout is also specified, a javax.persistence.LockTimeoutException will be thrown if an exception is raised when issuing the refresh query. If no timeout hint is specified a javax,persistence.PessimisticLockException will be thrown.
Internal execution

EclipseLink will create the following query:

ReadObjectQuery query = new ReadObjectQuery();
query.setSelectionObject(entity);
query.setIsExecutionClone(true);
query.refreshIdentityMapResult();
query.cascadeByMapping();

And based on the lock mode type will do the following.

// If the LockModeType is null or NONE
query.setLockMode(ObjectBuildingQuery.NO_LOCK);
return uow.executeQuery(query);

// If the LockModeType is PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT
// If the javax.persistence.lock.timeout hint has been specified in the EntityManager properties or PersistenceUnit properties
// If javax.persistence.lock.timeout > 0
query.setLockMode(ObjectBuildingQuery.LOCK) 
query.setWaitTimeout(javax.persistence.lock.timeout);
return uow.executeQuery(query);
// Else, javax.persistence.lock.timeout == 0
query.setLockMode(ObjectBuildingQuery.LOCK_NOWAIT);
return uow.executeQuery(query);
// Otherwise just a straight up lock is used
query.setLockMode(ObjectBuildingQuery.LOCK) 
return uow.executeQuery(query);

// If the LockModeType is READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT
// Refresh is on by default so we execute the query.
Object obj = uow.executeQuery(query);
uow.forceUpdateToVersionField(obj, (lockMode == WRITE || lockMode == OPTIMISTIC_FORCE_INCREMENT));
return obj;

Note: The only property/query hint that can be applied in this case is the javax.persistence.lock.timeout

refresh(Object, LockModeType, Map)

  1. Uses the lock mode specified
  2. A javax.persistence.lock.timeout setting from the EntityManager settings will be not be used as it is assumed that the properties that are passed in are the strict set to be used with the find query.
  3. If the PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT lock mode type is specified as and the javax.persistence.lock.timeout or eclipselink.jdbc.timeout hint is also specified, a javax.persistence.LockTimeoutException will be thrown if an exception is raised when issuing the refresh query. If no timeout hint is specified a javax,persistence.PessimisticLockException will be thrown.#A javax.persistence.lock.timeout setting from the EntityManager settings will be not be used as it is assumed that the properties that are passed in are the strict set to be used with the find query.
Internal execution

EclipseLink will create the following query:

ReadObjectQuery query = new ReadObjectQuery();
query.setSelectionObject(entity);
query.setIsExecutionClone(true);

// Apply the properties/query hints.
QueryHintsHandler.apply(properties, query);

// If the eclipselink.refresh hint is NOT set in the given properties, then
query.refreshIdentityMapResult();

// If the eclipselink.refresh.cascade hint is NOT set in the given properties, then
query.cascadePrivateParts();

And based on the lock mode type will do the following.

// If the LockModeType is null or NONE
// No query is executed and the selection object is returned.
query.setLockMode(ObjectBuildingQuery.NO_LOCK);
return uow.executeQuery(query);

// If the LockModeType is PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT
// If the javax.persistence.lock.timeout hint has been specified in the given properties or PersistenceUnit properties
// If javax.persistence.lock.timeout > 0
query.setLockMode(ObjectBuildingQuery.LOCK) 
query.setWaitTimeout(javax.persistence.lock.timeout);
return uow.executeQuery(query);
// Else, javax.persistence.lock.timeout == 0
query.setLockMode(ObjectBuildingQuery.LOCK_NOWAIT);
return uow.executeQuery(query);
// Otherwise just a straight up lock is used
query.setLockMode(ObjectBuildingQuery.LOCK) 
return uow.executeQuery(query);

// If the LockModeType is READ, WRITE, OPTIMISTIC, OPTIMISTIC_FORCE_INCREMENT
// If the eclipselink.refresh query hint is set to true (or not set at all)
Object obj = uow.executeQuery(query);
uow.forceUpdateToVersionField(obj, (lockMode == WRITE || lockMode == OPTIMISTIC_FORCE_INCREMENT));
return obj;
// Otherwise with an eclipselink.refresh query hint set to false
Object obj = query.getSelectionObject();
uow.forceUpdateToVersionField(obj, (lockMode == WRITE || lockMode == OPTIMISTIC_FORCE_INCREMENT));
return obj;

Note: See org.eclipse.persistence.internal.jpa.QueryHintsHandler for the full list of available query hints that may be applied in this case.

Query Interface

JPA queries now accept a lock mode type through the use of the following API:

Query.setLockMode(LockModeType)

  1. The query will then issue the necessary lock when either getResultList(), getResultCollection() or getSingleResult() is executed.
  2. Calling setLockMode on an invalid query type will cause an IllegalStateException to be thrown

Query.setHint(String, Object)

A query lock timeout may also be used in conjunction with the lock mode (or by itself) using the following API:

Query.setHint("javax.persistence.lock.timeout", 5)

  1. When the lock timeout hint is used in conjunction with a PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT lock mode type, a javax.persistence.LockTimeoutException will be thrown if the locking fails.
  2. If no lock timeout hint is used in conjunction with a PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT lock mode type, then a javax.persistence.PessimisticException will be thrown if the locking fails.
  3. When the lock timeout hint is used with any other lock mode type, a QueryTimeoutException will be thrown if the execution of the query takes longer the time out value.
Internal execution

For all query executions types that use a lock mode type, the following logic will apply to the executing query:

Pre-execution logic

// If the lock mode is PESSIMISTIC or PESSIMISTIC_FORCE_INCREMENT
// If the javax.persistence.lock.timeout hint has been specified for the query or a default PersistenceUnit properties one exists
// If javax.persistence.lock.timeout > 0
query.setLockMode(ObjectBuildingQuery.LOCK) 
query.setWaitTimeout(javax.persistence.lock.timeout);
// Else, javax.persistence.lock.timeout == 0
query.setLockMode(ObjectBuildingQuery.LOCK_NOWAIT);
return uow.executeQuery(query);
// Else just a straight up lock is used
query.setLockMode(ObjectBuildingQuery.LOCK) 

Query is executed on the active session

Object result = getActiveSession().executeQuery(getDatabaseQuery(), parameterValues);

Post-execution logic

// If the lock mode is READ, WRITE, OPTIMISTIC or OPTIMISTIC_FORCE_INCREMENT
// For each object in the result (which may be a single object or collection of obects)
((UnitOfWorkImpl) getActiveSession()).forceUpdateToVersionField(obj, lockMode == WRITE || lockMode == OPTIMISTIC_FORCE_INCREMENT);                    

Important Notes

  1. A considerable amount of behaviour definition will be found in the java docs of the EntityManger and Query APIs
    1. for instance upon issuing a pessimistic lock call on find if the version from the database does not match that of the persistence context then an exception must be raised
  2. A Pessimistic lock will issue a SELECT ... FOR UPDATE call.
    1. Used with a javax.persistence.lock.timeout value of 0, a SELECT ... FOR UPDATE NOWAIT is issued
    2. Used with a javax.persistence.lock.timeout value > 0, a SELECT ... FOR UPDATE WAIT n is issued
      1. The WAIT n option is currently only supported on the Oracle platform.
        1. The platform looks for error code 30006 and throws the LockTimeoutException is it is received.
      2. Other platforms do not use this clause and therefore, the users can only ever get a PessimisticLockException

Work Required

  1. Develop tests for testing access type settings
    approx 3 days
  2. Update Processing to process entire table
    approx 2 days - optimistic modes
    approx 4 days - pessimistic modes