Update the Locking documentation

- add a side-by-side comparison of LockMode and LockModeType
- describe the follow-on-locking behavior
This commit is contained in:
Vlad Mihalcea 2016-06-23 16:01:07 +03:00
parent 253820a289
commit 9ac93156a0
4 changed files with 118 additions and 10 deletions

View File

@ -96,18 +96,23 @@ Hibernate always uses the locking mechanism of the database, and never lock obje
====
[[locking-LockMode]]
=== The `LockMode` class
=== `LockMode` and `LockModeType`
The `LockMode` class defines the different lock levels that Hibernate can acquire.
Long before JPA 1.0, Hibernate already defined various explicit locking strategies through its `LockMode` enumeration.
JPA comes with its own http://docs.oracle.com/javaee/7/api/javax/persistence/LockModeType.html[`LockModeType`] enumeration which defines similar strategies as the Hibernate-native `LockMode`.
[cols=",",]
[cols=",",, options="header"]
|=======================================================================
|LockMode.WRITE |acquired automatically when Hibernate updates or inserts a row.
|LockMode.UPGRADE |acquired upon explicit user request using `SELECT ... FOR UPDATE` on databases which support that syntax.
|LockMode.UPGRADE_NOWAIT |acquired upon explicit user request using a `SELECT ... FOR UPDATE NOWAIT` in Oracle.
|LockMode.UPGRADE_SKIPLOCKED |acquired upon explicit user request using a `SELECT ... FOR UPDATE SKIP LOCKED` in Oracle, or `SELECT ... with (rowlock, updlock, readpast) in SQL Server`.
|LockMode.READ |acquired automatically when Hibernate reads data under Repeatable Read or Serializable isolation level. It can be re-acquired by explicit user request.
|LockMode.NONE |The absence of a lock. All objects switch to this lock mode at the end of a Transaction. Objects associated with the session via a call to update() or saveOrUpdate() also start out in this lock mode.
|`LockModeType`|`LockMode`|Description
|`NONE`|`NONE` |The absence of a lock. All objects switch to this lock mode at the end of a Transaction. Objects associated with the session via a call to `update()` or `saveOrUpdate()` also start out in this lock mode.
|`READ` and `OPTIMISTIC`|`READ` | The entity version is checked towards the end of the currently running transaction.
|`WRITE` and `OPTIMISTIC_FORCE_INCREMENT`|`WRITE` | The entity version is incremented automatically even if the entity has not changed.
|`PESSIMISTIC_FORCE_INCREMENT`|`PESSIMISTIC_FORCE_INCREMENT` | The entity is locked pessimistically and its version is incremented automatically even if the entity has not changed.
|`PESSIMISTIC_READ`|`PESSIMISTIC_READ` | The entity is locked pessimistically using a shared lock, if the database supports such a feature. Otherwise, an explicit lock is used.
|`PESSIMISTIC_WRITE`|`PESSIMISTIC_WRITE`, `UPGRADE` | The entity is locked using an explicit lock.
|`PESSIMISTIC_WRITE` with a `javax.persistence.lock.timeout` setting of 0 |`UPGRADE_NOWAIT` | The lock acquisition request fails fast if the row s already locked.
|`PESSIMISTIC_WRITE` with a `javax.persistence.lock.timeout` setting of -2 |`UPGRADE_SKIPLOCKED` | The lock acquisition request skips the already locked rows. It uses a `SELECT ... FOR UPDATE SKIP LOCKED` in Oracle and PostgreSQL 9.5, or `SELECT ... with (rowlock, updlock, readpast) in SQL Server`.
|=======================================================================
The explicit user request mentioned above occurs as a consequence of any of the following actions:
@ -182,3 +187,50 @@ include::{sourcedir}/ExplicitLockingTest.java[tags=locking-buildLockRequest-exam
include::{extrasdir}/locking-buildLockRequest-example.sql[]
----
====
[[locking-follow-on]]
=== Follow-on-locking
When using Oracle, the https://docs.oracle.com/database/121/SQLRF/statements_10002.htm#SQLRF55371[`FOR UPDATE` exclusive locking clause] cannot be used with:
- `DISTINCT`
- `GROUP BY`
- `UNION`
- inlined views (derived tables), therefore, affecting the legacy Oracle pagination mechanism as well.
For this reason, Hibernate uses secondary selects to lock the previously fetched entities.
[[locking-follow-on-example]]
.Follow-on-locking example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/ExplicitLockingTest.java[tags=locking-follow-on-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/locking-follow-on-example.sql[]
----
====
[NOTE]
====
To avoid the N+1 query problem, a separate query can be used to apply the lock using the associated entity identifiers.
====
[[locking-follow-on-secondary-query-example]]
.Secondary query entity locking
====
[source, JAVA, indent=0]
----
include::{sourcedir}/ExplicitLockingTest.java[tags=locking-follow-on-secondary-query-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/locking-follow-on-secondary-query-example.sql[]
----
====
The lock request was moved from the original query to a secondary one which takes the previously fetched entities to lock their associated database records.

View File

@ -0,0 +1,10 @@
SELECT DISTINCT p.id as id1_0_, p."name" as name2_0_
FROM Person p
SELECT id
FROM Person
WHERE id = 1 FOR UPDATE
SELECT id
FROM Person
WHERE id = 1 FOR UPDATE

View File

@ -0,0 +1,7 @@
SELECT DISTINCT p.id as id1_0_, p."name" as name2_0_
FROM Person p
SELECT p.id as col_0_0_
FROM Person p
WHERE p.id IN ( 1 , 2 )
FOR UPDATE

View File

@ -23,8 +23,10 @@ import javax.persistence.PessimisticLockScope;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect;
import org.junit.Test;
import org.jboss.logging.Logger;
@ -132,6 +134,43 @@ public class ExplicitLockingTest extends BaseEntityManagerFunctionalTestCase {
}
@Test
@RequiresDialect(Oracle8iDialect.class)
public void testFollowOnLocking() {
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "testBuildlLockRequest" );
Person person1 = new Person( "John Doe" );
Person person2 = new Person( "Mrs. John Doe" );
entityManager.persist( person1 );
entityManager.persist( person2 );
entityManager.flush();
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::locking-follow-on-example[]
List<Person> persons = entityManager.createQuery(
"select DISTINCT p from Person p", Person.class)
.setLockMode( LockModeType.PESSIMISTIC_WRITE )
.getResultList();
//end::locking-follow-on-example[]
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::locking-follow-on-secondary-query-example[]
List<Person> persons = entityManager.createQuery(
"select DISTINCT p from Person p", Person.class)
.getResultList();
entityManager.createQuery(
"select p.id from Person p where p in :persons")
.setLockMode( LockModeType.PESSIMISTIC_WRITE )
.setParameter( "persons", persons )
.getResultList();
//end::locking-follow-on-secondary-query-example[]
} );
}
//tag::locking-jpa-query-hints-scope-entity-example[]
@Entity(name = "Person")
public static class Person {