Spring JPA data: remove optimistic blocking optimization semantics

There is a Foo object with a @Version column. If I want to delete it, I expect Spring Data JPA and / or Hibernate to check if the current value of the @Version column matches that in the database. If this is not the case, the deletion should be rejected. This works as expected with a single object:

 @Transactional public void delete(Foo foo) { fooRepository.delete(foo); // throws ObjectOptimisticLockingFailureException } 

But if I first load the object from the repository and delete it within one transaction using a different version, the deletion will occur regardless of the value of the @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 at the output of Hibernate debugging, the version comparison is done ( delete from foo where id=? and version=? ), But not with the effect that I expect.

What am I missing?

+8
java spring-data-jpa hibernate jpa
source share
2 answers

From the JPA specification , section 3.4.2 :

An entity can access the state of its version field or property, or export the method to be used by the application to access the version, but must not change the version value. Except as specified in Section 4.10, only a persistence provider is allowed to set or update the version attribute value in an object.

The purpose of the version property is to protect us from simultaneous updates that may occur after the object is loaded in the current persistence context, and Hibernate implements it, ignoring any value that you set manually, but rather uses the value obtained from the database when loading the object . To verify this, enable the printing of the values ​​of the associated variables, and you will also notice that the value from the database is being used.

For example, the standard solution, which is used in practice when working with DTO, must perform a manual check when updating the state of an object from the DTO:

 if (entity.getVersion() != dto.getVersion()) { throw new OptimisticLockException("..."); } 

Of course, you can make this more general by going from the base class, which provides this check for all entities supported by versions or in some sort of recycling method. For example, some authors do this directly in the version editor:

 public void setVersion(long version) { if (this.version != version) { throw new OptimisticLockException("..."); } } 

Hibernate automatically performs this check for individual objects, as can be seen from 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); } 
+6
source share

According to the JPA specification (section 11.1.54 , emphasis mine):

The Version annotation indicates a version field or property of an entity class that serves as its optimistic lock value. The version is used to ensure integrity during the merge operation and for optimistic concurrency management.

The execution of the delete repository operation in an unmanaged instance first performs a merge and, therefore, an ObjectOptimisticLockingFailureException as expected.

Performing a delete repository operation in a managed instance, however, causes delete delete in the underlying EntityManager , therefore, is no exception.


Thus, the specification requires that the @Version field be used by merge , which is not called for the managed instance, therefore, in the second case, there was no error.

+5
source share

All Articles