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:
parent
253820a289
commit
9ac93156a0
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue