diff --git a/hibernate-core/src/main/java/org/hibernate/LockMode.java b/hibernate-core/src/main/java/org/hibernate/LockMode.java index 8b2087eea4a7..d442a23bf6c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/LockMode.java +++ b/hibernate-core/src/main/java/org/hibernate/LockMode.java @@ -39,7 +39,6 @@ * * @see Session#lock(Object, LockMode) * @see LockModeType - * @see LockOptions * @see org.hibernate.annotations.OptimisticLocking */ public enum LockMode implements FindOption, RefreshOption { @@ -119,7 +118,10 @@ public enum LockMode implements FindOption, RefreshOption { * lock mode, if the lock is successfully obtained, are the same * as {@link #PESSIMISTIC_WRITE}. If the lock is not immediately * available, an exception occurs. + * + * @deprecated Use {@linkplain Timeouts#NO_WAIT} instead. */ + @Deprecated UPGRADE_NOWAIT, /** @@ -129,7 +131,10 @@ public enum LockMode implements FindOption, RefreshOption { * as {@link #PESSIMISTIC_WRITE}. But if the lock is not * immediately available, no exception occurs, but the locked * row is not returned from the database. + * + * @deprecated Use {@linkplain Locking.LockedRows#SKIP} instead. */ + @Deprecated UPGRADE_SKIPLOCKED, /** @@ -143,6 +148,10 @@ public enum LockMode implements FindOption, RefreshOption { * lock mode is equivalent to {@link #PESSIMISTIC_WRITE}. * * @see LockModeType#PESSIMISTIC_READ + * @see jakarta.persistence.Timeout + * @see Locking.Scope + * @see Locking.FollowOn + * @see Locking.LockedRows */ PESSIMISTIC_READ, @@ -152,6 +161,10 @@ public enum LockMode implements FindOption, RefreshOption { * Obtained via a {@code select for update} statement. * * @see LockModeType#PESSIMISTIC_WRITE + * @see jakarta.persistence.Timeout + * @see Locking.Scope + * @see Locking.FollowOn + * @see Locking.LockedRows */ PESSIMISTIC_WRITE, @@ -163,6 +176,10 @@ public enum LockMode implements FindOption, RefreshOption { * Only legal for versioned entity types. * * @see LockModeType#PESSIMISTIC_FORCE_INCREMENT + * @see jakarta.persistence.Timeout + * @see Locking.Scope + * @see Locking.FollowOn + * @see Locking.LockedRows */ PESSIMISTIC_FORCE_INCREMENT; diff --git a/hibernate-core/src/main/java/org/hibernate/LockOptions.java b/hibernate-core/src/main/java/org/hibernate/LockOptions.java index 36926ebedbf0..9a619e1a4db8 100644 --- a/hibernate-core/src/main/java/org/hibernate/LockOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/LockOptions.java @@ -8,14 +8,12 @@ import jakarta.persistence.Timeout; import java.io.Serializable; -import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Set; -import static jakarta.persistence.PessimisticLockScope.NORMAL; import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableSet; @@ -28,8 +26,8 @@ * are: *
@@ -160,9 +158,9 @@ public class LockOptions implements Serializable {
private final boolean immutable;
private LockMode lockMode;
private Timeout timeout;
- private PessimisticLockScope pessimisticLockScope;
- private Boolean followOnLocking;
-
+ private Locking.LockedRows lockedRowHandling;
+ private Locking.Scope scope;
+ private Locking.FollowOn followOnHandling;
private Map
- *
- *
- * @return the current {@link PessimisticLockScope}
+ * Get the associated handling for follow-on locking, if needed.
*/
- public PessimisticLockScope getLockScope() {
- return pessimisticLockScope;
+ public Locking.FollowOn getFollowOn() {
+ return followOnHandling;
}
/**
- * Set the lock scope:
- *
- *
- *
- * @param scope the new {@link PessimisticLockScope}
- * @return {@code this} for method chaining
+ * Set the associated handling for follow-on locking, if needed.
*/
- public LockOptions setLockScope(PessimisticLockScope scope) {
+ public LockOptions setFollowOn(Locking.FollowOn followOnHandling) {
if ( immutable ) {
throw new UnsupportedOperationException("immutable global instance of LockOptions");
}
- pessimisticLockScope = scope;
+ this.followOnHandling = followOnHandling;
return this;
}
/**
- * Returns a value indicating if follow-on locking was force
- * enabled or disabled, overriding the default behavior of
- * the SQL dialect.
- *
- * @return {@code true} if follow-on locking was force enabled,
- * {@code false} if follow-on locking was force disabled,
- * or {@code null} if the default behavior of the dialect
- * has not been overridden.
- *
- * @see org.hibernate.jpa.HibernateHints#HINT_FOLLOW_ON_LOCKING
- * @see org.hibernate.dialect.Dialect#useFollowOnLocking(String, org.hibernate.query.spi.QueryOptions)
+ * How locked rows should be handled.
*/
- public Boolean getFollowOnLocking() {
- return followOnLocking;
+ public Locking.LockedRows getLockedRowHandling() {
+ return lockedRowHandling;
}
/**
- * Force enable or disable the use of follow-on locking,
- * overriding the default behavior of the SQL dialect.
- *
- * @param followOnLocking The new follow-on locking setting
- * @return {@code this} for method chaining
- *
- * @see org.hibernate.jpa.HibernateHints#HINT_FOLLOW_ON_LOCKING
- * @see org.hibernate.dialect.Dialect#useFollowOnLocking(String, org.hibernate.query.spi.QueryOptions)
+ * Specify how locked rows should be handled.
*/
- public LockOptions setFollowOnLocking(Boolean followOnLocking) {
+ public void setLockedRowHandling(Locking.LockedRows lockedRowHandling) {
if ( immutable ) {
throw new UnsupportedOperationException("immutable global instance of LockOptions");
}
- this.followOnLocking = followOnLocking;
- return this;
+ this.lockedRowHandling = lockedRowHandling;
}
/**
@@ -458,13 +414,7 @@ public LockOptions makeDefensiveCopy() {
* merging the alias-specific lock modes.
*/
public void overlay(LockOptions lockOptions) {
- setLockMode( lockOptions.getLockMode() );
- setLockScope( lockOptions.getLockScope() );
- setTimeOut( lockOptions.getTimeOut() );
- if ( lockOptions.aliasSpecificLockModes != null ) {
- lockOptions.aliasSpecificLockModes.forEach(this::setAliasSpecificLockMode);
- }
- setFollowOnLocking( lockOptions.getFollowOnLocking() );
+ copy( lockOptions, this );
}
/**
@@ -478,12 +428,13 @@ public void overlay(LockOptions lockOptions) {
*/
public static LockOptions copy(LockOptions source, LockOptions destination) {
destination.setLockMode( source.getLockMode() );
- destination.setLockScope( source.getLockScope() );
- destination.setTimeOut( source.getTimeOut() );
+ destination.setScope( source.getScope() );
+ destination.setTimeout( source.getTimeout() );
+ destination.setFollowOn( source.getFollowOn() );
+ destination.setLockedRowHandling( source.getLockedRowHandling() );
if ( source.aliasSpecificLockModes != null ) {
- destination.aliasSpecificLockModes = new HashMap<>( source.aliasSpecificLockModes );
+ source.aliasSpecificLockModes.forEach(destination::setAliasSpecificLockMode);
}
- destination.setFollowOnLocking( source.getFollowOnLocking() );
return destination;
}
@@ -497,16 +448,135 @@ else if ( !(object instanceof LockOptions that) ) {
}
else {
return timeout == that.timeout
- && pessimisticLockScope == that.pessimisticLockScope
+ && scope == that.scope
&& lockMode == that.lockMode
&& Objects.equals( aliasSpecificLockModes, that.aliasSpecificLockModes )
- && Objects.equals( followOnLocking, that.followOnLocking );
+ && Objects.equals( followOnHandling, that.followOnHandling );
}
}
@Override
public int hashCode() {
- return Objects.hash( lockMode, timeout, aliasSpecificLockModes, followOnLocking, pessimisticLockScope );
+ return Objects.hash(
+ lockMode,
+ timeout,
+ scope,
+ lockedRowHandling,
+ followOnHandling,
+ aliasSpecificLockModes
+ );
+ }
+
+ /**
+ * The {@linkplain #getTimeout() timeout}, in milliseconds, associated
+ * with {@code this} options.
+ *
+ * {@link #NO_WAIT}, {@link #WAIT_FOREVER}, or {@link #SKIP_LOCKED}
+ * represent 3 "magic" values.
+ *
+ * @return a timeout in milliseconds, {@link #NO_WAIT},
+ * {@link #WAIT_FOREVER}, or {@link #SKIP_LOCKED}
+ */
+ public int getTimeOut() {
+ return getTimeout().milliseconds();
+ }
+
+ /**
+ * Set the {@linkplain #getTimeout() timeout}, in milliseconds, associated
+ * with {@code this} options.
+ *
+ * {@link #NO_WAIT}, {@link #WAIT_FOREVER}, or {@link #SKIP_LOCKED}
+ * represent 3 "magic" values.
+ *
+ * @return {@code this} for method chaining
+ *
+ * @see #getTimeOut
+ */
+ public LockOptions setTimeOut(int timeout) {
+ return setTimeout( Timeouts.interpretMilliSeconds( timeout ) );
+ }
+
+ /**
+ * Returns a value indicating if follow-on locking was force
+ * enabled or disabled, overriding the default behavior of
+ * the SQL dialect.
+ *
+ * @return {@code true} if follow-on locking was force enabled,
+ * {@code false} if follow-on locking was force disabled,
+ * or {@code null} if the default behavior of the dialect
+ * has not been overridden.
+ *
+ * @see org.hibernate.jpa.HibernateHints#HINT_FOLLOW_ON_LOCKING
+ * @see org.hibernate.dialect.Dialect#useFollowOnLocking(String, org.hibernate.query.spi.QueryOptions)
+ *
+ * @deprecated Use {@link #getFollowOn()} instead.
+ */
+ @Deprecated(since = "7.1")
+ public Boolean getFollowOnLocking() {
+ return followOnHandling == Locking.FollowOn.ALLOW;
+ }
+
+ /**
+ * Force enable or disable the use of follow-on locking,
+ * overriding the default behavior of the SQL dialect.
+ *
+ * @param followOnLocking The new follow-on locking setting
+ * @return {@code this} for method chaining
+ *
+ * @see org.hibernate.jpa.HibernateHints#HINT_FOLLOW_ON_LOCKING
+ * @see org.hibernate.dialect.Dialect#useFollowOnLocking(String, org.hibernate.query.spi.QueryOptions)
+ *
+ * @deprecated Use {@link #setFollowOn} instead.
+ */
+ @Deprecated(since = "7.1")
+ public LockOptions setFollowOnLocking(Boolean followOnLocking) {
+ if ( immutable ) {
+ throw new UnsupportedOperationException("immutable global instance of LockOptions");
+ }
+ if ( followOnLocking == Boolean.FALSE ) {
+ followOnHandling = Locking.FollowOn.DISALLOW;
+ }
+ else {
+ followOnHandling = Locking.FollowOn.ALLOW;
+ }
+ return this;
+ }
+
+ /**
+ * The current lock scope:
+ *
+ *
+ *
+ * @return the current {@link PessimisticLockScope}
+ *
+ * @deprecated Use {@link #getScope()} instead
+ */
+ @Deprecated(since = "7.1")
+ public PessimisticLockScope getLockScope() {
+ return scope.getCorrespondingJpaScope();
+ }
+
+ /**
+ * Set the lock scope:
+ *
+ *
+ *
+ * @param scope the new {@link PessimisticLockScope}
+ * @return {@code this} for method chaining
+ *
+ * @deprecated Use {@link #setScope} instead
+ */
+ @Deprecated(since = "7.1")
+ public LockOptions setLockScope(PessimisticLockScope scope) {
+ return setScope( Locking.Scope.fromJpaScope( scope ) );
}
/**
diff --git a/hibernate-core/src/main/java/org/hibernate/Locking.java b/hibernate-core/src/main/java/org/hibernate/Locking.java
new file mode 100644
index 000000000000..a50d8bd40734
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/Locking.java
@@ -0,0 +1,136 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate;
+
+import jakarta.persistence.FindOption;
+import jakarta.persistence.LockOption;
+import jakarta.persistence.PessimisticLockScope;
+import jakarta.persistence.RefreshOption;
+import jakarta.persistence.Timeout;
+import org.hibernate.dialect.Dialect;
+
+/**
+ * Support for various aspects of pessimistic locking.
+ *
+ * @see LockMode#PESSIMISTIC_READ
+ * @see LockMode#PESSIMISTIC_WRITE
+ * @see LockMode#PESSIMISTIC_FORCE_INCREMENT
+ *
+ * @author Steve Ebersole
+ */
+public interface Locking {
+
+ /**
+ * When pessimistic locking is requested, this enum defines
+ * what exactly will be locked.
+ *
+ * @apiNote Same intention as the JPA {@linkplain PessimisticLockScope},
+ * but offering the additional {@linkplain #INCLUDE_FETCHES} behavior.
+ *
+ * @see FollowOn
+ */
+ enum Scope implements FindOption, LockOption, RefreshOption {
+ /**
+ * Lock the database row(s) that correspond to the non-collection-valued
+ * persistent state of that instance. If a joined inheritance strategy is
+ * used, or if the entity is otherwise mapped to a secondary table, this
+ * entails locking the row(s) for the entity instance in the additional table(s).
+ *
+ * @see PessimisticLockScope#NORMAL
+ */
+ ROOT_ONLY( PessimisticLockScope.NORMAL ),
+
+ /**
+ * In addition to the locking behavior specified for {@linkplain #ROOT_ONLY},
+ * rows for collection tables ({@linkplain jakarta.persistence.ElementCollection},
+ * {@linkplain jakarta.persistence.OneToMany} and {@linkplain jakarta.persistence.ManyToMany})
+ * will also be locked.
+ *
+ * @see PessimisticLockScope#EXTENDED
+ */
+ INCLUDE_COLLECTIONS( PessimisticLockScope.EXTENDED ),
+
+ /**
+ * All tables with fetched rows will be locked.
+ *
+ * @apiNote This is Hibernate's legacy behavior, and has no
+ * corresponding JPA scope.
+ */
+ INCLUDE_FETCHES( null );
+
+ private final PessimisticLockScope jpaScope;
+
+ Scope(PessimisticLockScope jpaScope) {
+ this.jpaScope = jpaScope;
+ }
+
+ /**
+ * The JPA PessimisticLockScope which corresponds to this LockScope.
+ *
+ * @return The corresponding PessimisticLockScope, or {@code null}.
+ */
+ public PessimisticLockScope getCorrespondingJpaScope() {
+ return jpaScope;
+ }
+
+ public static Scope fromJpaScope(PessimisticLockScope scope) {
+ if ( scope == PessimisticLockScope.EXTENDED ) {
+ return INCLUDE_COLLECTIONS;
+ }
+ // null, NORMAL
+ return ROOT_ONLY;
+ }
+ }
+
+ /**
+ * In certain circumstances, Hibernate may need to acquire locks
+ * through the use of subsequent queries. For example, some
+ * databases may not like attempting to lock rows in a join or
+ * queries with paging. In such cases, Hibernate will fall back
+ * to issuing additional lock queries to lock some of the rows.
+ * This option controls whether Hibernate is allowed to use
+ * this approach.
+ */
+ enum FollowOn implements FindOption, LockOption, RefreshOption {
+ /**
+ * Allow Hibernate to perform follow-on locking when it needs to.
+ */
+ ALLOW,
+
+ /**
+ * Do not allow Hibernate to perform follow-on locking when it needs to, throwing
+ * an exception instead.
+ */
+ DISALLOW,
+
+ /**
+ * Do not allow Hibernate to perform follow-on locking when it needs to, but just ignore
+ * the situation.
+ *
+ * @apiNote This can lead to rows not being locked when they are expected to be.
+ */
+ IGNORE
+ }
+
+ /**
+ * How locked rows should be handled with pessimistic lock attempts.
+ * The default is to {@linkplain #WAIT wait} for the locks to be released.
+ */
+ enum LockedRows implements FindOption, LockOption, RefreshOption {
+ /**
+ * The default. The transaction will wait for the row locks to
+ * be released, within any specified {@linkplain Timeout timeout}.
+ */
+ WAIT,
+
+ /**
+ * Immediately skips locked rows.
+ *
+ * @apiNote Only legal if the database
+ * {@linkplain Dialect#supportsSkipLocked() supports skipping locked rows}.
+ */
+ SKIP
+ }
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java
index d140524d8bd0..ace166339423 100644
--- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java
+++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java
@@ -22,11 +22,13 @@
import java.util.function.Function;
import java.util.function.Supplier;
+import jakarta.persistence.Timeout;
import org.hibernate.AssertionFailure;
import org.hibernate.Internal;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.QueryException;
+import org.hibernate.Timeouts;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.RowLockStrategy;
@@ -1756,7 +1758,7 @@ else if ( forUpdate != null ) {
}
protected void renderForUpdateClause(QuerySpec querySpec, ForUpdateClause forUpdateClause) {
- int timeoutMillis = forUpdateClause.getTimeoutMillis();
+ Timeout timeout = forUpdateClause.getTimeout();
LockKind lockKind = LockKind.NONE;
switch ( forUpdateClause.getLockMode() ) {
case PESSIMISTIC_WRITE:
@@ -1767,11 +1769,11 @@ protected void renderForUpdateClause(QuerySpec querySpec, ForUpdateClause forUpd
break;
case UPGRADE_NOWAIT:
case PESSIMISTIC_FORCE_INCREMENT:
- timeoutMillis = LockOptions.NO_WAIT;
+ timeout = Timeouts.NO_WAIT;
lockKind = LockKind.UPDATE;
break;
case UPGRADE_SKIPLOCKED:
- timeoutMillis = LockOptions.SKIP_LOCKED;
+ timeout = Timeouts.SKIP_LOCKED;
lockKind = LockKind.UPDATE;
break;
default:
@@ -1779,7 +1781,7 @@ protected void renderForUpdateClause(QuerySpec querySpec, ForUpdateClause forUpd
}
if ( lockKind != LockKind.NONE ) {
if ( lockKind == LockKind.SHARE ) {
- appendSql( getForShare( timeoutMillis ) );
+ appendSql( getForShare( timeout.milliseconds() ) );
if ( forUpdateClause.hasAliases() && dialect.getReadRowLockStrategy() != RowLockStrategy.NONE ) {
appendSql( " of " );
forUpdateClause.appendAliases( this );
@@ -1793,23 +1795,23 @@ protected void renderForUpdateClause(QuerySpec querySpec, ForUpdateClause forUpd
}
}
appendSql( getForUpdateWithClause() );
- switch ( timeoutMillis ) {
- case LockOptions.NO_WAIT:
+ switch ( timeout.milliseconds() ) {
+ case Timeouts.NO_WAIT_MILLI:
if ( dialect.supportsNoWait() ) {
appendSql( getNoWait() );
}
break;
- case LockOptions.SKIP_LOCKED:
+ case Timeouts.SKIP_LOCKED_MILLI:
if ( dialect.supportsSkipLocked() ) {
appendSql( getSkipLocked() );
}
break;
- case LockOptions.WAIT_FOREVER:
+ case Timeouts.WAIT_FOREVER_MILLI:
break;
default:
if ( dialect.supportsWait() ) {
appendSql( " wait " );
- appendSql( Math.round( timeoutMillis / 1e3f ) );
+ appendSql( Timeouts.getTimeoutInSeconds( timeout ) );
}
break;
}
@@ -1843,24 +1845,28 @@ protected String getSkipLocked() {
return " skip locked";
}
- protected LockMode getEffectiveLockMode(String alias) {
- final QueryPart currentQueryPart = getQueryPartStack().getCurrent();
- return currentQueryPart == null
- ? LockMode.NONE
- : getEffectiveLockMode( alias, currentQueryPart.isRoot() );
- }
-
- protected LockMode getEffectiveLockMode(String alias, boolean isRoot) {
+ protected LockMode getEffectiveLockMode() {
if ( getLockOptions() == null ) {
return LockMode.NONE;
}
- LockMode lockMode = getLockOptions().getAliasSpecificLockMode( alias );
- if ( isRoot && lockMode == null ) {
- lockMode = getLockOptions().getLockMode();
+
+ final QueryPart currentQueryPart = getQueryPartStack().getCurrent();
+ if ( currentQueryPart == null ) {
+ return LockMode.NONE;
}
+
+ final LockMode lockMode = getLockOptions().getLockMode();
return lockMode == null ? LockMode.NONE : lockMode;
}
+ protected LockMode getEffectiveLockMode(String alias) {
+ return getEffectiveLockMode();
+ }
+
+ protected LockMode getEffectiveLockMode(String alias, boolean isRoot) {
+ return getEffectiveLockMode();
+ }
+
protected int getEffectiveLockTimeout(LockMode lockMode) {
return getLockOptions() == null
? LockOptions.WAIT_FOREVER
@@ -5878,7 +5884,9 @@ protected void renderRootTableGroup(TableGroup tableGroup, List