Prevent Lost Updates in Database Transaction using Spring Hibernate
Upasana | August 31, 2019 | 5 min read | 3,933 views
What is Lost Update?
When two concurrent transactions simultaneously update the same database record/column, resulting in first update being silently overwritten by the second transaction. This phenomenon in Database is known as classic problem of Lost Updates. Below are the sequence of events in case of lost updates-
There are mainly two approaches to handle this Scenario -
-
Optimistic Locking
-
Pessimistic Locking
We will discuss both of these approaches one by one with proper examples,
Optimistic Locking Approach
All users/threads can read the data concurrently, but when more than one thread tries to update it simultaneously, the first one will win and all others will fail with OptimisticLockException, they have to try executing the update once again. So no update will be silently lost even in case of concurrent usage.
JPA Optimistic locking allows anyone to read and update an entity, however a version check is made upon commit and an exception is thrown if the version was updated in the database since the entity was last read.
How to enable Optimistic Locking in JPA?
To enable Optimistic Locking for an entity in JPA, you just have to annotate an attribute with @Version
, as shown in below code example
@Entity
@Table (name="t_flight")
public class Flight {
@ID
@GeneratedValue (strategy=GenerationType.AUTO)
private int id;
@Version (1)
private long version;
...
}
1 | Just two lines of code enables Optimistic Locking in JPA. |
It is important to note down that only short, int, long and Timestamp fields can be annotated with @Version attributes.
Timestamps are a less reliable way of optimistic locking than version numbers, but can be used by applications for other purposes as well.
@Entity
@Table ( name = "t_flight" )
public class Flight implements Serializable {
...
@Version
public Date getLastUpdate() { ... }
}
Under the hood
Under the hood, JPA will increment the Version attribute upon every successful commit.
This results in SQL like the following (note that JPA handles it all, its shown for illustration purpose only):
UPDATE Flight SET ..., version = version + 1 WHERE id = ? AND version = readVersion
Pros and Cons of Optimistic Approach
-
The advantages of optimistic locking are that no database locks are held which can give better scalability.
-
The disadvantages are that the user or application must refresh and retry failed updates.
Pessimistic Locking Approach
When using pessimistic locking, hibernate locks the record for your exclusive use until you commit the transaction. This is typically achieved using SELECT … FOR UPDATE
statement at the database level. In this case, any other transaction trying to update/access the same record will be blocked until the first transaction releases the lock.
This strategy gives better predictability at the price of performance and does not scale much. Internally it behaves like sequential access to a single record by all threads (read and write), that’s why scalability is a problem.
Typically, if you just specify the appropriate Isolation Level at the transaction level (Serializable), database will handle it automatically for you. Hibernate gives you options to obtain a exclusive pessimistic lock at the start of a new transaction. Below LockMode can be used to obtain a pessimistic lock in hibernate session.
acquired upon explicit user request using SELECT … FOR UPDATE
on databases which support that syntax.
acquired upon explicit user request using a SELECT … FOR UPDATE NOWAIT
in Oracle.
Please be noted that using above UPGRADE modes, you want to modify the loaded object and you don’t want any other thread to change it while you are in the process until you commit the transaction.
How to enable Pessimistic Locking in Spring Data JPA & Hibernate
interface Flight extends Repository<Flight, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE) (1)
Flight findOne(Long id);
}
1 | @Lock is supported on CRUD methods as of version 1.6 of Spring Data JPA, once you commit the transaction, lock is automatically released. |
tx.begin();
Flight w = em.find(Flight.class, 1L, LockModeType.PESSIMISTIC_WRITE);
w.decrementBy(4);
em.flush();
tx.commit();
What is difference between Transaction Isolation Level and LockModes?
Isolation Levels affect what you see. Lock Modes affect what you are allowed to do.
Why not to handle Concurrency at Java level rather than database/hibernate level?
When we work with databases that support transaction, the management of concurrency must move away from Java code, and should be handled by database transaction isolation level and locking strategy. The main reasons for this are -
-
It makes our code simpler, handling concurrency at database level is easier.
-
If you manage concurrency at JVM level, your application will break when moved to multiple distributed JVMs, so the solution will never scale. On the other hand, database is single point of entry that can handle concurrency even if multiple JVMs are invoking parallel requests.
-
Even if you have single JVM setup, optimistic locking may yield higher performance compared to your own hand woven synchronized Java code.
Top articles in this category:
- N+1 problem in Hibernate & Spring Data JPA
- Table backed global counter in spring hibernate
- How to prevent duplicate form submission in Spring MVC
- Spring RestTemplate Basic Authentication
- Redis rate limiter in Spring Boot
- Disable SSL validation in Spring RestTemplate
- Elasticsearch with Spring Boot + Spring Data