Version-Based Optimistic Concurrency Control in JPA/Hibernate

This article is an introduction to version-based optimistic concurrency control in Hibernate and JPA. The concept is fairly old and much has been written on it, but anyway I have seen it reinvented, misunderstood and misused. I’m writing it just to spread knowledge and hopefully spark interest in the subject of concurrency control and locking.

Use Cases

Let’s say we have a system used by multiple users, where each entity can be modified by more than one user. We want to prevent situations where two persons load some information, make some decision based on what they see, and update the state at the same time. We don’t want to lose changes made by the user who first clicked “save” by overwriting them in the following transaction.

It can also happen in server environment – multiple transactions can modify a shared entity, and we want to prevent scenarios like this:

  1. Transaction 1 loads data
  2. Transaction 2 updates that data and commits
  3. Using state loaded in step 1 (which is no longer current), transaction 1 performs some calculations and update the state

In some ways it’s comparable to non-repeatable reads.

Solution: Versioning

Hibernate and JPA implement the concept of version-based concurrency control for this reason. Here’s how it works.

You can mark a simple property with @Version or <version> (numeric or timestamp). It’s going to be a special column in database. Our mapping can look like:

@Entity
@Table(name = "orders")
public class Order {
	@Id
	private long id;

	@Version
	private int version;

	private String description;

	private String status;

	// ... mutators
}

When such an entity is persisted, the version property is set to a starting value.

Whenever it’s updated, Hibernate executes query like:

update orders
set description=?, status=?, version=? 
where id=? and version=?

Note that in the last line, the WHERE clause now includes version. This value is always set to the “old” value, so that it only will update a row if it has the expected version.

Let’s say two users load an order at version 1 and take a while looking at it in the GUI.

Anne decides to approve the order and executes such action. Status is updated in database, everything works as expected. Versions passed to update statement look like:

update orders
set description=?, status=?, version=2
where id=? and version=1

As you can see, while persisting that update the persistence layer increments the version counter to 2.

In her GUI, Betty still has the old version (number 1). When she decides to perform an update on the order, the statement looks like:

update orders
set description=?, status=?, version=2
where id=? and version=1

At this point, after Anne’s update, the row’s version in database is 2. So this second update affects 0 rows (nothing matches the WHERE clause). Hibernate detects that and an org.hibernate.StaleObjectStateException (wrapped in a javax.persistence.OptimisticLockException).

As a result, the second user cannot perform any updates unless he refreshes the view. For proper user experience we need some clean exception handling, but I’ll leave that out.

Configuration

There is little to customize here. The @Version property can be a number or a timestamp. Number is artificial, but typically occupies fewer bytes in memory and database. Timestamp is larger, but it always is updated to “current timestamp”, so you can actually use it to determine when the entity was updated.

Why?

So why would we use it?

  • It provides a convenient and automated way to maintain consistency in scenarios like those described above. It means that each action can only be performed once, and it guarantees that the user or server process saw up-to-date state while making a business decision.
  • It takes very little work to set up.
  • Thanks to its optimistic nature, it’s fast. There is no locking anywhere, only one more field added to the same queries.
  • In a way it guarantees repeatable reads even with read committed transaction isolation level. It would end with an exception, but at least it’s not possible to create inconsistent state.
  • It works well with very long conversations, including those that span multiple transactions.
  • It’s perfectly consistent in all possible scenarios and race conditions on ACID databases. The updates must be sequential, an update involves a row lock and the “second” one will always affect 0 rows and fail.

Demo

To demonstrate this, I created a very simple web application. It wires together Spring and Hibernate (behind JPA API), but it would work in other settings as well: Pure Hibernate (no JPA), JPA with different implementation, non-webapp, non-Spring etc.

The application keeps one Order with schema similar to above and shows it in a web form where you can update description and status. To experiment with concurrency control, open the page in two tabs, do different modifications and save. Try the same thing without @Version.

It uses an embedded database, so it needs minimal setup (only a web container) and only takes a restart to start with a fresh database.

