A beginner’s guide to Java Persistence locking

Implicit locking

In concurrency theory, locking is used for protecting mutable shared data against hazardous data integrity anomalies. Because lock management is a very complex problem, most applications rely on their data provider implicit locking techniques.

Delegating the whole locking responsibility to the database system can both simplify application development and prevent concurrency issues, such as deadlocking. Deadlocks can still occur, but the database can detect and take safety measures (arbitrarily releasing one of the two competing locks).

Physical locks

Most database systems use shared (read) and exclusive (write) locks, attributed to specific locking elements (rows, tables). While physical locking is demanded by the SQL standard, the pessimistic approach might hinder scalability.

Modern databases have implemented lightweight locking techniques, such as multiversion concurrency control.

The implicit database locking is hidden behind the transaction isolation level configuration. Each isolation level comes with a predefined locking scheme, aimed for preventing a certain set of data integrity anomalies.

READ COMMITTED uses query-level shared locks and exclusive locks for the current transaction modified data. REPEATABLE READ and SERIALIZABLE use transaction-level shared locks when reading and exclusive locks when writing.

Logical locks

If database locking is sufficient for batch processing systems, a multi-request web flow spans over several database transactions. For long conversations, a logical (optimistic) locking mechanism is much more appropriate.

Paired with a conversation-level repeatable read storage, optimistic locking can ensure data integrity without trading scalability.

JPA supports both optimistic locking and persistence context repeatable reads, making it ideal for implementing logical transactions.

Explicit locking

While implicit locking is probably the best choice for most applications concurrency control requirements, there might be times when you want a finer-grained locking strategy.

Most database systems support query-time exclusive locking directives, such as SELECT FOR UPDATE or SELECT FOR SHARE. We can, therefore, use lower level default isolation levels (READ COMMITTED), while requesting share or exclusive locks for specific transaction scenarios.

Most optimistic locking implementations verify modified data only, but JPA allows explicit optimistic locking as well.

JPA locking

As a database abstraction layer, JPA can benefit from the implicit locking mechanisms offered by the underlying RDBMS. For logical locking, JPA offers an optional automated entity version control mechanism as well.

JPA supports explicit locking for the following operations:

Explicit lock types

The LockModeType contains the following optimistic and pessimistic locking modes:

Lock Mode Type Description
NONE In the absence of explicit locking, the application will use implicit locking (optimistic or pessimistic)
OPTIMISTIC Always issues a version check upon transaction commit, therefore ensuring optimistic locking repeatable reads.
READ Same as OPTIMISTIC.
OPTIMISTIC_FORCE_INCREMENT Always increases the entity version (even when the entity doesn’t change) and issues a version check upon transaction commit, therefore ensuring optimistic locking repeatable reads.
WRITE Same as OPTIMISTIC_FORCE_INCREMENT.
PESSIMISTIC_READ A shared lock is acquired to prevent any other transaction from acquiring a PESSIMISTIC_WRITE lock.
PESSIMISTIC_WRITE An exclusive lock is acquired to prevent any other transaction from acquiring a PESSIMISTIC_READ or a PESSIMISTIC_WRITE lock.
PESSIMISTIC_FORCE_INCREMENT A database lock is acquired to prevent any other transaction from acquiring a PESSIMISTIC_READ or a PESSIMISTIC_WRITE lock and the entity version is incremented upon transaction commit.

If you like this article, I bet you are going to love my book as well.

Lock scope and timeouts

JPA 2.0 defined the javax.persistence.lock.scope property, taking one of the following values:

  • NORMAL

    Because object graphs can span to multiple tables, an explicit locking request might propagate to more than one table (e.g. joined inheritance, secondary tables).

    Because the entire entity associated row(s) are locked, many-to-one and one-to-one foreign keys will be locked as well but without locking the other side parent associations. This scope doesn’t propagate to children collections.

  • EXTENDED

    The explicit lock is propagated to element collections and junction tables, but it doesn’t lock the actual children entities. The lock is only useful for protecting against removing existing children, while permitting phantom reads or changes to the actual children entity states.

JPA 2.0 also introduced the javax.persistence.lock.timeout property, allowing us to configure the amount of time (milliseconds) a lock request will wait before throwing a PessimisticLockException.

Hibernate locking

Hibernate supports all JPA locking modes and some additional specific locking options. As with JPA, explicit locking can be configured for the following operations:

The LockModeConverter takes care of mapping JPA and Hibernate lock modes as follows:

Hibernate LockMode JPA LockModeType
NONE NONE
OPTIMISTIC
READ
OPTIMISTIC
OPTIMISTIC_FORCE_INCREMENT
WRITE
OPTIMISTIC_FORCE_INCREMENT
PESSIMISTIC_READ PESSIMISTIC_READ
PESSIMISTIC_WRITE
UPGRADE
UPGRADE_NOWAIT
UPGRADE_SKIPLOCKED
PESSIMISTIC_WRITE
PESSIMISTIC_FORCE_INCREMENT
FORCE
PESSIMISTIC_FORCE_INCREMENT

The UPGRADE and FORCE lock modes are deprecated in favor of PESSIMISTIC_WRITE.

