Entity versioning

In most of the enterprise application, multiple users can access the application concurrently. Suppose two users (User1 & User2) are trying to work on the same database record. Initially when they will fetch the record, it will be same for both the users. If User1 updates or deletes the record while User2 is still working, for User2 the initial record on which he is still working will become invalid. If the application still allow User2 to save the stale record, that can cause serious data issue.

Optimistic locking

Optimistic locking can be considered as a cure it approach rather than prevent it approach. In case of an optimistic locking strategy, we assume concurrency problems will happen rarely. But when that happens, we should be able to detect and handle it.

Consider the earlier example where User1 & User2 are trying to work on the same database record. Initially when they fetch the record, it will be the same for both the users. When User1 deletes the record, it will be deleted from the database successfully. But when User2 tries to update the same record, application will detect that the initial copy has been modified and will throw an exception.

How does JPA detect the change?

The change is detected by comparing an additional version number or timestamp column. Almost all the time we use the version number approach. When an user retrieves a record, the version column is retrieved as part of the entity instance. Also, every time an user updates a database row, the version column value is incremented by JPA. But before updating or deleting a record, JPA checks if the version number of the entity instance is the same as the database row. If it is the same, it means the row is not updated by anyone. If the version number is increased in the database, that means the entity is updated by someone else. Also if the row is missing, that means the entity is already deleted by someone.

Now let’s look at the same example again. We will assume the initial version number is 1.

  1. Both User1 and User2 retrieved the record with version number 1.
  2. When User1 tries to delete the record, the application will check the version number in the database row. It will be 1. So the delete will be successful.
  3. When User2 tries to update the record, there will be no row in the database with the same user id. So the update operation will fail with OptimisticLockingException.
How to configure the entity?

Now the question is how can we configure the entity to enable versioning? To understand this, consider we have an entity called User.

First of all, we have to add an additional column in the database to store the version number. You can give any name you want.

Then, add a new numeric field to the User entity of type long, Long, int, or Integer and map it with the newly added database column. Also, annotate this field with @Version annotation.

@Entity
@Table(name = "USER_DETAILS", schema = "demo")
public class User {
    @Id
    @Column(name = "USER_ID")
    private long userId;

    @Column(name = "USER_NAME")
    private String userName;

    @Version
    @Column(name = "VERSION_NO")
    private Log version;
}

That’s it.

When we create an entity for the very first time, the version field will be populated with 0.

User user = new User();
user.setUserId(1L);
user.setUserName("Tom");
entityManager.persist(user);

When we modify an existing entity instance and update it, the version will increase by 1. But if you try to save the entity without any change, the version will not be updated.

User user = entityManager.find(User.class, 1L);
user.setUserName("Jerry");

If two different user try to update the same entity (version is same) concurrently, first update will be successful, but the last update will fail with following exception –

javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
Change the entity version manually

You should not change the version by yourself. Even if you do so, that will not change the version value. Because, the optimistic locking mechanism doesn’t consider the version from the entity instance. Rather the version when the entity was loaded is considered.

But as you made the entity dirty, Hibernate will unnecessarily issue one UPDATE with existing field value. This update will increase the version by 1.

User user = entityManager.find(User.class, 1L); // version : 1
user.setVersion(10L);

Hibernate will issue following SQL query –

update
    DEMO.USER_DETAILS 
set
    USER_NAME = 'Jerry',
    VERSION_NO = 2
where
    USER_ID = 1 
    and VERSION_NO = 1;

You see, an useless update is triggered just because you have made the entity instance dirty. Just like regular updates, the version is increased by 1.

What should we do if we get an OptimisticLockException?

If you get an OptimisticLockException while updating an entity instance, probably the best option would be to retrieve a fresh copy of that entity again from the database and reapply the changes that you have done earlier.

That’s it for now. Hope you have enjoyed this tutorial. If you have any doubt, please ask in the comment section. I will try to answer that as soon as possible. Till then, bye bye.