Most of the time it is OK to simply let the database do the work of concurrency control, however sometimes you can encounter an application where you need to take over.
In this article I will give a brief introduction to optimistic and pessimistic concurrency control.
Optimistic concurrency control
The only consistent approach in highly concurrent applications is optimistic concurrency control with versioning. This approach uses a version number or timestamps to detect conflicting and prevent lost updates.
Application version checking
With this approach an application has to manually maintain the versions of the entities. This means the developer is responsible to load the actual entity state from the database before manipulating them. When the object is flushed by Hibernate the version is automatically incremented — so the developer does not need to increment this property.
You can use this approach if your application has low-concurrency and skip version control. In this case always the last commit wins and the object is updated according to that state. And this is why it is required to always load the actual state of the entity from the database prior manipulating it.
Automatic versioning
To use automatic versioning, simply add one field or a method to your entity you want to have under optimistic locking’s version control and annotate it with the javax.persistence.@Version annotation. As the documentation states this annotation can be used for following types: int, Integer, short, Short, long, Long, java.sql.Timestamp.
If some updates happen between loading the entity and flushing it back to the database you get an error message from Hibernate:
Exception in thread “main” org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [hibernate_example.joined.Book#1]
Pessimistic concurrency control
As I’ve already mentioned last time, Hibernate does not lock objects in memory, it will always use the locking mechanism of the underlying database.
However the LockMode class defines some mechanisms which can be obtained by Hibernate:
- WRITE: is acquired automatically when Hibernate updates or inserts a row
- UPGRADE: can be acquired upon explicit user request using SELECT … FOR UPDATE on databases which support this syntax
- UPGRADE_NOWAIT: can be acquired upon explicit user request using a SELECT … FOR UPDATE NOWAIT under Oracle
- READ: is acquired automatically when Hibernate reads data
- NONE: represents the absence of a lock, all objects switch to this lock mode at the end of the transaction
- PESSIMISTIC_FORCE_INCREMENT: forces incrementing the version upon loading the entity.
The above mentioned “explicit user requests” can be expressed as one of the following calls:
- load() with specifying a LockMode for the LockOptions parameter
- buildLockRequest()
- setLockMode()
Examples on setting lock modes
Above I have mentioned some lock modes and how to set them, now it is time to see them in action with some example code.
getBooks(session).stream().forEach(b -> session.load(Book.class, b.getId(),
new LockOptions(LockMode.PESSIMISTIC_FORCE_INCREMENT)));
getBooks(session).stream().forEach(System.out::println);
The block above does two things: loading the entities from the database setting a lock mode for forcing the version incrementation and then it prints out the books to the console. The result would be something like the following:
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29. 0:00 [1]
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29. 0:00 [1]
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29. 0:00 [1]
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29. 16:14 [1]
The square brackets at the end of each line contain the version number of the entity.
If we want to move the locking into the getBooks() method we can do this like follows:
private static List<Book> getBooks(Session session) {
final Query query = session.createQuery("from Book b");
query.setLockMode("b", LockMode.PESSIMISTIC_FORCE_INCREMENT);
return query.list();
}
The interesting part is the first string parameter of the setLockMode method: it is an alias for the entity to use this locking with, and you have to use this alias in the query’s FROM block.
The result of printing the books to the console after loading them with this repository method could be something like this:
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29. 0:00 [1]
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29. 0:00 [1]
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29. 0:00 [1]
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29. 16:15 [1]
An interesting fact
The version numbers are updated only in the current session until you update the entities and save them to the database.
If you do not do this you see version increments in your application but they aren’t saved to the database — even if you use some *_FORCE_INCREMENT strategy.
| 1 | Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft | 9781617291999 | 2015-07-29 | Java 8 in Action | 0 |
| 2 | Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft | 9781617291999 | 2015-07-29 | Java 8 in Action | 0 |
| 3 | Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft | 9781617291999 | 2015-07-29 | Java 8 in Action | 0 |
| 4 | Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft | 9781617291999 | 2015-07-29 | Java 8 in Action | 0 |
The block above shows the entities in the database if we do not update old entries. The columns are the following from left to right: ID, AUTHORS, ISBN, PUBLISHED, TITLE and VERSION.
To have some version changes, uncomment the code block in the example and run the application. That block updates the date of the books to the current date and saves the entities back into the database. To do this you will need a transaction.
To start a transaction simply call session.beginTransaction();, and if you finished call session.getTransaction().commit(); to finish and write to the database or session.getTransaction().rollback() to revert all the changes made in this transaction. Alternatively you could store the Transaction object returned by the beginTransaction() method and call commit() or rollback() on this Transaction instance.
Some transaction patterns
If I mentioned transactions let me give you some patterns (or even anti-patterns) to see how transactions are commonly handled.
Session per operation (anti pattern)
This is an anti pattern because if you use this transaction management approach you open and close a session for each database call. It is done even if you use the database’s auto-commit feature which implicitly calls commit after each call to the database.
Session per request
This is the most common pattern to use with transactions. The request in the name relates to a system where there are many requests coming in from clients/users — like web applications.
The common workflow is that when such a request arrives at the system, a Hibernate Session is opened and it stays open until the information of the request is processed (updating stored information or just retrieving something to display).
If you use this pattern you can reduce data-loss in most cases because the information is persisted continuously into the database.
Session per application
Developers could not agree if this is a pattern or an anti pattern because sometimes the application is small and you can indeed open one transaction and call commit at the end. However this is a way too bad approach for bigger applications where an application failure at the end creates data-loss, or if there are concurrent users and the data is related you cannot have a synchronized state between those users.
Conclusion
Locking can be a bit bothersome however Hibernate gives us aid and helps here out too with various locking mechanism. If you want to fine-tune the built-in solution you can do it too with the methods mentioned above.
We dig into transactions a little bit just to see how it is commonly handled (with design patterns) to understand how to store data in the database and persist this information. You Can download Code from Here.