Optimistic locking version property with JPA and Hibernate

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

In this article, we are going to see how optimistic locking version property works when using JPA and Hibernate.

Most often, we overlook basic concepts and focus only on more advanced topics such as associations or queries, without realizing that basic mappings can also have a significant impact when it comes to persistence effectiveness and efficiency.

Lost updates anomaly

Relational database systems have emerged in the time of mainframe systems when the client was interacting with the database from a terminal using a dedicated connection. Back then, when the client needed to operate on the database, a session was opened and all the reads and write happened in the scope of the same database transaction.

This way, the concurrency control mechanism could ensure that reads and writes coming from multiple concurrent clients can be properly interleaved so that they don’t break serializability.

However, this session-based database interaction is no longer the norm, especially when accessing services over the Internet. This is because you can no longer hold onto a database connection across multiple HTTP requests. For this reason, the ACID guarantees no longer hold when your application-level transaction spans over multiple HTTP requests as well as physical database transactions.

One example of an anomaly that can happen because reads and writes happen in different database transactions is the lost update phenomenon.

To understand the lost update anomaly, consider the following example:

Application-level transaction lost update anomaly

The flow of actions happens like this:

  1. Alice loads a Product having a quantity of 5.
  2. Right after, a warehouse batch process updates the Product quantity to 0.
  3. Alice decides to buy the Product, so the quantity will be decreased, resulting in a negative quantity value.

Optimistic locking version property

To address this issue, a @Version column can be used:

@Entity(name = "Product")
@Table(name = "product")
public class Product {

    @Id
    private Long id;

    private int quantity;

    @Version
    private int version;

    //Getters and setters omitted for brevity
}

This way, when the Product entity is updated:

Product product = entityManager.find(
    Product.class, 
    1L
);

product.setQuantity(0);

Hibernate includes the version column in the row filtering criteria:

UPDATE 
    product 
SET 
    quantity = 0, version = 2 
WHERE 
    id = 1 AND version = 1

Notice that the version column is set to the next value while the previous value that was read when the entity was fetched from the database is used to filter the record.

So, if the version column value has changed, the UPDATE will not take place and a value of 0 will be returned by the executeUpdate JDBC PreparedStatement method.

When Hibernate reads an update count value of 0, and a javax.persistence.OptimisticLockException will be thrown.

The version property is also used for the DELETE SQL statement, so, if we remove the Product entity:

Product product = entityManager.getReference(
    Product.class, 
    1L
);

entityManager.remove(product);

Hibernate executes the following DELETE statement:

DELETE FROM 
    product 
WHERE 
    id = 1 AND 
    version = 1

Notice that the version column is used in the WHERE clause to make sure that we are deleting the very same entity snapshot that we have previously read from the database.

Now, back to our previous example, if we are now using a version column, we can prevent the lost update as illustrated by the following diagram:

Application-level transaction prevents lost update using a version column

This time, the flow of actions happens like this:

  1. Alice loads a Product having a quantity of 5.
  2. Right after, the warehouse batch process updates the Product quantity to 0.
  3. Alice decides to buy the Product, so show decreases the quantity and tries to execute the UPDATE.
  4. The UPDATE is prevented because the Product version is no longer 1, and Hibernate throws an OptimisticLockException.

Cool, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

Knowing how the optimistic locking version property works is very important when using JPA and Hibernate, as it allows you to prevent the lost update anomaly when a given entity is being modified by multiple concurrent users.

For more details about the lost update phenomenon, and how the database prevents it in the scope of a single database transaction, check out the following article.

FREE EBOOK

Newsletter logo
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence,
  • a 10% discount coupon for my book.

4 Comments on “Optimistic locking version property with JPA and Hibernate

  1. Hello! 🙂

    In the second example, since we are on a cross-request case, wouldn’t Alice have to pull the record first before reducing the quantity?

    That would give her the new version (2) and thus she would be able to save with the -1 quantity.

    Isn’t optimistic locking guarding us from concurent changes? The issue here would be if Alice read the record before the BatchJob commited its transaction right?

    Am I missing something?

    Great articles, thanks a lot for the great work! 🙂

    • What if the batch process writes after Alice re-reads the record? Of course, you can’t rely on check-modift-write antipatterns that don’t materialize the conflict on the database. Check out my High-Performance Java Persistence book for more details about this topic.

      • Hello and thanks for the reply!

        If the batch job writes after Alice rereads then she will get an OptimisticLockException, right?

        I’ll check it out on the book, thanks! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.