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-

database lost update
Sequence of Events for a typical Lost Update Scenario
Mid Air Collision in JIRA

When two people try to update the same Bug in JIRA, a mid air collision happens and JIRA handles it gracefully. The programmers at JIRA know how to prevent lost updates!

There are mainly two approaches to handle this Scenario -

  1. Optimistic Locking

  2. 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.

How does it work?

Each record in database maintains a version number. When first transaction reads record from DB, it would receive the version too. Upon modification the server compares the record’s version number with that in the database, and if not changed, then record is updated written with an incremented version number.

A second transaction would then come in with the same record number (i.e. the record is being updated by both clients). This time the server would recognise that the version number has changed in the database by first transaction, and reject the update.

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

Using long attribute for versioning (Preferred Approach)
@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.

Using timestamps for optimistic locking (Should not be preferred)
@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

  1. The advantages of optimistic locking are that no database locks are held which can give better scalability.

  2. 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.

LockMode.UPGRADE

acquired upon explicit user request using SELECT …​ FOR UPDATE on databases which support that syntax.

LockMode.UPGRADE_NOWAIT

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

Example of Pessimistic Locking using Spring Data JPA
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.
Pessimistic Locking using Spring & Hibernate
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 -

  1. It makes our code simpler, handling concurrency at database level is easier.

  2. 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.

  3. Even if you have single JVM setup, optimistic locking may yield higher performance compared to your own hand woven synchronized Java code.

References


Buy my ebook for complete question bank

Most of these questions has been answered in my eBook "Cracking the Core Java Interview" updated on June 2018, that you can buy from this link:

Buy from Shunya (DRM Free PDF download with updates)

Top articles in this category:
  1. N+1 problem in Hibernate & Spring Data JPA
  2. Table backed global counter in spring hibernate
  3. How to prevent duplicate form submission in Spring MVC
  4. Spring RestTemplate Basic Authentication
  5. Redis rate limiter in Spring Boot
  6. Disable SSL validation in Spring RestTemplate
  7. Elasticsearch with Spring Boot + Spring Data

Recommended books for interview preparation:

Find more on this topic:
Buy interview books

Java & Microservices interview refresher for experienced developers.