In this article, we’ll learn what the optimistic and pessimistic locking mechanisms are. They both represent mechanisms for handling simultaneous access to data.
Additionally, we’ll learn when to use one or the other mechanism.
When dealing with transactions, most often we focus only on topics such as relations or queries without paying attention to the basic concepts. Sometimes, these basic concepts can have a significant impact on application performance.
Lost Updates Anomaly
Before diving into pessimistic and optimistic locking, let’s consider the Lost updates anomaly.
This problem happens when two transactions want to update the same record in the database.
Say we have two users, Alice and Bob, that are shopping. They both want to buy lemons.
The figure above illustrates the following:
- We have 5 lemons in stock.
- Alice reads the number of lemons which is currently 5.
- Right after, Bob buys 2 lemons. The stock changes from 5 to 3.
- Alice’s transaction is still running with an old value on the stock (5).
- She buys 4 lemons thinking the final value in the stock would be 1.
- However, since the balance has changed, Alice’s update is going to leave the number of lemons in a negative value (-1).
Additionally, the scenario described above is not sequential. Alice’s reading and writing didn’t follow Bob’s. Furthermore, Bob’s changes didn’t prevent Alice from executing her transaction.
We can prevent this situation in two ways - using optimistic and pessimistic locking.
Optimistic locking is a type of locking mechanism that uses the version field from the entities to control any modification on them.
It allows conflict to happen. During the entity modification, the version field from the entity will automatically increment. Further, the same field will be used in the where statement to check if the entity has changed.
When Alice and Bob read the record from the database, they both get the value of the version attribute.
However, when Bob buys the lemons, his transaction updates the version from 1 to 2.
Now, when Alice wants to buy lemons, her update query won’t match any record since the version is no longer 1, but 2. Therefore, her transaction will throw an OptimisticLockException and, consequently, the transaction will end with the rollback.
In this scenario, the lost update problem is prevented by rolling back the transaction.
Optimistic locking searches for the changes using the version attribute. This way, it ensures that any modifications won't be overwritten.
To use optimistic locking, our entity needs to have a field annotated with the @Version:
The entity can have only one attribute with the @Version annotation. The type of the attribute must be Short, Integer, Long (or their primitive equivalents), or Timestamp.
It is important to note we should never update the version attribute by ourselves. Otherwise, we could end up with inconsistent data.
Furthermore, if we include the version attribute, optimistic locking will be enabled by default.
Optimistic Lock Mode
We could use two different optimistic lock modes:
- OPTIMISTIC - lock for entities that contain a version attribute.
- OPTIMISTIC_FORCE_INCREMENT - automatically increments the version attribute.
All the types of locking modes are placed inside the LockModeType class.
There are several ways to request optimistic locking explicitly. Let’s see how.
Using Optimistic Locking
If we are using EntityManager, we could pass the LockModeType as the argument:
Additionally, we can set the optimistic locking with the lock() method:
We could use this type of locking mechanism if our application is mostly reading the data but not modifying it.
Pessimistic locking, on the other hand, doesn’t allow conflict to happen. It avoids conflicts using locking.
It requests a lock on a record before performing any modification on it. This way, we are sure the record will not be modified by some other transaction.
In this scenario, Alice and Bob lock the same record. Since we’re using pessimistic locking, neither of them can modify the record until one releases the lock.
Here, Bob cannot perform an update until Alice releases the lock. A transaction holding the lock prevents other transactions from reading or modifying the record.
We could use pessimistic locking in situations where two or more transactions would like to access the same record at the same time.
Pessimistic Lock Modes
Same as optimistic locking, JPA defines pessimistic lock modes in the LockModeType class:
- PESSIMISTIC_READ - prevents data from being modified.
- PESSIMISTIC_WRITE - prevents other transactions from reading or modifying the data.
- PERRIMISTIC_FORCE_INCREMENT - uses the version attribute.
Using Pessimistic Locking
To request pessimistic locking on the EntityManager, we can use the LockModeType as an argument:
Additionally, pessimistic locking can be enabled using the lock() method on EntityManager:
If we’re using the JPA repository, we could use the @Lock annotation:
Both, pessimistic and optimistic locking, are useful mechanisms.
To sum up, we could use optimistic locking when we have multiple transactions since it doesn’t lock records. Pessimistic locking, on the other hand, can come in handy when implementing the retrying mechanisms would be too expensive.