Spring Data JPA: Delete Optimistic Locking semantics

aha :

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?

Dragan Bozanovic :

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);
}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=444909&siteId=1