In multi-user enterprise applications, concurrency control is critical. Multiple users may attempt to read or modify the same database records simultaneously. Without proper control, this leads to issues like dirty reads, lost updates, and inconsistent data.
Hibernate provides two main locking strategies to handle concurrency: Optimistic Locking and Pessimistic Locking.
Think of it like reserving a library book:
- Optimistic Locking assumes no one else will borrow it, but checks before updating.
- Pessimistic Locking locks the book immediately so no one else can take it while you’re reading.
In this tutorial, we’ll cover both approaches with practical Hibernate examples.
Why Locking Matters in Hibernate
When multiple transactions operate concurrently, issues may arise:
- Lost Update: Two users update the same record, and one update is lost.
- Dirty Read: A transaction reads uncommitted changes from another transaction.
- Non-Repeatable Read: A record changes between two reads in the same transaction.
Locking ensures data integrity and prevents inconsistent results.
Optimistic Locking in Hibernate
Optimistic locking assumes conflicts are rare. It doesn’t block other transactions but detects conflicts when updating.
Setup: @Version
Annotation
Add a @Version
column to track changes.
@Entity
@Table(name = "accounts")
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String owner;
private Double balance;
@Version
private int version; // used for optimistic locking
}
How It Works
- When an entity is read, its
version
is stored. - On update, Hibernate compares the current DB version with the stored version.
- If they match, the update proceeds and version increments.
- If not, an OptimisticLockException is thrown.
Example
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Account acc = session.get(Account.class, 1L);
acc.setBalance(acc.getBalance() + 100);
tx.commit();
session.close();
If another transaction updated the same record in between, Hibernate throws an exception.
Pessimistic Locking in Hibernate
Pessimistic locking assumes conflicts are likely. It prevents concurrent modifications by locking rows at the database level.
Using LockMode in Hibernate
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Account acc = session.get(Account.class, 1L, LockMode.PESSIMISTIC_WRITE);
acc.setBalance(acc.getBalance() + 100);
tx.commit();
session.close();
Here, Hibernate issues a SELECT ... FOR UPDATE
query, blocking other transactions from modifying the row until the lock is released.
Pessimistic Lock Options
Account acc = session
.createQuery("FROM Account a WHERE a.id = :id", Account.class)
.setParameter("id", 1L)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.getSingleResult();
PESSIMISTIC_READ
: Prevents updates but allows reads.PESSIMISTIC_WRITE
: Prevents both reads and updates.PESSIMISTIC_FORCE_INCREMENT
: Increments version even without changes.
Optimistic vs Pessimistic Locking: Comparison
Feature | Optimistic Locking | Pessimistic Locking |
---|---|---|
Strategy | Detect conflicts | Prevent conflicts |
Performance | Lightweight | Heavier |
Use Case | Low contention systems | High contention systems |
Error Handling | Throws exception on conflict | Blocks until lock release |
Implementation | @Version |
LockMode / FOR UPDATE |
Real-World Use Cases
-
Optimistic Locking:
Online shopping cart where updates are rare. -
Pessimistic Locking:
Banking transactions where multiple users may try to update the same account balance simultaneously.
Common Pitfalls & Anti-Patterns
- Using Pessimistic Locks Everywhere → Hurts scalability.
- Ignoring Optimistic Exceptions → Can cause silent data loss.
- Long Transactions with Locks → Leads to deadlocks.
- Mixing Locking with Batch Updates → May bypass lock control.
Best Practices
- Use Optimistic Locking by default in most applications.
- Switch to Pessimistic Locking only when conflicts are frequent.
- Keep transactions short-lived to avoid deadlocks.
- Always handle
OptimisticLockException
gracefully (e.g., retry logic). - Monitor SQL logs to ensure correct lock modes are applied.
📌 Hibernate Version Notes
Hibernate 5.x
- Uses
javax.persistence
package. - Supports
@Version
and LockMode APIs. - Pessimistic locks rely on database-specific SQL (
SELECT ... FOR UPDATE
).
Hibernate 6.x
- Migrated to
jakarta.persistence
. - Improved lock handling and query hints.
- Enhanced SQL support for lock timeouts.
- Better exception handling for optimistic lock failures.
Conclusion & Key Takeaways
- Optimistic Locking is efficient for low-contention scenarios.
- Pessimistic Locking ensures stricter control in high-contention cases.
- Choose the right strategy based on your workload.
- Always test with production-like concurrency to validate correctness.
FAQ: Expert-Level Questions
Q1: What’s the difference between Hibernate and JPA?
Hibernate is a JPA implementation with extended features.
Q2: How does Hibernate caching improve performance?
By reducing database calls through first-level and second-level caching.
Q3: What are the drawbacks of eager fetching?
It can load unnecessary data, slowing down performance.
Q4: How do I solve the N+1 select problem in Hibernate?
Use fetch joins, batch fetching, or entity graphs.
Q5: Can I use Hibernate without Spring?
Yes, Hibernate can run standalone with SessionFactory
.
Q6: What’s the best strategy for inheritance mapping?
Depends on requirements: SINGLE_TABLE
, JOINED
, or TABLE_PER_CLASS
.
Q7: How does Hibernate handle composite keys?
Using @Embeddable
+ @EmbeddedId
or @IdClass
.
Q8: How is Hibernate 6 different from Hibernate 5?
Hibernate 6 uses jakarta.persistence
, has improved SQL, and enhanced query APIs.
Q9: Is Hibernate suitable for microservices?
Yes, but for lightweight services, jOOQ or MyBatis may be preferable.
Q10: When should I not use Hibernate?
Avoid Hibernate when performance-critical queries demand low-level SQL control.