Close

JPA - Using Multiple Pessimistic read Locks

[Updated: Dec 22, 2017, Created: Dec 22, 2017]

In the following example, we will use multiple LockModeType.PESSIMISTIC_READ locks (shared locks). According to JPA specification, a provider should allow multiple PESSIMISTIC_READ locks to read entities at the same time. However, specification also says:

It is permissible for an implementation to use LockModeType.PESSIMISTIC_WRITE where LockModeType.PESSIMISTIC_READ was requested, but not vice versa.

Let's see how Hibernate applies multiple shared locks.

Example

The Entity

@Entity
public class Article {
  @Id
  @GeneratedValue
  private long id;
  private String content;
    .............
}

Using multiple Shared locks simultaneously

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

  public static void main(String[] args) throws Exception {
      ExecutorService es = Executors.newFixedThreadPool(3);
      try {
          persistArticle();

          es.execute(() -> {
              readArticle();
          });

          es.execute(() -> {
              //simulating other user by using different thread
              readArticle();
          });
          es.shutdown();
          es.awaitTermination(1, TimeUnit.MINUTES);
      } finally {
          entityManagerFactory.close();
      }
  }

  private static void readArticle() {
      log("before acquiring read lock");
      EntityManager em = entityManagerFactory.createEntityManager();
      em.getTransaction().begin();
      Article article = em.find(Article.class, 1L, LockModeType.PESSIMISTIC_READ);
      log("After acquiring read lock", article);
      try {
          TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      em.getTransaction().commit();
      em.close();
      log("Article after read commit", article);
  }

  public static void persistArticle() {
      log("persisting article");
      Article article = new Article("test article");
      EntityManager em = entityManagerFactory.createEntityManager();
      em.getTransaction().begin();
      em.persist(article);
      em.getTransaction().commit();
      em.close();
      log("Article persisted", article);
  }

  private static void log(Object... msgs) {
      System.out.println(LocalTime.now() + " - " + Thread.currentThread().getName() +
              " - " + Arrays.toString(msgs));
  }
}
10:33:15.049 - main - [persisting article]
10:33:15.130 - main - [Article persisted, Article{id=1, content='test article'}]
10:33:15.131 - pool-2-thread-1 - [before acquiring read lock]
10:33:15.132 - pool-2-thread-2 - [before acquiring read lock]
10:33:15.144 - pool-2-thread-2 - [After acquiring read lock, Article{id=1, content='test article'}]
10:33:16.145 - pool-2-thread-2 - [Article after read commit, Article{id=1, content='test article'}]
10:33:16.152 - pool-2-thread-1 - [After acquiring read lock, Article{id=1, content='test article'}]
10:33:17.154 - pool-2-thread-1 - [Article after read commit, Article{id=1, content='test article'}]

As seen in above output, second read transaction blocks till the first transaction committed. It is exactly what happens with LockModeType.PESSIMISTIC_WRITE lock.

On setting hibernate.show_sql=true in persistence.xml and running above example again:

Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table Article (id bigint not null, content varchar(255), primary key (id))
10:36:54.202 - main - [persisting article]
Hibernate: call next value for hibernate_sequence
Hibernate: insert into Article (content, id) values (?, ?)
10:36:54.280 - main - [Article persisted, Article{id=1, content='test article'}]
10:36:54.280 - pool-2-thread-1 - [before acquiring read lock]
10:36:54.280 - pool-2-thread-2 - [before acquiring read lock]
Hibernate: select article0_.id as id1_0_0_, article0_.content as content2_0_0_ from Article article0_ where article0_.id=? for update
Hibernate: select article0_.id as id1_0_0_, article0_.content as content2_0_0_ from Article article0_ where article0_.id=? for update
10:36:54.302 - pool-2-thread-2 - [After acquiring read lock, Article{id=1, content='test article'}]
10:36:55.314 - pool-2-thread-2 - [Article after read commit, Article{id=1, content='test article'}]
10:36:55.329 - pool-2-thread-1 - [After acquiring read lock, Article{id=1, content='test article'}]
10:36:56.331 - pool-2-thread-1 - [Article after read commit, Article{id=1, content='test article'}]

As seen, Hibernate uses 'Select for update' statement which locks the rows for write. Hibernate uses the same sql statement in case of LockModeType.PESSIMISTIC_WRITE. Also EclipseLink has same behavior for shared locks.

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.
    Implements javax.persistence:javax.persistence-api version 2.1
  • JDK 1.8
  • Maven 3.3.9

Using @Version annotation Example Select All Download
  • pessimistic-multiple-shared-locks
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • PessimisticSharedLocksExample.java
          • resources
            • META-INF

    See Also