The server loads the item with the EntityManager which returns Item(id = 1, version = 1, name = “a”), it changes the name and persist Item(id = 1, version = 1, name = “b”). Hibernate increments the version to 2.
That’s a misuse of the JPA API, and the root cause of your bug.
If you use entityManager.merge(itemFromClient)
instead, the optimistic locking version would be checked automatically, and “updates from the past” rejected.
One caveat is that entityManager.merge
will merge the entire state of the entity. If you only want to update certain fields, things are a bit messy with plain JPA. Specifically, because you may not assign the version property, you must check the version yourself. However, that code is easy to reuse:
<E extends BaseEntity> E find(E clientEntity) {
E entity = entityManager.find(clientEntity.getClass(), clientEntity.getId());
if (entity.getVersion() != clientEntity.getVersion()) {
throw new ObjectOptimisticLockingFailureException(...);
}
return entity;
}
and then you can simply do:
public Item updateItem(Item itemFromClient) {
Item item = find(itemFromClient);
item.setName(itemFromClient.getName());
return item;
}
depending on the nature of the unmodifiable fields, you may also be able to do:
public Item updateItem(Item itemFromClient) {
Item item = entityManager.merge(itemFromClient);
item.setLastUpdated(now());
}
As for doing this in a DDD way, the version checking is an implementation detail of the persistence technology, and should therefore occur in the repository implementation.
To pass the version through the various layers of the app, I find it convenient to make the version part of the domain entity or value object. That way, other layers do not have to explicitly interact with the version field.