I'm trying to use @Lock annotation on Spring Jpa repository, setting lock mode to OPTIMISTIC_FORCE_INCREMENT, but the version is not incremented.
The very same test case using EntityManager.find() and specifying the lock mode works OK. The very same test case using PESSIMISTIC_FORCE_INCREMENT also works ok.
I wonder if I'm missing something or I am hitting some undocumented limitation or simply a bug.
My environment: Java 11 on Windows 10 Spring Boot 2.1.3.RELEASE with related Spring components (like SPring Data Jpa) as per Spring Boot BOM. Hibernate 5.3.7.Final H2 1.4.199 [but I've tested with MariaDB getting the same results]
The entity:
@Entity
@Access(AccessType.FIELD)
public class Parent {
@Id private Long id;
@Version private Long version;
private String desc;
... getters, setters, hashcode and equals
}
The repository ...
public interface ParentRepository extends JpaRepository<Parent, Long> {
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
Optional<Parent> findById(Long id);
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
@Query("select p from Parent p where p.id = ?1")
Optional<Parent> optimisticFindById(Long id);
@Lock(LockModeType.PESSIMISTIC_FORCE_INCREMENT)
@Query("select p from Parent p where p.id = ?1")
Optional<Parent> pessimisticFindById(Long id);
}
Test case using EntityManager.find(), the version is incremented on retrieval of entity
public void forceIncrementWithEmTest() {
// set the id
Long id = 275L;
// build the entity
Parent p0 = new Parent(id, "Hello world.");
assertThat(p0.getVersion(), is(nullValue()));
// persist it
em.persist(p0);
em.flush();
// Version is 0L as expected
assertThat(p0.getVersion(), is(0L));
// clear the persistence context
em.clear();
// get the object again from DB, requiring the lock
Parent p1 = em.find(
Parent.class,
id,
LockModeType.PESSIMISTIC_FORCE_INCREMENT);
assertThat(p1, notNullValue());
// version has been incremented as expected
assertThat(p1.getVersion(), is(1L));
// flush and clear the context
em.flush();
em.clear();
// get from DB without locks
Parent p2 = em.find(Parent.class, id);
assertThat(p2, notNullValue());
// version has not been incremented this time, as expected
assertThat(p2.getVersion(), is(1L));
}
Test case using the redeclared findById repository method the version is not incremented as I expected
@Test
@Transactional
public void forceIncrementWithRepoQueryOptimisticTest() {
// set the id
Long id = 275L;
// build the entity
Parent p0 = new Parent(id, "Hello world.");
assertThat(p0.getVersion(), is(nullValue()));
// persist it
em.persist(p0);
em.flush();
// Version is 0 as expected
assertThat(p0.getVersion(), is(0L));
// clear the persistence context
em.clear();
// get the object again from DB, requiring the lock
// this time using the repository with the @Lock annotation
// and a custom query
Optional<Parent> popt = parentRepo.optimisticFindById(id);
assertThat(popt.isPresent(), is(true));
Parent p1 = popt.get();
// I expected version to have been incremented, but instead it is still 0L
// so the @Lock annotation has had no effect
assertThat(p1.getVersion(), is(0L));
Parent p2 = parentRepo.saveAndFlush(p1);
// also the saved entity still has version not incremented
// as if the @Lock annotation was not considered.
assertThat(p2.getVersion(), is(0L));
}
I get the same behaviour with a custom method with a @Query annotation.
Substituting OPTIMISTIC_FORCE_INCREMENT with PESSIMISTIC_FORCE_INCREMENT I get the expected behaviour in all cases.
The complete setup and test cases are available at https://github.com/gpaglia/spring-lock-problem.git
I answer my own question, thanks to Jens for pointing out the oversight on my side and for clarifying the expected behaviour; maybe this can be useful to others as the documentation is not very clear on this point.
I understand now that PESSIMISTIC_FORCE_INCREMENT will force the increment on fetching the entity, therefore, within the transaction boundaries, the entity has the version already incremented.
On the other side, OPTIMISTIC_FORCE_INCREMENT will increment the version only on transaction commit, therefore, within the transaction boundaries, the entity has the version not yet incremented.
I updated the test cases on github if this can be useful to others.