UPGRADE_NOWAIT and UPGRADE_SKIPLOCKED use an Oracle-style select for update nowait or select for update skip locked syntax respectively.

Lock scope and timeouts

Hibernate also defines scope and timeout locking options:

  • scope

    The lock scope allows explicit locking cascade to owned associations.

  • timeout

    A timeout interval may prevent a locking request from waiting indefinitely.

In my next articles, I am going to unravel different explicit locking design pasterns, so stay tuned!

If you liked this article, you might want to subscribe to my newsletter too.

8 thoughts on “A beginner’s guide to Java Persistence locking

  1. Hello Vlad,

    my name is Dragan.
    I am a reader of your blog and live in Germany.

    I am trying to run your code (Hibernate tutorial), but maven is
    producing an error after calling mvn install.

    Is it possible to send you a attachment of the error-messages.

    Nice work your blog.

    Dragan

  2. Vlad – Very nice summary of the LockMode pessimistic locking support provided by JPA and Hibernate; this is of tremendous value to the Java Hibernate community. I have three comments:

    First – the ISO SQL Standard does not demand the existence of physical locks per se. The SQL Standard merely describes the handling of concurrency anomalies (dirty read, non-repeatable read, and phantom rows) and specifies four different isolation levels for applications to specify which anomalies they are willing to permit. A great resource is the 2007 paper by Hal Berenson et al. (Hal Berenson, Philip A. Bernstein, Jim Gray, Jim Melton, Elizabeth J. O’Neil, Patrick E. O’Neil: A Critique of ANSI SQL Isolation Levels. CoRR abs/cs/0701157) which is available online. Second, as I am sure you are aware, the SQL Standard does not include anything on the semantics of MVCC, as the Standard treats multi-version concurrency control (snapshot isolation) as an implementation, designed, like 2-phase locking, to mitigate the issues caused by the three concurrency anomalies. However, practictioners must certainly understand what is happening when combining the use of LockMode with a system such as Oracle that uses both snapshot isolation and 2-PL.

    Finally, I would note that with Hibernate in particular, the implementation for LockMode is largely accomplished by the code for individual SQL dialects, since this involves the annotation of an SQL SELECT statement to be shipped to the underlying DBMS. In this the application developer must be careful and adequately test their application (a difficult thing to do) because much of this behaviour is not thoroughly tested for every possible software combination.

    The interaction of LockMode by Hibernate and the 2-PL lock implementation by the underlying DBMS is complex; there are many options to consider, particularly with systems such as Microsoft SQL Server or SQL Anywhere that support *both* snapshot isolation and “ordinary” 2-PL transactions executing simultaneously. For example, SQL Anywhere, the system with which I am most familiar, supports a wide variety of legacy locking “hints” (http://dcx.sybase.com/index.html#sa160/en/dbreference/from-statement.html), only some of which does the Hibernate 4.3.6 AbstractTransactSQLDialect exploit.

    1. Thanks Glenn,

      1. You are right, SQL standard says nothing about locking, although most database vendors use either 2PL or MVCC or a combination of both.
      My claims were based on Jim Gray’s “Transaction concepts: Virtues and Limitations” paper which
      specifies how a RDBMS should use locking to “serialize updates”.

      Thanks for the link, I will investigate this paper since it’s much newer and more relevant.

      2. Hibernate uses the Dialect to resolve the database specific locking syntax, but eventually the MVCC and locking combinations should be addressed by the database server.
      Even when MVCC is used, there’s still some locking involved. Any DML statement must acquire locks, to prevent other transactions from issuing another DML statements that
      would degenerate into a “lost update” situation.

      As for testing, I always advocate for:

      Always checking the auto-generated statements
      – Using database specific integration tests, instead of a faster in-memory one (HSQLDB, H2, Derby)

      As for the Sybase dialect regression, you should file a Hibernate issue to request overriding the appendLockHint method:


      @Override
      public String appendLockHint(LockOptions lockOptions, String tableName) {
      return lockOptions.getLockMode().greaterThan( LockMode.READ ) ? tableName + " holdlock" : tableName;
      }

      to accommodate more LockOptions semantics, if possible.

      For other database specific locks a native query is a better alternative, then relying on Hibernate abstraction layer.

  3. One more comment – it isn’t clear to me how the support for LockMode in Hibernate is able to apply the correct semantics in the case of native queries. The Sybase ASE and SQL Anywhere Hibernate 4.3.6 dialects use table hints as the SQL syntax for supporting different LockModes, but I cannot see this working for complex native SQL queries. SQL Anywhere supports the FOR UPDATE BY LOCK clause for a SELECT statement, but while prior versions of the SybaseAnywhereDialect used that clause, the current dialect does not.

    1. The LockOptions are applies to entities, and for more complex queries you need to use native queries anyway. The SybaseASE15Dialect overrides the AbstractTransactSQLDialect and it overrides the appendLockHint method without adding proper support for the specific lock syntax, like it’s the case of SQLServer2005Dialect.

      You need to open a Hibernate JIRA issue for this or even better, submit a pull-request for this issue.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s