Concurrent Access

Let us think about the following scenario. User Foo selects a turtle to edit it. User Bar selects the same turtle to edit it. User Foo press the save button, then user Bar press the save button. Well, if two user edit the same data, we need to think of concurrent access. What should happen? Basically we have four options:

The default behaviour of Hibernate is option two. The last change will silently overwrite the changes before. Although it sounds bad, it is perfectly fine for a lot of application. If you don’t expect that two user edit the same data in parallel then you don’t have to care.

The first approach requires pessimistic locking, the third approach can be easily done with the optimistic locking functionality of Hibernate. The last approach is basically the third approach plus manual coding to craft the merge/overwrite/cancel dialog. Let’s have a look at the different approaches.

Optimistic Locking

Optimistic locking means that in fact we don’t lock the database at all. This is not perfectly true, because for a short moment during the insert or update statement, we will see a short write lock in the database. To prevent the caveats of concurrent access a version column is used.

The database table has a column indicating the current version of the row. The column can contain an integer number which is increased by each change or a timestamp showing the change time.

User Foo (left side in the diagram) and user Bar (right side) load data to edit the same employee. The version column has the value 1. User Foo saves his changes. Hibernate will verify which version of the object he has and which version is in the database. Only if he has the current version, he can update the row. In this case, he has version 1, which is the same as in the database and his changes will be saved. At the same time Hibernate increments the version to 2

Now user Bar tries to save his changes. Hibernate compares the version numbers. Bar has version 1 but the database has already version 2. Hibernate will throw an exception.

images/c_session_concurrent-access.jpg

 org.hibernate.StaleObjectStateException: Row was updated or deleted
by another transaction

We can catch the exception and tell Bar that he has to restart. You can find sample code for this approach in chapter Hibernate and Struts the section called “Hibernate and Struts”.

Version column impact on performance

The impact of a version column is neglect able. Don’t expect that you will get a separate select statement for the version. Hibernate will slightly change the update statement.

update employee set name=?, version=2 where id = ? and version = 1

An SQL update statement returns the number of changed rows. If the SQL result is 1, Hibernate knows that the entry was successfully updated and that the version is incremented. If it is 0, Hibernate will throw an exception.

Finally let’s have a look how to do this.

Version column The first important thing is to add a version column to your mapping. The type can be any of integer, long, short, timestamp, dbtimestamp or calendar. The version will be tracked by a integer value starting at zero or with a change timestamp depending on the type you selected. I recommend not to use timestamps. It is possible to create the same timestamp in the same moment and in addition modern machines quite often run on virtualized server. They suffer of larger time delays. If the time is readjusted, you might see new version conflicts.

Annotation

@Version
   private int version;

XML

<version name="version"  type="integer"></version>

Optimistic locking without a version column

If you are not allowed to add a version column – a common problem with legacy systems – you can still use optimistic locking. Hibernate can compare all columns of the old version to the current columns in the database. This is of course far less efficient but still better than nothing.

Annotation

We need to set optimisticLock to DIRTY or ALL and enable dynamic updates. This allows Hibernate to construct the update statements for every update. Normally it reuses the same statement all the time in order to improve performance.

ALL compares all columns, DIRTY compares only changed columns

import javax.persistence.Entity;
...
import org.hibernate.annotations.OptimisticLockType;



@Entity
@org.hibernate.annotations.Entity(optimisticLock=OptimisticLockType.ALL,
  dynamicUpdate=true)
public class Apple {

XML

  <class name="Apple" optimistic-lock="all" dynamic-update="true">

Limitations of this approach

Dynamic updates are slower, because Hibernate instead of reusing the same update query all the time, will recreate the update statement for every request.

Pessimistic locking

Pessimistic locking creates a lock in the database and enforces that no other user can edit the same data row. This is a suitable approach for Java application based on Swing or SWT. In a web application I discourage to use this approach. You don’t know, if the user is thinking, making a break or already on holiday. The lock on the object will stay until the HTTP session ends. If you forget to write a cleanup listener which rollback open transactions in your session, you might even have a transaction leak.

With modern Ajax technology you can work around this problem and send continuous ping from the client to the server. If the ping stops, you close down the lock on the database row.

How to lock a row with Hibernate?

You can lock an entity when you load it

      Apple appel = (Apple) session.get(Apple.class, id, LockOptions.UPGRADE);

or later

      session.buildLockRequest(LockOptions.UPGRADE).lock(apple);

It is even possible to lock entities using a query.

session.createQuery("from Apple a where a.name=?")
  .setParameter(0, theName)
  .setLockOptions(LockOptions.UPGRADE);

session.lock is deprecated

If you have some Hibernate experience or in old examples, you might be aware of code like session.lock(customer). This method is now deprecated. Use the buildLockRequest method, which was demonstrated in this chapter.