Optimistically Locking Your Spring Boot Web Services

Neil Buranakanchana
Slalom Build
Published in
8 min readFeb 9, 2017

by Neil Buranakanchana

Ancient History

Do you remember Microsoft Visual Source Safe? It was a code repository that followed a checkout/checkin style. If you wanted to edit a file, you would have to check it out. When you were done with it, you would check it back in. Great, what could go wrong? Well for one, it means that no more than one developer can be editing the same file at the same time — While it is checked out, it is locked. Also, someone would inevitably go on vacation for a week and leave a bunch of files checked out, which lead to headaches for everyone.

Locking

Record locking is a common approach used in computer science and software engineering to handle (and prevent) simultaneous access to shared data, with the intention of preventing inconsistent results. Generally speaking, there are two major approaches to locking: pessimistic and optimistic.

Pessimistic Locking

Source Safe is a great example of pessimistic locking. Pessimistic locking started out as a database concept, but as we can see through this example, it can be realized at the application level. The core concept behind this type of locking is that only one person has exclusive access to a shared record, achieved via an exclusive lock granted to that user while they are making changes. While the lock is held, no other person may obtain the lock (and thus must wait). Once that person is done updating the record, the lock is released and the next person is eligible to obtain the lock. As you can imagine, this does a great job of preserving data integrity, but it’s not very friendly to use, and can result in a lot of wasted time obtaining and releasing locks in cases when they really aren’t needed.

Optimistic Locking

With optimistic locking, records are freely given out to whoever wants them. Every record has a version field that can be represented with a unique number, timestamp, or some sort of a hash. Upon a successful save of the record, the version is incremented or updated. Ok, makes sense right?

Before the save operation, however, we need to check if the version we originally got matches what’s currently in the database. If they don’t match, we know that during the time we’ve had the record, someone else has already requested and saved that same record before we could save, and therefore we must take action to ensure that our update will be consistent (most commonly, re-reading the updated record). Let’s look at an illustration of the scenario.

Example flow:
1. User A gets version 1
2. — — — — — — — — — — User B also gets version 1
3. — — — — — — — — — — User B saves, the version is now incremented to 2
4. User A attempts to save. ← Lock Error.

So in step 4, User A still has a copy of version 1 and the save fails because the current version is now 2. User A would have to get a fresh snapshot of the version 2 data and retry the save.

Optimistic locking takes the approach of assuming that the update will be uncontested from the beginning. Worst case, it will need to re-read records and try again before updating succeeds, but in many cases it can be more efficient than the pessimistic approach if your application has low contention for records. For more details, read about optimistic locking on Wikipedia.

Example Project

From this point on, we’ll be looking at practical ways we can get achieve optimistic locking. You can pull down the project from GitHub. The README.md file explains how to run the project, how to access the database web view, and how to run the test scripts.

The project is split up into essentially two proofs of concept. The sections below deep dive into each option.

  1. The first option takes advantage of the built in support inside of JPA. We’ll see how we can add the @Version annotation to our Entities to accomplish this.
  2. The second option assumes that our project does not use JPA. We’ll see how we can make our own annotation and annotation processor, and then apply the annotation to our data access modules to achieve the same effect.

The data model we’ll be using is a very simple representation of a Product. The version column will be used in similar ways by both endpoints.

Screen Shot 2016-08-05 at 4.14.36 PM

Optimistic Locking with JPA and Hibernate

Java Persistence API (JPA) is a popular specification that describes the management of relational data. Providers implement this specification. Here is a list of the most popular implementations:

For our example, we’re going to use Hibernate as the JPA provider. Let’s start by making a controller endpoint addressed at /jpa-products.

You’ll notice we have an endpoint to get a Product by its ID, and also a way to save the Product. For simplicity, our controller is interacting with our data layer directly. In real life, we would have a business logic layer in-between. Spring provides a nice wrapper to JPA called CrudRepository that we can use to simplify our repository:

