JPA - When to use OPTIMISTIC_FORCE_INCREMENT lock mode?

[Updated: Jan 1, 2018, Created: Dec 18, 2017]

LockModeType.OPTIMISTIC_FORCE_INCREMENT can be used in the scenarios where we want to lock another entity while updating the current entity. The the two entities can be or cannot be directly related, but they might be dependent on each other from business logic perspective.

Example

The Entity

@Entity
public class Employee {
  @Id
  @GeneratedValue
  private long id;
  @Version
  private long version;
  private String name;
  private int salary;
  @ManyToOne(cascade = CascadeType.ALL)
  private Department department;
    .............
}
@Entity
public class Department {
  @Id
  @GeneratedValue
  private Integer id;
  @Version
  private long version;
  private String name;
    .............
}

Example without OPTIMISTIC_FORCE_INCREMENT

In following example, we are going to use threads to simulate two users. One thread will update the Employee entity, during that time another thread will update/commit the corresponding Department entity:

public class EmployeeUpdateWithoutForceIncrement {
  private static EntityManagerFactory entityManagerFactory =
          Persistence.createEntityManagerFactory("example-unit");

  public static void main(String[] args) throws Exception {
      ExecutorService es = Executors.newFixedThreadPool(2);
      try {
          persistEmployee();
          es.execute(() -> {
              employeeUpdate();
          });
          es.execute(() -> {
              //simulating other user update
              departmentUpdate();
          });
          es.shutdown();
          es.awaitTermination(10, TimeUnit.SECONDS);
      } finally {
          entityManagerFactory.close();
      }
  }

  private static void employeeUpdate() {
      EntityManager em = entityManagerFactory.createEntityManager();
      Employee employee = em.find(Employee.class, 1L);
      em.getTransaction().begin();
      employee.setSalary(3000);
      try {
          TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      System.out.println("-- updating employee --");
      em.getTransaction().commit();
      em.close();
      System.out.println("Employee updated 1: " + employee);
  }

  private static void departmentUpdate() {
      EntityManager em = entityManagerFactory.createEntityManager();
      Department department = em.find(Department.class, 2);
      em.getTransaction().begin();
      department.setName("Admin");
      System.out.println("-- updating department --");
      em.getTransaction().commit();
      em.close();
      System.out.println("Department updated: " + department);
  }

  public static void persistEmployee() {
      Employee employee = new Employee("Joe", 2000, Department.create("Account"));
      EntityManager em = entityManagerFactory.createEntityManager();
      em.getTransaction().begin();
      em.persist(employee);
      System.out.println("-- persisting employee --");
      em.getTransaction().commit();
      em.close();
      System.out.println("Employee persisted: " + employee);
  }
}
-- persisting employee --
Employee persisted: Employee{id=1, version=0, name='Joe', salary=2000, department=Department{id=2, version=0, name='Account'}}
-- updating department --
Department updated: Department{id=2, version=1, name='Admin'}
-- updating employee --
Employee updated 1: Employee{id=1, version=1, name='Joe', salary=3000, department=Department{id=2, version=0, name='Account'}}

As seen above, the commit in the first thread (which we delayed purposely) happened after the second thread's commit. The second thread's updates of Department entity are lost which will be unexpected for the second thread. To avoid that, we should lock (optimistically with force version update) the corresponding Department entity during the first thread update of Employee entity. That will result OptimisticLockException in the first thread (due to version contention), which will be good for our purpose, because the first thread is the one which is overwriting the second's thread changes:

public class EmployeeUpdateWithForceIncrement {
  private static EntityManagerFactory entityManagerFactory =
          Persistence.createEntityManagerFactory("example-unit");
    .............
  private static void employeeUpdate() {
      EntityManager em = entityManagerFactory.createEntityManager();
      Employee employee = em.find(Employee.class, 1L);
      em.getTransaction().begin();
      em.lock(employee.getDepartment(), LockModeType.OPTIMISTIC_FORCE_INCREMENT);
      employee.setSalary(3000);
      try {
          TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      System.out.println("-- updating employee --");
      em.getTransaction().commit();
      em.close();
      System.out.println("Employee updated 1: " + employee);
  }
    .............
}
-- persisting employee --
Employee persisted: Employee{id=1, version=0, name='Joe', salary=2000, department=Department{id=2, version=0, name='Account'}}
-- updating department --
Department updated: Department{id=2, version=1, name='Admin'}
-- updating employee --
Exception in thread "pool-2-thread-1" javax.persistence.RollbackException: Error while committing the transaction
	at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:77)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:71)
	at com.logicbig.example.EmployeeUpdateWithForceIncrement.employeeUpdate(EmployeeUpdateWithForceIncrement.java:45)
	at com.logicbig.example.EmployeeUpdateWithForceIncrement.lambda$main$0(EmployeeUpdateWithForceIncrement.java:20)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.logicbig.example.Department#2]
	at org.hibernate.internal.ExceptionConverterImpl.wrapStaleStateException(ExceptionConverterImpl.java:202)
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:88)
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)
	at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:56)
	... 6 more
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.logicbig.example.Department#2]
	at org.hibernate.persister.entity.AbstractEntityPersister.forceVersionIncrement(AbstractEntityPersister.java:1690)
	at org.hibernate.action.internal.EntityIncrementVersionProcess.doBeforeTransactionCompletion(EntityIncrementVersionProcess.java:43)
	at org.hibernate.engine.spi.ActionQueue$BeforeTransactionCompletionProcessQueue.beforeTransactionCompletion(ActionQueue.java:935)
	at org.hibernate.engine.spi.ActionQueue.beforeTransactionCompletion(ActionQueue.java:510)
	at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2414)
	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:156)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:68)
	... 5 more

Example Project

Dependencies and Technologies Used :

  • h2 1.4.196: H2 Database Engine.
  • hibernate-core 5.2.12.Final: The core O/RM functionality as provided by Hibernate.
  • JDK 1.8
  • Maven 3.3.9

OPTIMISTIC_FORCE_INCREMENT use case Example Select All Download
  • optimistic-force-increment-use-case-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources
          • META-INF

See Also