There's an entity Foo
with a @Version
column. If I want to delete it I expect Spring Data JPA and/or Hibernate to check whether the current value of the @Version
column matches the one in the database. If it does not, the deletion should be rejected. This works as expected with a detached entity:
@Transactional
public void delete(Foo foo) {
fooRepository.delete(foo); // throws ObjectOptimisticLockingFailureException
}
But if I load the entity first from the repository and then delete it within the same transaction using a different version the deletion passes regardless of the value of @Version
column:
@Transactional
public void delete(int fooId, long version) {
Foo foo = fooRepository.findOne(fooId);
foo.setVersion(version);
fooRepository.delete(foo); // passes regardless of value of version
}
When I look into the Hibernate debug output, the version comparison is performed (delete from foo where id=? and version=?
) but not with the effect I'm expecting.
What am I missing?
This tutorial shows how to enable Optimistic locking in Spring Data JPA. Optimistic Locking is a mechanism which ensures that data has not changed externally within a transaction. To enable Optimistic Locking we need to use a version field annotated with @Version. This annotation is provided by JPA specification ( tutorial ).
JPA supports two types of locking mechanisms: optimistic model and pessimistic model. We will try different kinds of locking on a Spring Data JPA application with concurrent transactional code. Locking is a mechanism that allows parallel work with the same data in the database.
JPA provides us with two different optimistic lock modes (and two aliases): OPTIMISTIC obtains an optimistic read lock for all entities containing a version attribute. OPTIMISTIC_FORCE_INCREMENT obtains an optimistic lock the same as OPTIMISTIC and additionally increments the version attribute value. READ is a synonym for OPTIMISTIC.
It's good to know that in contrast to optimistic locking JPA gives us pessimistic locking. It's another mechanism for handling concurrent access for data. We cover pessimistic locking in one of our previous articles – Pessimistic Locking in JPA .
From the JPA specification, section 3.4.2:
An entity may access the state of its version field or property or export a method for use by the application to access the version, but must not modify the version value. With the exception noted in section 4.10, only the persistence provider is permitted to set or update the value of the version attribute in the object.
The purpose of the version property is to guard us from concurrent updates that may happen after the object is loaded in the current persistence context, and Hibernate implements it by ignoring any value you set manually, but rather uses the value obtained from the database when the object is loaded. To verify this, enable printing of bound variable values as well and you will notice that the value from the database is used.
For example, the standard solution that is used in practice when working with DTOs is to perform the check manually when updating entity state from DTOs:
if (entity.getVersion() != dto.getVersion()) {
throw new OptimisticLockException("...");
}
Of course you can make this more generic by extending from a base class that provides this check for all version-able entities, or in some util method. For example, some authors do it in the version setter directly:
public void setVersion(long version) {
if (this.version != version) {
throw new OptimisticLockException("...");
}
}
Hibernate performs this check automatically for detached entities, as can be seen in the implementation of DefaultMergeEventListener
:
else if (isVersionChanged(entity, source, persister, target)) {
if (source.getFactory().getStatistics().isStatisticsEnabled()) {
source.getFactory().getStatisticsImplementor()
.optimisticFailure(entityName);
}
throw new StaleObjectStateException(entityName, id);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With