improve javadoc for LockModes, and deprecate LockOptions.SKIP_LOCKED

Use LockMode.UPGRADE_SKIPLOCKED instead of setting the timeout to -2.
This commit is contained in:
Gavin King 2022-11-07 13:34:58 +01:00
parent 02ad34091c
commit 9d141a2793
7 changed files with 126 additions and 55 deletions

View File

@ -12,87 +12,136 @@
/** /**
* Instances represent a lock mode for a row of a relational * Instances represent a lock mode for a row of a relational
* database table. It is not intended that users spend much * database table. It is not intended that users spend much time
* time worrying about locking since Hibernate usually * worrying about locking since Hibernate usually obtains exactly
* obtains exactly the right lock level automatically. * the right lock level automatically. Some "advanced" users may
* Some "advanced" users may wish to explicitly specify lock * wish to explicitly specify lock levels.
* levels. * <p>
* This enumeration of lock modes competes with the JPA-defined
* {@link LockModeType}, but offers additional options, including
* {@link #UPGRADE_NOWAIT} and {@link #UPGRADE_SKIPLOCKED}.
* *
* @author Gavin King * @author Gavin King
* *
* @see Session#lock(Object, LockMode) * @see Session#lock(Object, LockMode)
* @see LockModeType
* @see LockOptions
*/ */
public enum LockMode { public enum LockMode {
/** /**
* No lock required. If an object is requested with this * No lock required. If an object is requested with this lock
* lock mode, a {@link #READ} lock will be obtained if it * mode, a {@link #READ} lock will be obtained if it turns out
* is necessary to actually read the state from the database, * to be necessary to actually read the state from the database,
* rather than pull it from a cache. * rather than pull it from a cache.
* <p> * <p>
* This is the "default" lock mode. * This is the "default" lock mode, the mode requested by calling
* {@link Session#get(Class, Object)} without passing an explicit
* mode. It permits the state of an object to be retrieved from
* the cache without the cost of database access.
*
* @see LockModeType#NONE
*/ */
NONE( 0, "none" ), NONE( 0, "none" ),
/** /**
* A shared lock. Objects in this lock mode were read from * A shared lock. Objects in this lock mode were read from the
* the database in the current transaction, rather than being * database in the current transaction, rather than being pulled
* pulled from a cache. * from a cache.
* <p>
* Note that this lock mode is the same as the JPA-defined modes
* {@link LockModeType#READ} and {@link LockModeType#OPTIMISTIC}.
*
* @see LockModeType#OPTIMISTIC
*/ */
READ( 5, "read" ), READ( 5, "read" ),
/** /**
* Optimistically assume that transaction will not experience * A shared optimistic lock. Assumes that the current transaction
* contention for an entity. The version will be verified near * will not experience contention for the state of an entity. The
* the transaction end. * version will be checked near the end of the transaction, to
* verify that this was indeed the case.
* <p>
* Note that, despite the similar names this lock mode is not the
* same as the JPA-defined mode {@link LockModeType#OPTIMISTIC}.
*/ */
OPTIMISTIC( 6, "optimistic" ), OPTIMISTIC( 6, "optimistic" ),
/** /**
* Optimistically assume that transaction will not experience * A kind of exclusive optimistic lock. Assumes that the current
* contention for an entity. The version will be verified and * transaction will not experience contention for the state of an
* incremented near the transaction end. * entity. The version will be checked and incremented near the
* end of the transaction, to verify that this was indeed the
* case, and to signal to concurrent optimistic readers that their
* optimistic locks have failed.
*
* @see LockModeType#OPTIMISTIC_FORCE_INCREMENT
*/ */
OPTIMISTIC_FORCE_INCREMENT( 7, "optimistic_force_increment" ), OPTIMISTIC_FORCE_INCREMENT( 7, "optimistic_force_increment" ),
/** /**
* A {@code WRITE} lock is obtained when an object is updated * An exclusive write lock. Objects in this lock mode were updated
* or inserted. * or inserted in the database in the current transaction.
* <p> * <p>
* This lock mode is for internal use only and is not a valid * This lock mode is for internal use only and is not a legal
* argument to {@code load()} or {@code lock()}. These method * argument to {@link Session#get(Class, Object, LockMode)},
* throw an exception if {@code WRITE} given as an argument. * {@link Session#refresh(Object, LockMode)}, or
* {@link Session#lock(Object, LockMode)}. These methods throw
* an exception if {@code WRITE} is given as an argument.
* <p>
* Note that, despite the similar names, this lock mode is not
* the same as the JPA-defined mode {@link LockModeType#WRITE}.
*/ */
@Internal @Internal
WRITE( 10, "write" ), WRITE( 10, "write" ),
/** /**
* Attempt to obtain an upgrade lock, using an Oracle-style * A pessimistic upgrade lock, obtained using an Oracle-style
* {@code select for update nowait}. The semantics of this * {@code select for update nowait}. The semantics of this
* lock mode, once obtained, are the same as * lock mode, if the lock is successfully obtained, are the same
* {@link #PESSIMISTIC_WRITE}. * as {@link #PESSIMISTIC_WRITE}. If the lock is not immediately
* available, an exception occurs.
*/ */
UPGRADE_NOWAIT( 10, "upgrade-nowait" ), UPGRADE_NOWAIT( 10, "upgrade-nowait" ),
/** /**
* Attempt to obtain an upgrade lock, using an Oracle-style * A pessimistic upgrade lock, obtained using an Oracle-style
* {@code select for update skip locked}. The semantics of * {@code select for update skip locked}. The semantics of this
* this lock mode, once obtained, are the same as * lock mode, if the lock is successfully obtained, are the same
* {@link #PESSIMISTIC_WRITE}. * 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.
*/ */
UPGRADE_SKIPLOCKED( 10, "upgrade-skiplocked" ), UPGRADE_SKIPLOCKED( 10, "upgrade-skiplocked" ),
/** /**
* Implemented as {@link #PESSIMISTIC_WRITE}. * A pessimistic shared lock, which prevents concurrent
* transactions from writing the locked object. Obtained via
* a {@code select for share} statement in dialects where this
* syntax is supported, and via {@code select for update} in
* other dialects.
* <p>
* On databases which do not support {@code for share}, this
* lock mode is equivalent to {@link #PESSIMISTIC_WRITE}.
*
* @see LockModeType#PESSIMISTIC_READ
*/ */
PESSIMISTIC_READ( 12, "pessimistic_read" ), PESSIMISTIC_READ( 12, "pessimistic_read" ),
/** /**
* Transaction will obtain a database lock immediately. * A pessimistic upgrade lock, which prevents concurrent
* transactions from reading or writing the locked object.
* Obtained via a {@code select for update} statement.
*
* @see LockModeType#PESSIMISTIC_WRITE
*/ */
PESSIMISTIC_WRITE( 13, "pessimistic_write" ), PESSIMISTIC_WRITE( 13, "pessimistic_write" ),
/** /**
* Transaction will immediately increment the entity version. * A pessimistic write lock which immediately increments
* the version of the locked object. Obtained by immediate
* execution of an {@code update} statement.
*
* @see LockModeType#PESSIMISTIC_FORCE_INCREMENT
*/ */
PESSIMISTIC_FORCE_INCREMENT( 17, "pessimistic_force_increment" ); PESSIMISTIC_FORCE_INCREMENT( 17, "pessimistic_force_increment" );

View File

@ -66,7 +66,8 @@ public class LockOptions implements Serializable {
/** /**
* Indicates that the database should not wait at all to acquire * Indicates that the database should not wait at all to acquire
* a pessimistic lock which is not immediately available. * a pessimistic lock which is not immediately available. This
* has the same effect as {@link LockMode#UPGRADE_NOWAIT}.
* *
* @see #getTimeOut * @see #getTimeOut
*/ */
@ -85,7 +86,10 @@ 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
public static final int SKIP_LOCKED = -2; public static final int SKIP_LOCKED = -2;
private LockMode lockMode = LockMode.NONE; private LockMode lockMode = LockMode.NONE;

View File

@ -742,6 +742,9 @@ public interface Session extends SharedSessionContract, EntityManager {
* </ul> * </ul>
* This operation cascades to associated instances if the association is mapped * This operation cascades to associated instances if the association is mapped
* with {@link jakarta.persistence.CascadeType#REFRESH}. * with {@link jakarta.persistence.CascadeType#REFRESH}.
* <p>
* This operation requests {@link LockMode#READ}. To obtain a stronger lock,
* call {@link #refresh(Object, LockMode)}.
* *
* @param object a persistent or detached instance * @param object a persistent or detached instance
*/ */
@ -844,9 +847,24 @@ public interface Session extends SharedSessionContract, EntityManager {
/** /**
* Return the persistent instance of the given entity class with the given identifier, * Return the persistent instance of the given entity class with the given identifier,
* or null if there is no such persistent instance. If the instance is already associated * or null if there is no such persistent instance. If the instance is already associated
* with the session, return that instance. This method never returns an uninitialized instance. * with the session, return that instance. This method never returns an uninitialized
* instance.
* <p> * <p>
* This operation is very similar to {@link #find(Class, Object)}. * This operation is very similar to {@link #find(Class, Object)}.
* <p>
* This operation requests {@link LockMode#NONE}, that is, no lock, allowing the object
* to be retrieved from the cache without the cost of database access. However, if it is
* necessary to read the state from the database, the object will be returned with the
* lock mode {@link LockMode#READ}.
* <p>
* To bypass the second-level cache, and ensure that the state is read from the database,
* either:
* <ul>
* <li>call {@link #get(Class, Object, LockMode)} with the explicit lock mode
* {@link LockMode#READ}, or
* <li>{@linkplain #setCacheMode(CacheMode) set the cache mode} to {@link CacheMode#IGNORE}
* before calling this method.
* </ul>
* *
* @param entityType the entity type * @param entityType the entity type
* @param id an identifier * @param id an identifier
@ -858,8 +876,8 @@ public interface Session extends SharedSessionContract, EntityManager {
/** /**
* Return the persistent instance of the given entity class with the given identifier, * Return the persistent instance of the given entity class with the given identifier,
* or null if there is no such persistent instance. If the instance is already associated * or null if there is no such persistent instance. If the instance is already associated
* with the session, return that instance. This method never returns an uninitialized instance. * with the session, return that instance. This method never returns an uninitialized
* Obtain the specified lock mode if the instance exists. * instance. Obtain the specified lock mode if the instance exists.
* <p> * <p>
* Convenient form of {@link #get(Class, Object, LockOptions)}. * Convenient form of {@link #get(Class, Object, LockOptions)}.
* <p> * <p>
@ -878,8 +896,8 @@ public interface Session extends SharedSessionContract, EntityManager {
/** /**
* Return the persistent instance of the given entity class with the given identifier, * Return the persistent instance of the given entity class with the given identifier,
* or null if there is no such persistent instance. If the instance is already associated * or null if there is no such persistent instance. If the instance is already associated
* with the session, return that instance. This method never returns an uninitialized instance. * with the session, return that instance. This method never returns an uninitialized
* Obtain the specified lock mode if the instance exists. * instance. Obtain the specified lock mode if the instance exists.
* *
* @param entityType the entity type * @param entityType the entity type
* @param id an identifier * @param id an identifier
@ -892,7 +910,8 @@ public interface Session extends SharedSessionContract, EntityManager {
/** /**
* Return the persistent instance of the given named entity with the given identifier, * Return the persistent instance of the given named entity with the given identifier,
* or null if there is no such persistent instance. If the instance is already associated * or null if there is no such persistent instance. If the instance is already associated
* with the session, return that instance. This method never returns an uninitialized instance. * with the session, return that instance. This method never returns an uninitialized
* instance.
* *
* @param entityName the entity name * @param entityName the entity name
* @param id an identifier * @param id an identifier
@ -903,9 +922,9 @@ public interface Session extends SharedSessionContract, EntityManager {
/** /**
* Return the persistent instance of the given entity class with the given identifier, * Return the persistent instance of the given entity class with the given identifier,
* or null if there is no such persistent instance. (If the instance is already associated * or null if there is no such persistent instance. If the instance is already associated
* with the session, return that instance. This method never returns an uninitialized instance.) * with the session, return that instance. This method never returns an uninitialized
* Obtain the specified lock mode if the instance exists. * instance. Obtain the specified lock mode if the instance exists.
* <p/> * <p/>
* Convenient form of {@link #get(String, Object, LockOptions)} * Convenient form of {@link #get(String, Object, LockOptions)}
* *
@ -922,8 +941,8 @@ public interface Session extends SharedSessionContract, EntityManager {
/** /**
* Return the persistent instance of the given entity class with the given identifier, * Return the persistent instance of the given entity class with the given identifier,
* or null if there is no such persistent instance. If the instance is already associated * or null if there is no such persistent instance. If the instance is already associated
* with the session, return that instance. This method never returns an uninitialized instance. * with the session, return that instance. This method never returns an uninitialized
* Obtain the specified lock mode if the instance exists. * instance. Obtain the specified lock mode if the instance exists.
* *
* @param entityName the entity name * @param entityName the entity name
* @param id an identifier * @param id an identifier
@ -1235,7 +1254,8 @@ interface LockRequest {
/** /**
* A timeout value indicating that the database should not * A timeout value indicating that the database should not
* wait at all to acquire a pessimistic lock which is not * wait at all to acquire a pessimistic lock which is not
* immediately available. * immediately available. This has the same effect as
* {@link LockMode#UPGRADE_NOWAIT}.
*/ */
int PESSIMISTIC_NO_WAIT = LockOptions.NO_WAIT; int PESSIMISTIC_NO_WAIT = LockOptions.NO_WAIT;

View File

@ -1874,10 +1874,10 @@ public String getForUpdateString(String aliases) {
} }
/** /**
* Get the {@code FOR UPDATE OF column_list} fragment appropriate for this * Get the {@code FOR UPDATE OF} or {@code FOR SHARE OF} fragment appropriate
* dialect given the aliases of the columns to be write locked. * for this dialect given the aliases of the columns to be locked.
* *
* @param aliases The columns to be write locked. * @param aliases The columns to be locked.
* @param lockOptions the lock options to apply * @param lockOptions the lock options to apply
* @return The appropriate {@code FOR UPDATE OF column_list} clause string. * @return The appropriate {@code FOR UPDATE OF column_list} clause string.
*/ */

View File

@ -691,9 +691,7 @@ public String getForUpdateString(String aliases) {
@Override @Override
public String getForUpdateString(String aliases, LockOptions lockOptions) { public String getForUpdateString(String aliases, LockOptions lockOptions) {
/* // parent's implementation for (aliases, lockOptions) ignores aliases
* Parent's implementation for (aliases, lockOptions) ignores aliases.
*/
if ( aliases.isEmpty() ) { if ( aliases.isEmpty() ) {
LockMode lockMode = lockOptions.getLockMode(); LockMode lockMode = lockOptions.getLockMode();
for ( Map.Entry<String, LockMode> entry : lockOptions.getAliasSpecificLocks() ) { for ( Map.Entry<String, LockMode> entry : lockOptions.getAliasSpecificLocks() ) {

View File

@ -19,7 +19,7 @@ public class RefreshEvent extends AbstractEvent {
private final Object object; private final Object object;
private String entityName; private String entityName;
private LockOptions lockOptions = new LockOptions().setLockMode(LockMode.READ); private LockOptions lockOptions = new LockOptions(LockMode.READ);
public RefreshEvent(Object object, EventSource source) { public RefreshEvent(Object object, EventSource source) {
super(source); super(source);

View File

@ -43,7 +43,7 @@ public SimpleSelect(Dialect dialect) {
protected LockOptions lockOptions = new LockOptions( LockMode.READ ); protected LockOptions lockOptions = new LockOptions( LockMode.READ );
private Dialect dialect; private final Dialect dialect;
public SimpleSelect addColumns(String[] columnNames, String[] columnAliases) { public SimpleSelect addColumns(String[] columnNames, String[] columnAliases) {
@ -189,7 +189,7 @@ public String toStatementString() {
} }
if ( lockOptions != null ) { if ( lockOptions != null ) {
buf = new StringBuilder(dialect.applyLocksToSql( buf.toString(), lockOptions, null ) ); buf = new StringBuilder( dialect.applyLocksToSql( buf.toString(), lockOptions, null ) );
} }
return dialect.transformSelectString( buf.toString() ); return dialect.transformSelectString( buf.toString() );