It’s pretty simplistic – accesses EntityManager in a @Transactional @Controller and backs the form directly with JPA-mapped entity. May not be the best way to do things for less trivial projects, but at least it gathers all code in one place and is very easy to grasp.

Full source code as Eclipse project can be found at my GitHub repository.

10 thoughts on “Version-Based Optimistic Concurrency Control in JPA/Hibernate

  1. I’ve seen this used before in transactional UIs but handling the resulting exception was basically a message box letting the user know that the record had already changed and they should refresh. This seemed to me to be a singularly unhelpful method for dealing with this.

    Is there a Hibernate/JPA way of automatically handling the update collision, at least in some situations, or is an application-specific way always required?

  2. Alan – I am not aware of automated collision resolution, and can’t really imagine a one-size-fit-all approach. What would you expect it to do?

    One idea I can think of is do nothing if the state in DB is identical to the one you’re trying to commit (same change performed more than once). I’m not aware of a built-in support for this though.

    If you’re OK with overwriting changes done in other transactions or keep history elsewhere, don’t use concurrency control at all. If optimistic control is too weak, consider a pessimistic approach. There’s quite a range of options here, each with its pros and cons, specific assumptions and solutions.

  3. Hi Konrad, thanks for your post.

    Your approach looks fine, but the initial ‘version’ value/object must be passed to the front-end when it’s asynch & distributed env., I understand.

    Now, when it needs to persist(update) the detached object after any change, what are the exact steps? Will this be the right way?

    (1) get the managed bean by querying the ID of the entity
    (2) update the bean with the values passed from the front-end tier including the ‘version’
    (3) call EntityManager.merge()

    Thanks for your answer.

  4. Brendan,

    when I tested it, it did not seem to work that way. In this scenario it seems you have to have a detached instance to merge. If you update versioned field on an attached entity, Hibernate gives up and does not do the version check.

    I am aware of two ways to do it:

    1. Pass complete entity with all the fields from client. In Hibernate session, just call merge on this detached instance from client. Depending on application it may be impossible, or it may be perfectly acceptable.

    2. With CQRS, DTOs and other applications that don’t have complete entity model on the outer layers, it seems you have to: load the entity in session, evict (detach), apply modifications including the version fields, then merge.

  5. Great explanation on this topic.

    I have a gameserver application, which is used by several thousand users and most probably by several hundred users in concurrent.
    I already have a pretty good exception handling included, which handles OptimisticLockExceptions.
    However i’d like to reduce the number of exceptions by suppress the version increment of my User-Entity when sone of the nested entities have changed, because I’m perfectly sure that when a User A is changing these nested entities on User A, there is no action available for another User-Entitiy B, which need a view on A and depend on the changed nested Entities of A.

    In the end, I could reduce the version increments and therefore the occurence of OptimisticLockExceptions i hope.

    Andy idea on this?

    Thanks and greetings,
    Andy

  6. Andy,

    I’m afraid this is too little information to suggest anything. Let me try a wild guess…

    If something is “owned” by user A, why does the update operation on user B care about it at all? And if the shared bit is in a way external to the users, it should probably have its own concurrency control (e.g. its own version property).

  7. Hi, does the hibernate optimistic concurrence control only need @version in the entity class and hibernate will check the version every time before saving to db, or I need to check the version by my side? I did this way but it seams hibernate didn’t check the version. If it can check the version, could you provide more about the configuration? Thanks

  8. Lotus,

    it should Just Work all by itself. Did you see my project at GitHub? It’s linked at the bottom.

  9. Hi, Konrad!

    I did go through your project. You added @version in the entity and the controller has annotation with @transactional . I add the @transactional in my service layer, where EntityManager belongs. Do you think this why the hibernate didn’t do the version check for me? or is there any other do I need to config? Thanks very much!

  10. Hi, Konrad!

    I think I found out the reason why hibernate cucurrency control didn’t work for me. It’s because within the same transaction my service code retrieve the new record from db to compare with the current changes. Then the retrieved record made hibernate didn’t consider the changes were made based on old version object.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Spam protection by WP Captcha-Free