Concurrency Control in Java Persistence API (JPA) with Hibernate: Optimistic vs. Pessimistic Locking
In the context of Java Persistence API (JPA) and Hibernate, optimistic locking and pessimistic locking are two strategies used to handle concurrent access to database records by multiple users or threads. These locking mechanisms help ensure data consistency and prevent conflicts when multiple entities attempt to read or modify the same database records simultaneously.
Why Locking Necessary : Concurrent Data Access Problem Scenarios
- Two or more users or threads read the same database record simultaneously and update it independently.
- Result: Only one of the updates is persisted, and the changes made by other transactions are lost.
2. One transaction updates a record, and another transaction reads the record before the first transaction commits its changes.
- Result: The second transaction reads uncommitted data, which may be rolled back later, leading to incorrect information.
3. A transaction reads a record, and another transaction modifies or deletes the same record before the first transaction completes.
- Result: The first transaction encounters different data when it reads the same record again, causing inconsistency.
4. A transaction queries a set of records that meet certain criteria. Another transaction inserts or deletes records that match the criteria.
- Result: The first transaction, when re-executed, may retrieve a different set of records, leading to unexpected results.
5. Multiple transactions attempt to Concurrent insert new records simultaneously without any synchronization.
- Result: Data integrity violations, such as primary key or unique constraint violations, can occur when two transactions try to insert records with the same key values.
Solution to Concurrency Database Access Issues
To address these issues and maintain data consistency in a concurrent database environment, locking mechanisms like optimistic and pessimistic locking are used. These mechanisms help coordinate access to database records and ensure that transactions can proceed safely without causing conflicts or inconsistencies.
Optimistic Locking:
Optimistic locking is a concurrency control strategy where multiple transactions can read data concurrently, but they must check for conflicts before making updates. It assumes that conflicts between transactions are infrequent, and it minimizes the time that records are locked, thus reducing contention.
How Optimistic Locking Works (@Version Annotation):
Read Phase:
- When a transaction reads a record, it also retrieves and stores the current version of that record. This version is typically stored in a version column in the database.
- The transaction uses this version to track changes to the record while it works with it.
Update Phase:
- When the transaction attempts to update the record, it checks whether the version of the record in the database matches the version it initially read.
- If the versions match, it proceeds with the update, assuming no other transaction has modified the record in the meantime.
- If the versions do not match, it means that another transaction has modified the record, and an
OptimisticLockException
is thrown.
Scenario Inventory Quantity: Concurrency Problem:
- Alice loads a product with a quantity of 5 from the database.
- A warehouse batch process updates the same product’s quantity to 0 without Alice’s knowledge.
- Alice attempts to purchase the product by decrementing quantity by 1.
- Since Alice’s purchase code is unaware of the warehouse update, it reads the quantity as 5 and proceeds to decrement it by 1.
- The result is that Alice’s update sets the quantity to -1, which is an incorrect state.
- The lost update problem occurs because Alice thinks there are still products available for purchase, but in reality, there are none left due to the earlier batch process update.
How Optimistic Locking Solves Our Concurrency Problem
- Create the
Product
Entity with Optimistic Locking:
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int quantity;
@Version
private int version; // Optimistic locking version field
// getters and setters
}
Step 1: Alice loads a Product with a quantity of 5. The initial version is 1.
SELECT id, name, quantity, version FROM Product WHERE id = ?;
==============================================================
| id | name | quantity | version |
|----|------------|----------|---------|
| 1 | Product A | 5 | 1 |
Step 2: The warehouse batch process updates the Product quantity to 0 and increments the version to 2.
UPDATE Product SET quantity = 0, version = 2 WHERE id = ? AND version = 1;
==========================================================================
| id | name | quantity | version |
|----|------------|----------|---------|
| 1 | Product A | 0 | 2 |
Step 3: Alice, unaware of the warehouse batch update, attempts to buy the Product by decrementing the quantity and executing an UPDATE.
UPDATE Product SET quantity = 4, version = 1 WHERE id = ? AND version = 1;
However, this UPDATE fails because the version in the database (2) no longer matches the version Alice initially read (1).
- Since the versions do not match, Hibernate throws an
OptimisticLockException
.Alice’s code can catch and handle this exception, allowing her to respond appropriately, such as notifying the user that the product is no longer available for purchase or giving her the option to retry the purchase.
In this scenario, optimistic locking helps maintain data consistency by detecting the concurrent modification and preventing Alice from making an update based on outdated information.
Pessimistic Locking:
Pessimistic locking is a strategy where a transaction locks a database record explicitly to prevent other transactions from accessing or modifying it until the lock is released. It assumes that conflicts are more likely to occur and prioritizes data consistency over concurrency.
You use the LockModeType
enumeration to specify the type of pessimistic lock you want when querying an entity.
Product product = em.find(Product.class, productId, LockModeType.PESSIMISTIC_WRITE);
- PESSIMISTIC_READ: Other transactions can read the entity but cannot obtain a write lock.
- PESSIMISTIC_WRITE: Other transactions are blocked from reading or writing to the locked entity until the lock is released.
Scenario Account Balance Transfer: Concurrency Problem:
- Alice reads the account balance, which is 50.
- Bob changes the account balance to 20 and commits.
- Alice, unaware of Bob’s update, still believes the balance is 50 and attempts to withdraw 40.
- Alice’s withdrawal, based on the outdated balance of 50, results in an incorrect negative balance (-20).
How JPA locking would solve the problem
To prevent such anomalies, databases typically employ concurrency control mechanisms. Two common approaches are:
Solving With Pessimistic Locking: In this approach, Alice would request a lock on the account when reading the balance. This lock would prevent Bob from making changes to the balance until Alice’s transaction is complete. However, this can lead to reduced concurrency as other users would have to wait for Alice to finish.
Solving With Optimistic Locking: In this approach, both Alice and Bob can read the balance without locking it. However, when Alice attempts to update the balance, the system checks by Version if the balance has been modified since Alice initially read it. If it has, Alice’s update would fail, and she would need to re-read the balance and make her withdrawal based on the updated value. This approach allows for greater concurrency but requires careful handling of conflicts.
Choosing Between Optimistic & Pessimistic Locking
In practice, many applications use a combination of both locking mechanisms based on the specific requirements of different parts of the system. For example, you might use pessimistic locking in critical financial transactions but rely on optimistic locking for less critical data
Use Pessimistic Locking When:
- Data Consistency Is Critical: If your application deals with highly critical data where data consistency is of utmost importance (e.g., financial transactions), pessimistic locking is a safer choice. It ensures that only one transaction can access the data at a time, preventing conflicts.
- Short, Critical Sections: Pessimistic locking is beneficial when you need to protect short, critical sections of code or data. Because it may hold locks for a long time.
Use Optimistic Locking When:
- High Concurrency Is Required: If your application has a high concurrency requirement and can tolerate occasional conflicts, optimistic locking is a better choice. It allows multiple transactions to work on data simultaneously until they attempt to commit changes.
- Reduced Locking Overhead: Optimistic locking generally incurs lower locking overhead compared to pessimistic locking, which can lead to better system performance and scalability.
- Read-Heavy Workloads: Optimistic locking is well-suited for applications with read-heavy workloads, as it allows concurrent reading without locking.
Concurrency Problem in data access scenario & Solution Use Cases
Developers may encounter various real-life scenarios and problems related to JPA locking when working with database transactions. Here are some relevant scenarios and the issues they might face:
Inventory Management in an E-commerce System:
Problem: When multiple users try to purchase the last available item of a product, they can potentially place orders simultaneously. This can lead to overselling, where the system accepts more orders than there are available items in stock. Consequently, customers might be disappointed when they realize that the product is out of stock after placing an order.
Solution
- Optimistic Locking: When a user adds a product to their cart, the system checks the product’s availability. Before confirming the order, it verifies that the product’s stock quantity has not changed since it was added to the cart. If another user purchases the last available item simultaneously, the system detects the version mismatch and prevents the user confirming the order and prevents overselling.
- Pessimistic Locking: Before a user proceeds to purchase, the system locks the product’s stock quantity exclusively. Only one user can modify the stock at a time. This prevents concurrent purchases of the same product and guarantees that the stock is decremented accurately.
Booking Seats in a Theater or Flight Reservation System:
- Problem: In a scenario where there are only a few seats left for a popular event, multiple customers may attempt to book the same seats concurrently. Without locking, the system may allow multiple bookings for the same seats, leading to overbooking and disputes when customers arrive at the venue to find their seats occupied by others.
- Solution:
- Optimistic Locking: When customers select seats, the system checks seat availability. If two users attempt to book the same seat concurrently, the system will detect the conflict during the reservation confirmation step, allows reading all users with the currently available seats count but prevents while confirming and prevent overbooking.
- Pessimistic Locking: Before allowing a customer to book a seat, the system locks the seat exclusively, ensuring that only one booking can occur at a time. This guarantees that the seat is not double-booked.
Banking Transactions:
- Problem: If multiple customers initiate funds transfers from a shared bank account simultaneously, there’s a risk that two or more transactions could withdraw funds from the same account at the same time. This can result in incorrect account balances, overdrafts, and financial discrepancies.
- Solution
- Optimistic Locking: During a funds transfer, the system checks the account balance before and after the transaction. If multiple users initiate transfers concurrently, the system allows only one to successfully make transaction and other responds with exception, detects the version mismatch and prevents overdrafts or discrepancies.
- Pessimistic Locking: When a user starts a funds transfer, the system locks the account exclusively until the transaction is complete. This prevents other transactions from accessing the same account simultaneously, ensuring accurate balances.
Order Processing in a Restaurant POS System:
- Problem: Without locking, multiple waitstaff members taking orders at a particular restaurant table can modify or delete each other’s orders. This can result in incorrect bills, missing orders, or duplicated items in customer orders, leading to operational inefficiencies.
- Solution
- Optimistic Locking: When waitstaff take orders, the system checks the order’s current status and the items selected. If multiple waitstaff members attempt to modify the same order concurrently, the system detects conflicts and requires manual resolution.
- Pessimistic Locking: When a waitstaff member takes an order, the system locks the order exclusively until it’s finalized. This prevents other staff from modifying the same order simultaneously, ensuring order accuracy.
Inventory Price Updates in a Supermarket Chain:
- Problem: When employees update product prices across multiple stores in a retail chain, concurrent updates can occur. Without pessimistic locking, these updates may not be synchronized, leading to inconsistent pricing information across stores. Customers might be charged different prices for the same product at different locations.
- Solution
- Optimistic Locking: During price updates, the system checks the current price and updates it. If two employees update the same product’s price concurrently, the system detects the version mismatch and prevents inconsistent pricing.
- Pessimistic Locking: Before an employee updates a product’s price, the system locks the product exclusively for the duration of the update. This ensures that no other updates can occur simultaneously, maintaining pricing consistency.
Reservation System for Conference Rooms:
- Problem: In a corporate environment, multiple employees may try to reserve the same conference room for a meeting without realizing that someone else is already in the process of booking it. Without pessimistic locking, the room can be double-booked, causing scheduling conflicts and disruptions to planned meetings.
- Solution
- Optimistic Locking: When employees try to book a room, the system checks the room’s availability. If two employees attempt to book the same room concurrently, the system detects the conflict and requires manual resolution.
- Pessimistic Locking: Before an employee reserves a room, the system locks the room exclusively, preventing others from reserving it at the same time. This guarantees that no double-bookings occur.