My application is using Spring Data JPA (Hibernate). The application exposes a REST API which allows to manipulate the data in the persistence store (using Hibernate).
If a REST client (of the application) wants to update something he would send in the expected version number of the entity along with the data. There's an RFC for that, but it is besides the point here. Bottom line: I get some "expected version" value along with the PUT or PATCH operation.
My entity looks like this:
@Entity
public class Person {
@Version
@Column(name="entity_version")
private Long version;
// getters and setters
}
On an incoming PUT or PATCH operation, I do this:
- Retrieve existing entity from the database.
- Set the "expected version" using
person.setVersion(expectedVersionReceivedFromRestClient). - Apply the new/updated data to the entity.
- Then perform
repo.save(entity).
Now, if the REST client sends some very weird long number as the expected version, I would assume that I would get an OptimisticLockException. However, this doesn't happen.
I've debugged and it seems Hibernate will refresh the entity just prior to merging it. Thus the value I set with the setVersion() is lost and the merge operation thereby always succeeds. Not what I expected!
I've read various docs that say that I shouldn't manually call set value on any @Version annotated property. But how then can I marry the stateless REST world with that of the JPA model?
I think I have THIS PROBLEM, but I have to admit, I do not understand any of it. :-(
Analysis of Spring Data REST
Based on the comments it seems clear that we cannot manually set @Version annotated property value.
It would be obvious to look into how Spring Data REST solves this problem (although I do not use it in my project) as it is able to expose a JPA repository using REST and it supports conditional operations using the If-Match http header.
So what actually happens when you do say a PUT request with an If-Match header which doesn't match the current version value of the database?
As far as I can tell it checks the version first and then moves on to perform the actual update of the entity in the database. This is not safe, IMHO. It is quite easy for two operations to happen at approx the same time and both will pass the check and then they will both move on to update the entity. Thus, the lost update syndrome is not solved. (The Spring Data REST documentation doesn't mention this deficiency, so please comment if you think I'm wrong)
The bullet proof solution?
All in all, the only mechanism that would be "safe" in a concurrent world would be if the solution generated SQL as follows:
UPDATE persons
SET ..., -- set column values
entity_version = entity_version+1
WHERE id = :id
AND entity_version = :expected_entity_version
If the above updated exactly 1 row it means the update happened as expected. If the above updated 0 rows it means we should raise 412 Precondition Failed. (incidentally this is how Hibernate deals with @Version, but as we've already established, unfortunately we cannot leverage JPA/Hibernate's @Version feature here)
Restating the problem
The question is: How to build a safe "conditional-update" REST API on top of a Spring Data JPA solution? By "safe", I mean that concurrent updates do not override each other. Above, I've looked into how Spring Data REST solves this problem and I've argued that I don't think it solves it all.
There has to someone out there who has tried this? :-)
The version is managed by Hibernate and is needed to prevent updating entities in parallel transactions. What happens between GET and PUT requests - is not a DB transaction, so you shouldn't solve it in such way. Hibernate doc says: Your application is forbidden from altering the version number set by Hibernate.
If you want, you can manually check that the version from a request is equal to the version of the existing entity. But in my opinion,
@CreatedDateis more suitable for that case because is human-readable.