HHH-15682 fix potential classloading deadlock
+ add some documentation around follow-on locking
This commit is contained in:
parent
85836fbcf8
commit
39bef7bc70
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue