I have a Spring Boot 1.3.M1 web application using Spring Data JPA. For optimistic locking, I am doing the following:
- Annotate the version column in the entity:
@Version private long version;
. I confirmed, by looking at the database table, that this field is incrementing properly. - When a user requests an entity for editing, sending the
version
field as well. - When the user presses submit after editing, receiving the
version
field as a hidden field or something. Server side, fetching a fresh copy of the entity, and then updating the desired fields, along with the
version
field. Like this:User user = userRepository.findOne(id); user.setName(updatedUser.getName()); user.setVersion(updatedUser.getVersion()); userRepository.save(user);
I was expecting this to throw exception when the versions wouldn't match. But it doesn't. Googling, I found some posts saying that we can't set the @Vesion
property of an attached entity, like I'm doing in the third statement above.
So, I am guessing that I'll have to manually check for the version mismatch and throw the exception myself. Would that be the correct way, or I am missing something?
Unfortunately, (at least for Hibernate) changing the
@Version
field manually is not going to make it another "version". i.e. Optimistic concurrency checking is done against the version value retrieved when entity is read, not the version field of entity when it is updated.e.g.
This will work
However this will not work
There are some way to workaround this. The most straight-forward way is probably by implementing optimistic concurrency check by yourself. I used to have a util to do the "DTO to Model" data population and I have put that version checking logic there. Another way is to put the logic in
setVersion()
which, instead of really setting the version, it do the version checking:In addition to @Adrian Shum answer, I want to show how I solved this problem. If you want to manually change a version of Entity and perform an update to cause
OptimisticConcurrencyException
you can simply copy Entity with all its field, thus causing an entity to leave its context (same asEntityManager.detach()
). In this way, it behaves in a proper way.EDIT: the assembled version works, only if hibernate cache does not contain entity with the same id. This will not work:
Part of the @AdrianShum answer is correct.
The version comparing behavior follows basically this steps:
Now suppose that between steps 1 and 3 the entity was modified by another transaction so its version number at step 3 isn't V1. Then as the version number are different the update query won't modify any registry, hibernate realize that and throw the exception.
You can simply test this behavior and check that the exception is thrown altering the version number directly on your database between steps 1 and 3.
Edit. Don't know which JPA persistence provider are you using with Spring Data JPA but for more details about optimistic locking with JPA+Hibernate I suggest you to read chapter 10, section Controlling concurrent access, of the book Java Persistence with Hibernate (Hibernate in Action)
You can also detach entity after reading it from db, this will lead to version check as well.
Spring repositories don't have detach method, you must implement it. An example: