Hibernate Versionless Optimistic Locking

This article is about a Hibernate feature called versionless optimistic locking. Versionless optimistic locking is an alternative to using JPA’s @Version annotation with a numeric or timestamp column. It can be used to lower the risk of optimistic locking exceptions.

The @OptimisticLocking Annotation

Optimistic locking with Hibernate is commonly implemented by applying the @Version annotation to a version instance variable resulting in a VERSION column with either a numberic value or a timestamp (depending on the declared type of the instance variable).

Although this is sufficient in many situations, Hibernate does provide alternative locking strategies too. By applying the @OptimisticLocking annotation on type level you can define the style of optimistic locking to be applied to a JPA entity.

import org.hibernate.annotations.OptimisticLocking;
import org.hibernate.annotations.OptimisticLockType;

@Entity
@OptimisticLocking(type = OptimisticLockType.VERSION)
@Getter
@Setter
public class Car {
    
    @Id
    private Integer id;

    @Version
    private Integer version;

}

The example above shows the default locking strategy. When having a look at the OptimisticLockType (JavaDoc) enumeration, we can see the following locking strategies:

ALL and DIRTY define so-called versionless locking strategies.

If OptimisticLockType.ALL is given in the @OptimisticLocking#type attribute, the optimistic lockingn strategy works as follows: whenever an UPDATE or DELETE statement is generated by Hibernate, it will append not only the @Id column in its WHERE clause but also all of the persistent fields.

Let’s say our Car class would look like

import org.hibernate.annotations.OptimisticLocking;
import org.hibernate.annotations.OptimisticLockType;

@Entity
@OptimisticLocking(type = OptimisticLockType.ALL)
@Getter
@Setter
public class Car {
    
    @Id
    private Integer id;

    private String model;

    private String brand;
}

let us consider we have an already persisted Car instance and we would change property model

Car car = carRepository.findOne(carId);
car.setModel("Punto");

car = carRepository.save(car);

with OptimisticLockType.ALL the generated SQL statement would be

UPDATE CAR SET MODEL = ?, BRAND = ? WHERE ID = ? AND MODEL = ? AND BRAND = ?

All columns of the associated database row are used in the WHERE clause. If the Car instance would have been changed in the meantime and the UPDATE would not find a row, a StaleStateException or an OptimisticLockException is going be thrown.

With OptimisticLockType.DIRTY only the dirty/changed fields (in our case model) would be part of the SQL statement

UPDATE CAR SET MODEL = ?, BRAND = ? WHERE ID = ? AND MODEL = ?

If the model column still has the same value the UPDATE will succeed.

With such an optimistic locking strategy, the main advantage is that it reduces the potential of overlapping updates. When using OptimisticLockType.DIRTY, the documentation advises to use the @DynamicUpdate and @SelectBeforeUpdate annotations in combination with @OptimisticLocking. @DynamicUpdate will generate UPDATE statements only SETting the dirty columns, @SelectBeforeUpdate will avoid issues with detached entities, assuming the Hibernate session is opened for the entire request.

Summary

Besides the commonly known versioning optimistic locking strategy, Hibernate comes with two other versionless strategies this article has a closer look at.