Manage Concurrent Access to JPA Entity with Locking

Learn how to handle concurrent access to entity data, and the locking strategies available to JPA API.

Introduction

Database transactions are notoriously messy in managing data integrity. Systems for reservations, banking, credit card processing, stock markets, etc. require high availability and fast response time for hundreds of concurrent users. With the surge of online transactions, developers now have to deal with it more often. Applications dealing with simultaneous transactions should be equipped to handle such a situation efficiently. As we know, 'Isolation' is the fundamental property of 'Transaction' but to manage one is an issue of concern. Without proper management there is the possibility of getting unwarranted results from concurrent transactions due to overlapped CRUD operations. Locking mechanism is an efficient way out of the situation. This mechanism surfaces at different levels. Many popular DBMS packages have their own provisions; JPA is no exception, providing some of the capability of inherent locking mechanism in harnessing the situation.

Locking and its Types

Locking is one of the basic techniques used to control concurrent execution of transactions. This mechanism is actually pretty simple. A lock is like a status variable associated with a data item with respect to possible operations applied to it. This lock is used as a means of synchronizing the access by concurrent transaction to the database item. Classical locking mechanisms have numerous ways of implementation at the database-level but JPA supports two types of locking mechanisms at the entity-level: optimistic model and pessimistic model.

An Example

The code below demonstrates a database transaction without enabling the locking feature. The code snippet down the line will show how to enable this in POJO under JPA.

Optimistic Locking

In optimistic locking, it is assumed that the transaction which changes/modifies the entity is engaged in isolation; in other words, it is assumed that this transaction is the only one playing with the entity currently. Obvious, this model of transaction does not acquire any locks until an actual transaction has been made, which usually is obtained at the end of the transaction. This is only possible when the query is fired to send data to the database and update at flush time. Now what happens when in the meantime some other transaction brings forth changes to the same entity? That means, flushing the transaction must be able to see whether other transactions have intervened with the commit operation. In such a situation, the current transaction simply rolls back and throws an exception called OptimisticLockException. Observe in the table below what happens when a concurrent transaction of withdraw and deposit occurs in an uncontrolled fashion.

Assume initial balance is 1000.

Time

T1

T2

Remarks

1

withdraw()

balance=balance-500

balance=1000-500

2

deposit()

balance=balance+500

balance=1000+500

3

commit(balance)

writes balance = 500

4

commit(balance)

Writes balance=1500 because when balance was read by T2 it was 1000, and writes back 1000+500 = 1500. Previous value committed by T1 gets lost/overwritten

Note: This is a classic example of a lost update problem; there can be many other problems such as dirty read, incorrect summary, etc. found in any database literature.

We can overcome this syndrome with the help of the versioning mechanism of the JPA provider. Add a dedicated persistent property to store the version number of the entity into the database with the annotation @Version as shown below.

Now, during any change, the JPA provider checks the version number; if it matches with the version number it obtained previously then changes can be applied otherwise an exception is thrown. Versioning is the basis of the optimistic locking model of JPA. Optimistic locking is the default model.

Pessimistic Locking

In pessimistic locking, instead of waiting until the commit phase, with blind optimism that no other transaction has intervened and no change in the data item has occurred, a lock is obtained immediately. That is, the objective is to acquire the lock before starting any transaction operation. Thus it leaves no room for transaction failure due to concurrent changes; however, observe that it also leaves no room for parallel execution of transactions. In a sense it violates the principles of concurrency as there can be a lot of unused time left for some other transaction to perform in between lock obtained and lock released. But it has its use in some mission critical situations, where transaction isolation is an absolute necessity. It is also true, in real life that there are very few instances where such pessimism is required. This is one of the primary reasons why optimistic locking is the default model of JPA.

For example, to enable pessimistic WRITE_LOCK in our above application we have to invoke the function lock(a, LockModeType.PESSIMISTIC_WRITE) of EntityManager as follows.

Conclusion

How to manage concurrent access to JPA entity is a big topic. The article gives a glimpse of the phenomenon, a basic understanding of how to implement one in JPA. Bear in mind that locking at the database-level is quite different from locking at entity-level. JPA does not replace, it rather complements the process of concurrency control mechanism. More information on locking and concurrency can be obtained from Java EE 7 tutorial documentation.

Advertiser Disclosure:
Some of the products that appear on this site are from companies from which QuinStreet receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. QuinStreet does not include all companies or all types of products available in the marketplace.