As seen in previous article, Hibernate, by default, follows the JDBC approach of relying on DBMS default transaction isolation level allowing to override it for a specific transaction if required. This means that whenever you wish to create a user transaction or annotate your method with Spring’s @Transactional, your database’s default transaction isolation level will be applied unless you override it. You’ll find a list of few common DBMS isolation levels in this article.
Moreover, Hibernate’s Persistence Context cache, stores entities which were already once retrieved from the database using the same Entity Manager. Another fetch for data will load the same entity from cache rather than executing a select query. This blocks other transactions from modifying the state of the entity in memory and ensures repeatable reads.
Still, with such a setup, the Last Update Wins problems are possible, but there’s a way to deal with them using Hibernate’s Optimistic Locking.
Feel free to explore the full code behind this article’s examples in allAroundJava github.

Last Update Wins Problem

Just as a reminder, last update wins problem occurs when two simultaneous transactions read the same database row at the same time and then one of them commits before another, resulting in the update done by one of the transactions being completely lost. You’ll find a full description of this problem with a diagram in the transaction isolation issues article.

What is Optimistic Locking

Optimistic Locking allows to turn the Last Commit wins into preferred First Commit wins. This effectively means that in the case of two transactions competing for the same resource, only the first transaction will be able to execute an update query. The second transaction will fail with an exception. With Optimistic Locking we’re assuming that conflicts between transactions will be rather infrequent and they therefore can be resolved late.
An exception will be thrown at the time the transaction is committed. In case of lengthy computation, this may be a major drawback.
Hibernate implements Optimistic Locking in a simple but very smart way. All it really requires is adding additional version property to your entities. Hibernate takes care of the rest itself. Most probably not all of your entities require this sort of protection, so knowing which ones may require concurrency control is key here.

@Entity
public class Car {
    
	//other properties
	
    @Version
    private int version;

Enabling versioning is just a matter of adding one column annotated with @Version. It can be of short, int, long type or their object representations. The type does not really have any meaning here. Version number is incremented with every update and when the value overflows, it starts from zero again.
The important bit is how it changes the update query. Take a look at below test method.

@Test
public void whenModifyingCar_thenVersionNumberIncremented() {
	Car car = createCar("Fiat", "Bravo");
	carDao.persist(car);
	Optional retrievedCarOptional = carDao.getById(car.getId());
	Assert.assertEquals(0, retrievedCarOptional.get().getVersion());

	carDao.executeInTransaction(entityManager -> {
		Car carInTransaction = entityManager.find(Car.class, car.getId());
		carInTransaction.setModel("Ducato");
	});

	retrievedCarOptional = carDao.getById(car.getId());
	Assert.assertEquals(1, retrievedCarOptional.get().getVersion());
}

An update query is fired for an object with a particular ID and Version number. The query, fired at the database at the moment persistence context is flushed will be

18:27:08.994 [main] DEBUG org.hibernate.SQL - update Car set make=?, model=?, version=? where id=? and version=?
18:27:08.994 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Fiat]
18:27:08.994 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [VARCHAR] - [Ducato]
18:27:08.999 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [INTEGER] - [1]
18:27:08.999 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [BIGINT] - [1]
18:27:08.999 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [5] as [INTEGER] - [0]

hence if any other transaction modifies Car with version 0 before this one commits, the version number will be incremented in the database and Car with version 0 will no longer exist. Our query will not update any rows and as a result javax.persistence.OptimisticLockException will be thrown.

Version number is updated by Hibernate whenever the entity instance becomes dirty, which is whenever any property is modified. This works for properties being all simple Java types, object types, collections and properties within collection elements. The exception is read-only One to Many and Many to Many mapped collections.

It’s important that the entity class does not implement a setter method for version property. Hibernate manages to increment it itself.

Enabling versioning with timestamp

Very often, database tables already contain a LAST_UPDATED or MODIFIED_ON column. To avoid changing the schema, a @Version annotation can be added to an existing LAST_UPDATED column.

@Entity
public class FinancialTransaction {
    
	//other properties

    @Column(nullable = false)
    @Version
    private LocalDateTime lastUpdated;

This approach, however, is said to be less safe as two transactions can potentially read and update the data at exactly the same millisecond. It’s advised by Hibernate to use the numeric column versioning when using Optimistic Locking. Here’s an SQL statement from updating FinancialTransaction properties.

11:10:24.965 [main] DEBUG org.hibernate.SQL - update FinancialTransaction set accountNumber=?, lastUpdated=? where id=? and lastUpdated=?
11:10:24.965 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [0000001]
11:10:24.965 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [TIMESTAMP] - [2019-05-12T11:10:24.964]
11:10:24.965 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - [1]
11:10:24.965 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [TIMESTAMP] - [2019-05-12T11:10:24.920]

Optimistic locking comparing instance properties

This feature is a Hibernate addon and does not appear in JPA standard. In addition to the two approaches mentioned above, Hibernate offers an option to determine an entity instance version by comparing all the class’ persistent properties. It’s helpful when you cannot modify your schema or use version or lastUpdated properties.

@Entity
@OptimisticLocking(type = OptimisticLockType.DIRTY)
@DynamicUpdate
public class House {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String address;

    @Column(nullable = false)
    private String ownerName;

In the below test method we are attempting to modify house’s address property.

@Test
public void whenModifyingHouse_thenDirtyPropertiesChecked() {
	House house = new House();
	house.setAddress("Summer Street");
	house.setOwnerName("John");
	houseDao.persist(house);

	houseDao.executeInTransaction(entityManager -> {
		House houseInTransaction = entityManager.find(House.class, house.getId());
		houseInTransaction.setAddress("Winter Street");
	});
}

Here are the queries which Hibernate generates with this approach to Optimistic Locking

10:58:42.679 [main] DEBUG org.hibernate.SQL - update House set address=? where id=? and address=?
10:58:42.680 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [Winter Street]
10:58:42.680 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [1]
10:58:42.681 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [VARCHAR] - [Summer Street]

When persistence context is flushed, Hibernate sends an update query containing a chosen set of persistent properties in the WHERE clause. If any of them change in the database during the transaction, the WHERE clause will fail to match any rows and javax.persistence.OptimisticLockException is thrown.
With OptimisticLockType.ALL, Hibernate will place all the persistent properties in the where clause. Matching of dirty properties can be narrowed down to only the properties which have changed, by enabling OptimisticLockType.DIRTY, just as we did in the example above. This way only the modified properties will be listed in WHERE clause. This, however, can lead to business functionality related issues when two concurrent transactions modify different properties without knowing about each other.
Additionally, when merging a detached instance to a new Persistence Context, the old values of an entity instance are not known, so this approach fails. Number and Timestamp related versioning has a definite advantage here.

To understand more about transactions in Hibernate, see the full code behind this article on Versioning, take a look here

Leave a Comment