Yup, that’s it, the CrudRepository is pretty slick. It’s really just an interface that defines common ways to access data. Under the hood it’s using Spring, JPA, and your JPA Provider. It also dynamically provides custom ways to query for your data, based on the field names defined. Suppose you have fields firstName and lastName in your Entity. You could just say repo.findByFirstNameAndLastName(firstName, lastName) and that would work. Its similar to the magic you may have seen with Ruby on Rails Models.

Now let’s take a look at the Product entity:

All the annotations you see are part of the JPA specification. The @Version annotation assumes a column in the database exists to represent the field that the annotation is tagged to. That’s really it. JPA takes care of incrementing the version as well as all the version checking upon save. Lets start up our Spring Boot server and test it out with a python script.

Running the script, we see some encouraging output:

There we can see that User A requests Product 1 before User B. But then User B saves first. The version gets updated to 2. Now when User A tries to save, he gets the error.

Optimistic Locking with Spring JDBC

Well, that was easy. We should just do that every time, right? Not so fast. Sometimes Hibernate is deemed “Too Heavy” for certain applications. Or sometimes the explanation goes “Hey, we only use JDBC at this organization”. So it’s time to find another way to achieve our goal.

Here are some other popular non-JPA relational APIs:

The problem with these options is that they do not provide an implementation for optimistic locking. But that’s ok, we can use the concepts we saw in the JPA implementation and try to do something similar with the help of Spring and the Java Reflection API. So for now we’ll pretend that we prefer to use Spring JDBC.

Our Product object now implements a VersionedEntity interface that we’ve defined. This is essentially what our processor will interface with later on. Notice that tableName is a new field. It is used by the processor to know what database table this object maps to. You may consider removing that field and just using the class name as the table name. I chose to define it this way just to show that your entity class names don’t necessarily need to match your physical table name.

The JDBC controller looks pretty similar to our JPA controller, but it now uses a Spring JDBC based DAO that we’ve defined.

Our DAO class is pretty typical, but notice the @OptimisticlyLocked annotation on the save method. This is a custom annotation that we’ve defined.

An annotation is really just metadata that’s accessible to our code. Heres what ours looks like:

The @Retention annotation used on our @OptimisticlyLocked annotation is important. It says that this annotation should live beyond the compile time and be there during run time.

Ok, that was easy enough, but where’s the code that does all the checking and version bumping? We need something that essentially intercepts the save() call, does the checking and bumping we want, and then proceeds if everything is good.

Spring provides an interface called BeanPostProcessor. If we implement this interface, it lets us do things to the beans before and/or after they’re initialized. That’s perfect. Now we have a place to use reflection that says, “Hey, before you actually execute the code inside the method, run this code first.” Let’s take a look:

That is essentially the heart of the solution. The two methods implemented will run for every bean in your application. So at a high level this is what this processor is doing:

1. Get all the declared methods in the bean’s class
2. If the method is annotated with @OptimisticlyLocked, proceed
3. Create a Proxy that wraps our bean.
4. Implement a method interceptor on our proxy that holds our optimistic locking logic
5. Check that the argument passed to the method is a VersionedEntity. Our Product implements this interface.
6. We’ll need a handle to the JdbcTemplate to query the database for the latest version, so we get this from the app context at method call time.
7. If the versions match, bump up the version and let the called method execute.
8. If the versions do not match, we’ve got a stale copy of the record. Throw the exception.

Let’s create another test script for our JDBC endpoint:

And run it:

Screen Shot 2016-08-09 at 2.34.32 PM

Great, we got similar results as with the JPA example.

Moving Forward

In this article, we saw that there are JPA providers that support optimistic locking out of the box. We also saw that there are options available if you are not able to use these providers. These examples should help you get started with using optimistic locking in your own applications.

Be sure to clone the project on GitHub to see the full examples.

About the Author

Neil Buranakanchana is a Solution Architect with Slalom’s Cross-Market delivery center in Chicago.

--

--