When your Java application has multiple transactions reading or modifying the same record in the database, data concurrency is a major issue that you need to address. A concurrency control mechanism, which applies locks to the records, enables data integrity and avoids any conflict of data. The two main types of locks used are pessimistic locking and optimistic locking.
In pessimistic locking, the object is locked when it is initially accessed for the first time in a given transaction. The lock then is released only when the transaction completes; the object is not accessible for any other transactions during the transaction.
In optimistic locking, the object is not locked when it is accessed for the first time in the transaction. Instead, its state (generally the version number) is saved. When other transactions that are accessing the same object try to modify the state of the object, the present state and the saved state are compared. If the states differ, then it’s a clear indication of a conflicting update and the transaction will be rolled back.
Version 1.0 of the Java Persistence API (JPA) specification, which offered just the fundamental features of an object relational mapping (ORM) framework, supported only optimistic locking. JPA 2 added pessimistic locking support, bringing it more in line with the locking features in the Hibernate ORM framework. In this article, we explain pessimistic locking in JPA 2 and Hibernate and compare how the two technologies implement this feature.
Pessimistic Locking in JPA 2
JPA 2 supports pessimistic locking, with three new locking modes added to the existing optimistic locking such as like READ and WRITE. In all, these are all the locking modes available in JPA (the pessimistic locking modes are bold):
- READ (JPA1)
- WRITE (JPA1)
- OPTMISTIC (Synonymous to READ)
- OPTIMISTIC_FORCE_INCREMENT (Synonymous to WRITE)
- PESSIMISTIC_READ (JPA2)
- PESSIMISTIC_WRITE (JPA2)
- PESSIMISTIC_FORCE_INCREMENT (JPA2)
In this section, we explore the JPA 2 locking modes for pessimistic locking.
PESSIMISTIC_READ mode generally represents a shared lock. In this mode the EntityManager holds the lock on an entity during read operations as soon as the transaction begins. It is not released until the transaction is completed. This lock is best used when you access data that is not frequently modified, as it allows other transactions to read the entity.
Here is an example of using PESSIMISTIC_READ:
......// Transaction t1 beginst1.begin();// Employee entity is read from the databaseEmployee e = em.find(Employee.class, 1);// Lock is performed after readem.lock(e, LockModeType.PESSIMISTIC_READ);............// Transaction is committedt1.commit();...
At the time of writing, most of the persistence providers such as EclipseLink and Hibernate provided support for PESSIMISTIC_READ through PESSIMISTIC_WRITE.
The PESSIMISTIC_WRITE mode generally represents an exclusive lock. In this mode the EntityManager holds the lock on an entity as soon as the entity is updated in a transaction. This lock is best used in when there is a very high probability of update failure due to multiple transactions accessing the object.
Here is an example of using PESSIMISTIC_WRITE:
......// Transaction t1 beginst1.begin();// Employee entity is read and write lock is appliedEmployee e = em.find(Employee.class, 1, LockModeType.PESSIMISTIC_WRITE);............// Transaction is committedt1.commit();...
In this mode the EntityManager holds the lock on an entity when a transaction reads the entity. The version number is incremented towards the end of the transaction, irrespective of whether the entity was updated or not.
To understand the concept of PESSIMISTIC_FORCE_INCREMENT better, suppose two transactions (t1 and t2) occur concurrently. The t1 transaction locks the department instance by using PESSIMISTIC_FORCE_INCREMENT to ensure that no other transaction (say, t2) can update the state of the same department instance. In the first code snippet below, a lock is performed on the department entity and a flush method of the EntityManager is invoked so that the version number is incremented in the database even if the entity is not modified. After flush, the thread is made to sleep for a while. At the same time, another transaction (t2) tries to update the same department instance, which results in an exception and the t2 transaction getting rolled back.
// Transaction1 – t1 // Transaction t1 beginst1.begin();// Get an instance of Employee entity with Id 1Emp e = em.find(Emp.class, 1);// Get the department of Employee with Id 1 which returns DEP 'A'Dep d = e.getDept();// Perform lock on department entity dem.lock(d, LockModeType.PESSIMISTIC_FORCE_INCREMENT);// Flush the entity manager, so that it increments the version numberem.flush();// Make the Thread to sleepThread.sleep(20000);// Transaction is committed after the thread resumes...t1.commit();// Transaction 2 – t2// Transaction t2 beginst2.begin();// Get an instance of Department entity with Id 'A'Dep d = em.find(Dep.class, 'A');// Modify the state of the Department entityd.setDeptName("RESEARCH");// Transaction is committedt2.commit();
You can lock the entities in JPA 2 using the
find() methods of the EntityManager. When
EntityManager.refresh() is invoked it refreshes the state of the entity and applies a lock to it. You can also use the
setLockMode() method of the Query interface to set a lock mode to an entity. For
NamedQuery annotations, you can use the
Pessimistic Locking in Hibernate
With Hibernate the database obtains the locking and the framework never locks the objects in memory. The LockMode class provides the following set of locks, which can be obtained in a Hibernate application:
- LockMode.WRITE: This lock is obtained when Hibernate updates or inserts a new row. LockMode.WRITE is an internal mode (default) and is not specified in the application because it is obtained automatically when a new row is inserted to the database by the framework.
- LockMode.UPGRADE: This lock is obtained when a user makes an explicit request using
SELECT .. FOR UPDATE. This lock is valid only on databases that support the
SELECT .. FOR UPDATESQL syntax.
- LockMode.UPGRADE_NOWAIT: This lock is acquired when a user makes an explicit request using
SELECT. . FOR UPDATE NOWAIT, which is supported only by Oracle.
- LockMode.READ: This lock is obtained automatically when the framework reads the data under Repeatable Read or Serializable Isolation mode. A version check is performed to compare the version number of the object in memory to the record in the database. This lock can be acquired explicitly by invoking Hibernate APIs (more on this shortly).
- LockMode.NONE: This represents the absence of a lock. At the end of each transaction objects attain this lock mode.
The user can acquire locks explicitly by invoking one of the following Hibernate API:
Session.load(): This method is invoked by specifying the locking mode as UPGRADE or UPDATE_NOWAIT . If the object is not loaded by session, then
SELECT .. FOR UPDATEis executed to load the object. If the object is loaded with a less restrictive lock, then the framework invokes
lock()for that object.
Session.lock(): This method performs a version number check when the lock mode is READ, UPGRADE or UPGRADE_NOWAIT. When the lock mode is UPGRADE or UPGRADE_NOWAIT, the
SELECT .. FOR UPDATEstatement is used.
Query.setLockMode(): This method is used to set the lock mode for the object specified in the FROM clause of the Query string.
JPA 2 and Hibernate both support pessimistic locking as a concurrency control mechanism, but not in the same way. In this article, we compared how the two technologies implement this feature.
The authors would like to sincerely thank Mr. Subrahmanya SV (VP, ECOM Research Group, E&R) for his ideas, guidance, support and constant encouragement and Ms. Mahalakshmi for kindly reviewing this article and providing valuable comments.
About the Authors
Sangeetha S. works as a Senior Technical Architect at the E-Commerce Research Labs at Infosys Technologies. She has over 10 years of experience in design and development of Java and Java EE applications. She has co-authored a book on ‘J2EE Architecture’ and also has written articles for online Java publications.
Nitin KL works at the E-Commerce Research Labs at Infosys Technologies. He is involved in design and development of Java EE applications using Hibernate, iBATIS, and JPA.