HHH-15682 fix potential classloading deadlock

+ add some documentation around follow-on locking
This commit is contained in:
Gavin King 2022-11-08 15:16:29 +01:00
parent 85836fbcf8
commit 39bef7bc70
2 changed files with 85 additions and 69 deletions

View File

@ -12,12 +12,14 @@ import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import jakarta.persistence.PessimisticLockScope; import jakarta.persistence.PessimisticLockScope;
import org.hibernate.query.Query; import org.hibernate.query.Query;
import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptions;
import static java.util.Collections.emptyList; import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;
/** /**
* Contains a set of options describing how a row of a database table * Contains a set of options describing how a row of a database table
@ -40,6 +42,11 @@ import static java.util.Collections.emptyList;
* more granular fashion, with the option to specify a lock mode that * more granular fashion, with the option to specify a lock mode that
* {@linkplain #setAliasSpecificLockMode(String, LockMode) applies * {@linkplain #setAliasSpecificLockMode(String, LockMode) applies
* only to a certain query alias}. * only to a certain query alias}.
* <p>
* Finally, the use of follow-on locking may be force enabled or disabled,
* overriding the {@linkplain org.hibernate.dialect.Dialect#useFollowOnLocking
* default behavior of the SQL dialect} by passing a non-null argument
* to {@link #setFollowOnLocking(Boolean)}.
* *
* @author Scott Marlow * @author Scott Marlow
*/ */
@ -48,13 +55,13 @@ public class LockOptions implements Serializable {
* Represents {@link LockMode#NONE}, to which timeout and scope are * Represents {@link LockMode#NONE}, to which timeout and scope are
* not applicable. * not applicable.
*/ */
public static final LockOptions NONE = new ImmutableLockOptions(LockMode.NONE); public static final LockOptions NONE = new LockOptions(true, LockMode.NONE);
/** /**
* Represents {@link LockMode#READ}, to which timeout and scope are * Represents {@link LockMode#READ}, to which timeout and scope are
* not applicable. * not applicable.
*/ */
public static final LockOptions READ = new ImmutableLockOptions(LockMode.READ); public static final LockOptions READ = new LockOptions(true, LockMode.READ);
/** /**
* Represents {@link LockMode#PESSIMISTIC_WRITE} with * Represents {@link LockMode#PESSIMISTIC_WRITE} with
@ -62,7 +69,7 @@ public class LockOptions implements Serializable {
* {@linkplain PessimisticLockScope#NORMAL no extension of the * {@linkplain PessimisticLockScope#NORMAL no extension of the
* lock to owned collections}. * lock to owned collections}.
*/ */
public static final LockOptions UPGRADE = new ImmutableLockOptions(LockMode.PESSIMISTIC_WRITE); public static final LockOptions UPGRADE = new LockOptions(true, LockMode.PESSIMISTIC_WRITE);
/** /**
* Indicates that the database should not wait at all to acquire * Indicates that the database should not wait at all to acquire
@ -86,44 +93,49 @@ public class LockOptions implements Serializable {
* Indicates that rows which are already locked should be skipped. * Indicates that rows which are already locked should be skipped.
* *
* @see #getTimeOut() * @see #getTimeOut()
*
* @deprecated use {@link LockMode#UPGRADE_SKIPLOCKED} * @deprecated use {@link LockMode#UPGRADE_SKIPLOCKED}
*/ */
@Deprecated @Deprecated
public static final int SKIP_LOCKED = -2; public static final int SKIP_LOCKED = -2;
private LockMode lockMode = LockMode.NONE; private final boolean immutable;
private int timeout = WAIT_FOREVER; private LockMode lockMode;
private int timeout;
private boolean scope; private boolean scope;
private Map<String,LockMode> aliasSpecificLockModes;
private Boolean followOnLocking; private Boolean followOnLocking;
private Map<String, LockMode> aliasSpecificLockModes;
/** /**
* Construct an instance with mode {@link LockMode#NONE} and * Construct an instance with mode {@link LockMode#NONE} and
* timeout {@link #WAIT_FOREVER}. * timeout {@link #WAIT_FOREVER}.
*/ */
public LockOptions() { public LockOptions() {
immutable = false;
lockMode = LockMode.NONE;
timeout = WAIT_FOREVER;
} }
/** /**
* Construct an instance with the given {@linkplain LockMode mode} * Construct an instance with the given {@linkplain LockMode mode}
* and {@link #WAIT_FOREVER}. * and {@link #WAIT_FOREVER}.
* *
* @param lockMode The lock mode to use * @param lockMode The initial lock mode
*/ */
public LockOptions(LockMode lockMode) { public LockOptions(LockMode lockMode) {
immutable = false;
this.lockMode = lockMode; this.lockMode = lockMode;
timeout = WAIT_FOREVER;
} }
/** /**
* Construct an instance with the given {@linkplain LockMode mode} * Construct an instance with the given {@linkplain LockMode mode}
* and timeout. * and timeout.
* *
* @param lockMode The lock mode to use * @param lockMode The initial lock mode
* @param timeout The initial timeout
*/ */
public LockOptions(LockMode lockMode, int timeout) { public LockOptions(LockMode lockMode, int timeout) {
immutable = false;
this.lockMode = lockMode; this.lockMode = lockMode;
this.timeout = timeout; this.timeout = timeout;
} }
@ -132,14 +144,25 @@ public class LockOptions implements Serializable {
* Construct an instance with the given {@linkplain LockMode mode}, * Construct an instance with the given {@linkplain LockMode mode},
* timeout, and {@link PessimisticLockScope scope}. * timeout, and {@link PessimisticLockScope scope}.
* *
* @param lockMode The lock mode to use * @param lockMode The initial lock mode
* @param timeout The initial timeout
* @param scope The initial lock scope
*/ */
public LockOptions(LockMode lockMode, int timeout, PessimisticLockScope scope) { public LockOptions(LockMode lockMode, int timeout, PessimisticLockScope scope) {
immutable = false;
this.lockMode = lockMode; this.lockMode = lockMode;
this.timeout = timeout; this.timeout = timeout;
this.scope = scope == PessimisticLockScope.EXTENDED; this.scope = scope == PessimisticLockScope.EXTENDED;
} }
/**
* Internal operation used to create immutable global instances.
*/
private LockOptions(boolean immutable, LockMode lockMode) {
this.immutable = immutable;
this.lockMode = lockMode;
timeout = WAIT_FOREVER;
}
/** /**
* Determine of the lock options are empty. * Determine of the lock options are empty.
* *
@ -171,6 +194,9 @@ public class LockOptions implements Serializable {
* @return {@code this} for method chaining * @return {@code this} for method chaining
*/ */
public LockOptions setLockMode(LockMode lockMode) { public LockOptions setLockMode(LockMode lockMode) {
if ( immutable ) {
throw new UnsupportedOperationException("immutable global instance of LockMode");
}
this.lockMode = lockMode; this.lockMode = lockMode;
return this; return this;
} }
@ -185,6 +211,9 @@ public class LockOptions implements Serializable {
* @see Query#setLockMode(String, LockMode) * @see Query#setLockMode(String, LockMode)
*/ */
public LockOptions setAliasSpecificLockMode(String alias, LockMode lockMode) { public LockOptions setAliasSpecificLockMode(String alias, LockMode lockMode) {
if ( immutable ) {
throw new UnsupportedOperationException("immutable global instance of LockMode");
}
if ( aliasSpecificLockModes == null ) { if ( aliasSpecificLockModes == null ) {
aliasSpecificLockModes = new LinkedHashMap<>(); aliasSpecificLockModes = new LinkedHashMap<>();
} }
@ -240,8 +269,7 @@ public class LockOptions implements Serializable {
* {@code false} otherwise. * {@code false} otherwise.
*/ */
public boolean hasAliasSpecificLockModes() { public boolean hasAliasSpecificLockModes() {
return aliasSpecificLockModes != null return aliasSpecificLockModes != null && !aliasSpecificLockModes.isEmpty();
&& ! aliasSpecificLockModes.isEmpty();
} }
/** /**
@ -266,13 +294,14 @@ public class LockOptions implements Serializable {
} }
/** /**
* Iterable with {@link Map.Entry}s, each containing an alias and its * Set of {@link Map.Entry}s, each associating an alias with its
* specified {@linkplain #setAliasSpecificLockMode alias-specific}
* {@link LockMode}. * {@link LockMode}.
* *
* @return an iterable with the {@link Map.Entry}s * @return an iterable with the {@link Map.Entry}s
*/ */
public Iterable<Map.Entry<String,LockMode>> getAliasSpecificLocks() { public Set<Map.Entry<String,LockMode>> getAliasSpecificLocks() {
return aliasSpecificLockModes == null ? emptyList() : aliasSpecificLockModes.entrySet(); return aliasSpecificLockModes == null ? emptySet() : unmodifiableSet( aliasSpecificLockModes.entrySet() );
} }
/** /**
@ -328,6 +357,9 @@ public class LockOptions implements Serializable {
* @see #getTimeOut * @see #getTimeOut
*/ */
public LockOptions setTimeOut(int timeout) { public LockOptions setTimeOut(int timeout) {
if ( immutable ) {
throw new UnsupportedOperationException("immutable global instance of LockMode");
}
this.timeout = timeout; this.timeout = timeout;
return this; return this;
} }
@ -360,6 +392,9 @@ public class LockOptions implements Serializable {
* @return {@code this} for method chaining * @return {@code this} for method chaining
*/ */
public LockOptions setLockScope(PessimisticLockScope scope) { public LockOptions setLockScope(PessimisticLockScope scope) {
if ( immutable ) {
throw new UnsupportedOperationException("immutable global instance of LockMode");
}
return setScope(scope==PessimisticLockScope.EXTENDED); return setScope(scope==PessimisticLockScope.EXTENDED);
} }
@ -397,15 +432,24 @@ public class LockOptions implements Serializable {
*/ */
@Deprecated(since = "6.2") @Deprecated(since = "6.2")
public LockOptions setScope(boolean scope) { public LockOptions setScope(boolean scope) {
if ( immutable ) {
throw new UnsupportedOperationException("immutable global instance of LockMode");
}
this.scope = scope; this.scope = scope;
return this; return this;
} }
/** /**
* The current follow-on locking setting. * 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 is enabled * @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, QueryOptions) * @see org.hibernate.dialect.Dialect#useFollowOnLocking(String, QueryOptions)
*/ */
public Boolean getFollowOnLocking() { public Boolean getFollowOnLocking() {
@ -413,14 +457,19 @@ public class LockOptions implements Serializable {
} }
/** /**
* Set the follow-on locking setting. * 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 * @param followOnLocking The new follow-on locking setting
* @return {@code this} for method chaining * @return {@code this} for method chaining
* *
* @see org.hibernate.jpa.HibernateHints#HINT_FOLLOW_ON_LOCKING
* @see org.hibernate.dialect.Dialect#useFollowOnLocking(String, QueryOptions) * @see org.hibernate.dialect.Dialect#useFollowOnLocking(String, QueryOptions)
*/ */
public LockOptions setFollowOnLocking(Boolean followOnLocking) { public LockOptions setFollowOnLocking(Boolean followOnLocking) {
if ( immutable ) {
throw new UnsupportedOperationException("immutable global instance of LockMode");
}
this.followOnLocking = followOnLocking; this.followOnLocking = followOnLocking;
return this; return this;
} }
@ -492,40 +541,4 @@ public class LockOptions implements Serializable {
public int hashCode() { public int hashCode() {
return Objects.hash( lockMode, timeout, aliasSpecificLockModes, followOnLocking, scope ); return Objects.hash( lockMode, timeout, aliasSpecificLockModes, followOnLocking, scope );
} }
private static class ImmutableLockOptions extends LockOptions {
private ImmutableLockOptions(LockMode lockMode) {
super(lockMode);
}
@Override
public LockOptions setLockMode(LockMode lockMode) {
throw new UnsupportedOperationException();
}
@Override
public LockOptions setLockScope(PessimisticLockScope scope) {
throw new UnsupportedOperationException();
}
@Override
public LockOptions setFollowOnLocking(Boolean followOnLocking) {
return super.setFollowOnLocking(followOnLocking);
}
@Override
public LockOptions setTimeOut(int timeout) {
throw new UnsupportedOperationException();
}
@Override
public LockOptions setAliasSpecificLockMode(String alias, LockMode lockMode) {
throw new UnsupportedOperationException();
}
@Override
public LockOptions setScope(boolean scope) {
throw new UnsupportedOperationException();
}
}
} }

View File

@ -12,7 +12,7 @@ import org.hibernate.query.Query;
/** /**
* List of Hibernate-specific (extension) hints available to query, * List of Hibernate-specific (extension) hints available to query,
* load and lock scenarios. * load and lock scenarios.
* * <p>
* Some hints are only effective in certain scenarios, which is noted on * Some hints are only effective in certain scenarios, which is noted on
* each constant's documentation * each constant's documentation
* *
@ -96,36 +96,39 @@ public interface HibernateHints {
/** /**
* Hint to enable/disable the follow-on-locking mechanism provided by * Hint to enable/disable the follow-on-locking mechanism provided by
* {@link org.hibernate.dialect.Dialect#useFollowOnLocking(String, org.hibernate.query.spi.QueryOptions)}. * {@link org.hibernate.dialect.Dialect#useFollowOnLocking}.
* <p>
* A value of {@code true} enables follow-on-locking, whereas a value of * A value of {@code true} enables follow-on-locking, whereas a value of
* {@code false} disables it. If the value is {@code null}, the * {@code false} disables it. If the value is {@code null}, the
* {@code Dialect}'s default strategy is used. * {@code Dialect}'s default strategy is used.
* *
* @see org.hibernate.LockOptions#setFollowOnLocking(Boolean)
*
* @since 5.2 * @since 5.2
*/ */
String HINT_FOLLOW_ON_LOCKING = "hibernate.query.followOnLocking"; String HINT_FOLLOW_ON_LOCKING = "hibernate.query.followOnLocking";
/** /**
* Hint for specifying the lock-mode to apply to the results from a * Hint for specifying the lock mode to apply to the results of a
* native-query. * native query.
* * <p>
* While Hibernate supports applying lock-mode to a native-query, the specification * While Hibernate supports applying lock-mode to a native-query, the
* requires that {@link jakarta.persistence.Query#setLockMode} throw an * JPA specification requires that {@link jakarta.persistence.Query#setLockMode}
* {@link IllegalStateException} if called for a native query. * throw an {@link IllegalStateException} if called for a native query.
* * <p>
* Accepts a {@link jakarta.persistence.LockModeType} or a {@link org.hibernate.LockMode} * Accepts a {@link jakarta.persistence.LockModeType} or a {@link org.hibernate.LockMode}
*/ */
String HINT_NATIVE_LOCK_MODE = "org.hibernate.lockMode"; String HINT_NATIVE_LOCK_MODE = "org.hibernate.lockMode";
/** /**
* Hint for specifying query spaces to be applied to a NativeQuery. * Hint for specifying query spaces to be applied to a native query.
* * <p>
* Passed value can be any of:<ul> * Passed value can be any of:<ul>
* <li>List of the spaces</li> * <li>List of the spaces</li>
* <li>array of the spaces</li> * <li>array of the spaces</li>
* <li>String as "whitespace"-separated list of the spaces</li> * <li>String as "whitespace"-separated list of the spaces</li>
* </ul> * </ul>
* * <p>
* Note that the passed space need not match any real spaces/tables in * Note that the passed space need not match any real spaces/tables in
* the underlying query. This can be used to completely circumvent * the underlying query. This can be used to completely circumvent
* the auto-flush checks as well as any cache invalidation that might * the auto-flush checks as well as any cache invalidation that might