How to version rows automatically in Hibernate / Optimistic locking in Hibernate / Version-controlled optimistic concurrency control in Hibernate

Practical Scenario

Two users are working on a project and they both are currently viewing a bug or an enhancement in a project management application. Let’s say one user knows that it is a duplicate bug while the other does not know and thinks that this needs to be done. Now one user changes its status as IN PROGRESS and the other user marks it as DUPLICATE at the same time but the request with status as DUPLICATE goes a bit early to the server and the request with status IN PROGRESS arrives later. What will be the current status?

It will be IN PROGRESS as the later request will overwrite the previous status. These kind of scenarios are common when multiple users work on same application and should be prevented.

In general, following is the scenario which should be prevented :

  • Two transactions read a record at the same time.
  • One transaction updates the record with its value.
  • Second transaction, not aware of this change, updates the record according to its value.

End Result is, the update of first transaction is completely lost.

Solution

Hibernate has a provision of version based control to avoid such kind of scenarios. Under this strategy, a version column is used to keep track of record updates. This version may either be a timestamp or a number.

If it is a number, Hibernate automatically assigns a version number to an inserted record and increments it every time the record is modified.

If it is a timestamp, Hibernate automatically assigns the current timestamp at which the record was inserted / updated.

Hibernate keeps track of the latest version for every record (row) in the database and appends this version to the where condition used to update the record. When it finds that the entity record being updated belongs to the older version, it issues an org.hibernate.StaleObjectStateException and the wrong update will be cancelled. How versioning will solve the above scenario? Stay Tuned !!!

 

How To Implement Versioning

There are only three steps required for versioning to work for an entity. They are :

  1. Add a column with any name (usually it is named as Version) in the database table of the entity. Set its type either to a number data type (int, long etc.) or a timestamp as you want to store its value.
  2. Add a field of the corresponding type to your entity class. If you have made the version column in the database table of a number type , then this field should be either int, long etc. and if the column type in the database is of type timestamp, then this field should be a java.sql.Timestamp.
  3. Annotate the above field in your entity class with @Version  or provide the definition of version column in the <class> tag if you are using XML based entity definitions.

Example

Let’s say we have an entity class named Defect in which we want to implement versioning. Notice the field annotated with @Version.

 @Entity
 @Table(name = "defect")
 public class Defect {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private int id;
   @Column(name = "Type")
   private String type;
   @Column(name = "Description")
   private String description;
   @Column(name = "Status")
   private String status;
   @Version
   private int version;
    
    // getter and setter methods 

 }

For XML based entity configuration, version will be added as :

<hibernate-mapping package=”com.codippa”>
<class name=”Defect” table=”defect”>
<id name=”id” column=”Id”/>
<version name=”version” column=”Version” />
<property name=”type” column=”Type” />
<property name=”description” column=”Description” />
<property name=”status” column=”Status”/>
</class>
</hibernate-mapping>

Now let’s say we create a new Defect using the below code :

        Configuration cfg = new Configuration();
	cfg.configure("hibernate.cfg.xml");
	cfg.addAnnotatedClass(Defect.class);
	SessionFactory sessionFactory = cfg.buildSessionFactory();
	Session session = sessionFactory.getCurrentSession();
	Defect defect = new Defect();
	defect.setType("Bug");
	defect.setDescription("Core defect");
	defect.setStatus("DUPLICATE");
        // We did not set the version field value
        session.beginTransaction();
	session.save(defect);
	session.getTransaction().commit();

 
Executing the above code will generate a database record as shown below :

After record insertion

The value 0 in the version column is automatically generated by Hibernate. Note that we did not set version in the above code. Below is the query generated by Hibernate while saving the record.

Hibernate: insert into defect(Description, Status, Type, version) values (?, ?, ?, ?)

See the field version is included in the insert query generated by Hibernate though we did not set its value.

Now let’s make a change in this record and update it using the below code :

        Configuration cfg = new Configuration();
	cfg.configure("hibernate.cfg.xml");
	cfg.addAnnotatedClass(Defect.class);
	SessionFactory sessionFactory = cfg.buildSessionFactory();
	Session session = sessionFactory.getCurrentSession();
	session.beginTransaction();
        // fetch record with id as 1
	Defect defect = session.get(Ticket.class, 1);
        // change a field value
	defect.setStatus("CLOSED");
        // We did not set the version field value again
	session.update(defect);
	session.getTransaction().commit();

Executing the above code will generate a database record as shown below :

After record updation

The value 1 in the version column is automatically generated by Hibernate. Note that we did not update version in the above code. Below is the query generated by Hibernate while saving the record.

Hibernate: update defect set Description=?, Status=?, Type=?, version=? where id=? and version=?

See how version is added into the WHERE clause condition.

How versioning will solve the above scenario?

Two Users are viewing at a defect ticket at the same time. We will call them User1 and User2. Let’s say the defect is a new entry in database table. Post versioning implemented, it will have version as 0. User1 and User2 change the status of a bug at the same time but the request of User1 arrives a bit early to the server. User1 has changed the status to DUPLICATE, the query issued is :

update defect set Description=?, Status=DUPLICATE, Type=?, version=1 where id=? and version=0

Now the request of User2 to change the status to IN PROGRESS arrives, the query issued will be :

update defect set Description=?, Status=IN PROGRESS, Type=?, version=1 where id=? and version=0

Note the version in the WHERE clause is 0 since both the users were looking at version 0 of the defect. Now there is NO record matching the id of that defect and version since the version has been changed by the previous update. Hibernate is smart enough to detect an object mismatch and issues a org.hibernate.StaleObjectStateException thus preventing the update on an already updated record.

Let’s tweak in :

  1. If the type of version column in database and the field type in java class are set to timestamp, then every time a record is updated, the current timestamp is set as the version value.
  2. When timestamp is set as the type of value for version column, then its value can indicate the time at which the entity was updated.
  3. It is not mandatory to name the java field representing version as version itself.

Leave a Reply