I am using Spring Boot 3.5 and have added an @Version field to my entity. This all works fine, but I wonder how I need to handle concurrent updates coming from the REST API.
Suppose 2 users have my web app open. They both see version 1 of my entity.
The first user saves and the version is updated in the database. After a minute or so, the 2nd user saves his changes. I want to return a 409 Conflict at that point.
The saving itself is done in a use case like this:
@Component
@Transactional
class UpdateMyEntity {
private final MyEntityRepository repo;
public UpdateMyEntity(MyEntityRepository repo) {
this.repo = repo;
}
public MyEntity execute(MyEntityUpdateParameters parameters) {
MyEntity item = repo.findById(parameters.id()).orElseThrow();
item.setName(parameters.name());
return repo.save(item);
}
At first, I tried to set the version on the MyEntity, but JPA/Hibernate does not take that into account.
Should I manually check the version that the frontend sends me back through the rest api in my use case? Or is there a way to have the database or JPA handle this?
-
Did you wrap it in a transaction?K.Nicholas– K.Nicholas2025年09月19日 15:11:50 +00:00Commented Sep 19, 2025 at 15:11
-
Versioning is used to check when serializing your entity data around. In the 'execute' method, you are just taking a name parameter and overwriting the name regardless of any changes made since it was read- so there isn't likely going to be any versioning exceptions except in the case of concurrency in the small window from the 'find' call to the transaction committing. If you need some version control, you'll have to check the version in the entity yourself, or if you don't care if there are other changes, maybe send in the old name and the new one and check the old version of the nameChris– Chris2025年09月19日 15:35:23 +00:00Commented Sep 19, 2025 at 15:35
-
1stackoverflow.com/a/78134422/3426309Andrey B. Panfilov– Andrey B. Panfilov2025年09月20日 06:51:58 +00:00Commented Sep 20, 2025 at 6:51
-
@K.Nicholas My code had the transaction annotation. Added it now in my example code.Wim Deblauwe– Wim Deblauwe2025年09月22日 05:51:35 +00:00Commented Sep 22, 2025 at 5:51
1 Answer 1
Versioning control in JPA is based on entities. Merge (or Spring's Save) will take in your unmanaged entity that you have previously read in and modified and check the version in that entity against the version in the database. How it does it is an implementation detail: It can check it upfront against the version value in the cached or read back entity, but it also will include the version value in the UPDATE statement to ensure the row is only modified if the old value it has is still there when the statement is made.
In your case, you are just issuing an immediate 'findById', which will obtain the latest entity data (usually anyway, this still depends on caching options), and then modifying the name parameter. This is equivalent to an 'update X set name=:newName where id=:id' type logic statement. There is only a very small window where it might fail, such as if someone does the same thing concurrently. It will NOT work for the usecase you describe, where two users may read in the same data but issue REST requests to modify parts at slightly offset times.
What you need is to serialize the entity data back, or at least the version of the data, and then force accepting it on the way back.
public MyEntity execute(MyEntityUpdateParameters parameters) {
MyEntity item = repo.findById(parameters.id()).orElseThrow();
if (item.getVersion() != parameters.getVersion()
throw new My409ConflictException();
item.setName(parameters.name());
return repo.save(item);
}
You will then also need to have your container handle the JPA persistence exception that may get thrown when there are concurrent updates that it detects when it issues the update statement to ensure you get the 409 message you get, but there are tutorials on handling it
3 Comments
Explore related questions
See similar questions with these tags.