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

1 Answers

0
gpaglia On

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.