Prevent database concurrency with @Version JPA and Hibernate
Tags: java, jpa, hibernate
Published: 2022-05-29 14:20:05

When working on a project of big business company, sometime someone will ask "What happens when 2 users update the same record in the database?"

It sound confuse for the new developer, but very easy to avoid it.

In **Java Persistence API** (JPA), there's **@Version** annotation that will help you to check **how many time the database's record has been updated**

Let's have a look at these simple table and entity
```sql
create table device
(
    id      integer not null constraint device_pk primary key,
    serial  integer,
    name    varchar(255),
    version integer
)

```
And the entity
```java
package com.vominh.example.entity;

import javax.persistence.*;

@Entity
@Table(name = "device")
public class DeviceWithVersionEntity {
    @Id
    @Column(name = "id")
    private Integer id;
    @Column(name = "serial")
    private Integer serial;
    @Column(name = "name")
    private String name;
    @Version
    @Column(name = "version")
    private int version;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getSerial() {
        return serial;
    }
    public void setSerial(Integer serial) {
        this.serial = serial;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }
}
```

The part can understand like "**This field will increase by 1 when someone perform update action on it**"
```java
@Version
@Column(name = "version")
private int version;
```

Main class
```java
package com.vominh.example;

import com.vominh.example.entity.DeviceWithVersionEntity;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

import java.util.Random;
import java.util.concurrent.CompletableFuture;

public class ConcurrencyControl {
    private static final SessionFactory sessionFactory;
    private static final ServiceRegistry serviceRegistry;

    static {
        Configuration configuration = new Configuration();
        configuration.configure("hibernate.cfg.xml");

        // Since Hibernate Version 4.x, ServiceRegistry Is Being Used
        serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);
    }

    public static void main(String[] args) {
        Session session = sessionFactory.openSession();
        Query deleteAllDevice = session.createQuery("delete from DeviceWithVersionEntity");

        try {
            session.beginTransaction();
            deleteAllDevice.executeUpdate();

            DeviceWithVersionEntity device = new DeviceWithVersionEntity();
            device.setId(1);
            device.setSerial(8888);
            device.setName("Dell xps 99");

            session.save(device);

            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            session.getTransaction().rollback();
        } finally {
            session.close();
        }

        // open 50 session in 50 thread to update one record
        for (int i = 0; i < 50; i++) {
            CompletableFuture.runAsync(() -> {
                var s = sessionFactory.openSession();
                try {
                    s.beginTransaction();

                    DeviceWithVersionEntity d = (DeviceWithVersionEntity) s.load(DeviceWithVersionEntity.class, 1);
                    d.setName((new Random()).nextInt(500) + "");
                    s.save(d);

                    s.getTransaction().commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    s.getTransaction().rollback();
                } finally {
                    s.close();
                }
            });
        }
    }
}

```

- First part save a record to database
- Second part create 50 thread & hibernate session then try to update the inserted record

Execute result will throw a lot of **org.hibernate.StaleObjectStateException**

![Execute result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9s0lyrdb3k1gog7ouy0f.png)
The exception message already explain what happens, but how and when? 

> Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

If @Version add to a field of Entity (number), when the create/update operation execute, Hibernate will set/increase value of @Version field.

```sql
UPDATE device set name = 'new name', version = version + 1
```

When the next UPDATE happens, Hibernate will check if the version is matched by add a condition to WHERE clause

```sql
UPDATE device SET name = 'new name', version = version + 1
WHERE id = ? AND version = **CURRENT_VERSION**
```

if another session already update the record and version had increased, WHERE clause won't match and exception throw

This method also called **Optimistic Locking** and it's easy to implement with JPA and Hibernate (which handle the heavy logic)

Source code available at: https://github.com/docvominh/java-example/tree/master/hibernate-jpa
I use maven, hibernate 4.3 and postgres sql

Thank you for your reading!