diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index 67ed6b5a73..9251528887 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -808,14 +808,28 @@ public interface Session extends SharedSessionContract, EntityManager { /** * Obtain the specified lock level on the given managed instance associated - * with this session. This may be used to: + * with this session. This operation may be used to: * *

+ * If the requested lock mode is already held on the given entity, this + * operation has no effect. + *

* This operation cascades to associated instances if the association is * mapped with {@link org.hibernate.annotations.CascadeType#LOCK}. + *

+ * The modes {@link LockMode#WRITE} and {@link LockMode#UPGRADE_SKIPLOCKED} + * are not legal arguments to {@code lock()}. * * @param object a persistent instance * @param lockMode the lock level diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java index 84eeb45769..3cc50ab6d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityVerifyVersionProcess.java @@ -34,7 +34,7 @@ public class EntityVerifyVersionProcess implements BeforeTransactionCompletionPr @Override public void doBeforeTransactionCompletion(SessionImplementor session) { final EntityEntry entry = session.getPersistenceContext().getEntry( object ); - // Don't check version for an entity that is not in the PersistenceContext; + // Don't check version for an entity that is not in the PersistenceContext if ( entry != null ) { final Object latestVersion = entry.getPersister().getCurrentVersion( entry.getId(), session ); if ( !entry.getVersion().equals( latestVersion ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 49a9e069f5..fe93d260e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -2018,16 +2018,21 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun switch ( lockMode ) { case PESSIMISTIC_FORCE_INCREMENT: return new PessimisticForceIncrementLockingStrategy( lockable, lockMode ); + case UPGRADE_NOWAIT: + case UPGRADE_SKIPLOCKED: case PESSIMISTIC_WRITE: return new PessimisticWriteSelectLockingStrategy( lockable, lockMode ); case PESSIMISTIC_READ: return new PessimisticReadSelectLockingStrategy( lockable, lockMode ); - case OPTIMISTIC: - return new OptimisticLockingStrategy( lockable, lockMode ); case OPTIMISTIC_FORCE_INCREMENT: return new OptimisticForceIncrementLockingStrategy( lockable, lockMode ); - default: + case OPTIMISTIC: + return new OptimisticLockingStrategy( lockable, lockMode ); + case READ: return new SelectLockingStrategy( lockable, lockMode ); + default: + // WRITE, NONE are not allowed here + throw new IllegalArgumentException( "Unsupported lock mode" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java index aae2f03bf3..22d9a182f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java @@ -6,13 +6,29 @@ */ package org.hibernate.dialect.lock; +import org.hibernate.HibernateException; +import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.StaleObjectStateException; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.Lockable; +import org.hibernate.sql.SimpleSelect; +import org.hibernate.stat.spi.StatisticsImplementor; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import static org.hibernate.pretty.MessageHelper.infoString; /** * Base {@link LockingStrategy} implementation to support implementations - * based on issuing {@code SQL} {@code SELECT} statements + * based on issuing SQL {@code SELECT} statements. For non-read locks, + * this is achieved via the dialect's native {@code SELECT ... FOR UPDATE} + * syntax. * * @author Steve Ebersole */ @@ -35,20 +51,88 @@ public abstract class AbstractSelectLockingStrategy implements LockingStrategy { return lockMode; } - protected abstract String generateLockString(int lockTimeout); + protected String generateLockString(int lockTimeout) { + final SessionFactoryImplementor factory = lockable.getFactory(); + final LockOptions lockOptions = new LockOptions( lockMode ); + lockOptions.setTimeOut( lockTimeout ); + final SimpleSelect select = + new SimpleSelect( factory ) + .setLockOptions( lockOptions ) + .setTableName( lockable.getRootTableName() ) + .addColumn( lockable.getRootTableIdentifierColumnNames()[0] ) + .addRestriction( lockable.getRootTableIdentifierColumnNames() ); + if ( lockable.isVersioned() ) { + select.addRestriction( lockable.getVersionColumnName() ); + } + if ( factory.getSessionFactoryOptions().isCommentsEnabled() ) { + select.setComment( lockMode + " lock " + lockable.getEntityName() ); + } + return select.toStatementString(); + } + + @Override + public void lock(Object id, Object version, Object object, int timeout, EventSource session) + throws StaleObjectStateException, JDBCException { + final String sql = determineSql( timeout ); + final SessionFactoryImplementor factory = session.getFactory(); + final Lockable lockable = getLockable(); + try { + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + final PreparedStatement st = jdbcCoordinator.getStatementPreparer().prepareStatement( sql ); + try { + lockable.getIdentifierType().nullSafeSet( st, id, 1, session ); + if ( lockable.isVersioned() ) { + lockable.getVersionType().nullSafeSet( + st, + version, + lockable.getIdentifierType().getColumnSpan( factory ) + 1, + session + ); + } + + final ResultSet rs = jdbcCoordinator.getResultSetReturn().extract( st, sql ); + try { + if ( !rs.next() ) { + final StatisticsImplementor statistics = factory.getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.optimisticFailure( lockable.getEntityName() ); + } + throw new StaleObjectStateException( lockable.getEntityName(), id ); + } + } + finally { + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( rs, st ); + } + } + finally { + jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( st ); + jdbcCoordinator.afterStatementExecution(); + } + } + catch ( SQLException sqle ) { + throw convertException( object, jdbcException( id, session, sqle, sql ) ); + } + } + + private JDBCException jdbcException(Object id, EventSource session, SQLException sqle, String sql) { + return session.getJdbcServices().getSqlExceptionHelper() + .convert( sqle, "could not lock: " + infoString( lockable, id, session.getFactory() ), sql ); + } + + protected HibernateException convertException(Object entity, JDBCException ex) { + return ex; + } protected String determineSql(int timeout) { - if ( timeout == LockOptions.WAIT_FOREVER) { - return waitForeverSql; - } - else if ( timeout == LockOptions.NO_WAIT) { - return getNoWaitSql(); - } - else if ( timeout == LockOptions.SKIP_LOCKED) { - return getSkipLockedSql(); - } - else { - return generateLockString( timeout ); + switch (timeout) { + case LockOptions.WAIT_FOREVER: + return waitForeverSql; + case LockOptions.NO_WAIT: + return getNoWaitSql(); + case LockOptions.SKIP_LOCKED: + return getSkipLockedSql(); + default: + return generateLockString( timeout ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java index 864a56966f..6fd50472ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java @@ -46,7 +46,7 @@ public class OptimisticForceIncrementLockingStrategy implements LockingStrategy if ( !lockable.isVersioned() ) { throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); } - final EntityEntry entry = session.getPersistenceContextInternal().getEntry( object ); +// final EntityEntry entry = session.getPersistenceContextInternal().getEntry( object ); // Register the EntityIncrementVersionProcess action to run just prior to transaction commit. session.getActionQueue().registerProcess( new EntityIncrementVersionProcess( object ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java index 2e4f8c0763..b2f6170805 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java @@ -6,32 +6,18 @@ */ package org.hibernate.dialect.lock; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - +import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.StaleObjectStateException; -import org.hibernate.engine.jdbc.spi.JdbcCoordinator; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.Lockable; -import org.hibernate.pretty.MessageHelper; -import org.hibernate.sql.SimpleSelect; -import org.hibernate.stat.spi.StatisticsImplementor; /** - * A pessimistic locking strategy where a lock is obtained via a - * select statements. + * A pessimistic locking strategy where {@link LockMode#PESSIMISTIC_READ} + * is obtained via a select statement. *

- * For non-read locks, this is achieved through the dialect's native - * {@code SELECT ... FOR UPDATE} syntax. - *

- * This strategy is valid for {@link LockMode#PESSIMISTIC_READ}. - *

- * This class is a clone of {@link SelectLockingStrategy}. + * Differs from {@link SelectLockingStrategy} in throwing + * {@link PessimisticEntityLockException}. * * @author Steve Ebersole * @author Scott Marlow @@ -53,68 +39,7 @@ public class PessimisticReadSelectLockingStrategy extends AbstractSelectLockingS } @Override - public void lock(Object id, Object version, Object object, int timeout, EventSource session) { - final String sql = determineSql( timeout ); - final SessionFactoryImplementor factory = session.getFactory(); - try { - final Lockable lockable = getLockable(); - try { - final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); - final PreparedStatement st = jdbcCoordinator.getStatementPreparer().prepareStatement( sql ); - try { - lockable.getIdentifierType().nullSafeSet( st, id, 1, session ); - if ( lockable.isVersioned() ) { - lockable.getVersionType().nullSafeSet( - st, - version, - lockable.getIdentifierType().getColumnSpan( factory ) + 1, - session - ); - } - - final ResultSet rs = jdbcCoordinator.getResultSetReturn().extract( st, sql ); - if ( !rs.next() ) { - final StatisticsImplementor statistics = factory.getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.optimisticFailure( lockable.getEntityName() ); - } - throw new StaleObjectStateException( lockable.getEntityName(), id ); - } - } - finally { - jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( st ); - jdbcCoordinator.afterStatementExecution(); - } - - } - catch ( SQLException e ) { - throw session.getJdbcServices().getSqlExceptionHelper().convert( - e, - "could not lock: " + MessageHelper.infoString( lockable, id, session.getFactory() ), - sql - ); - } - } - catch (JDBCException e) { - throw new PessimisticEntityLockException( object, "could not obtain pessimistic lock", e ); - } - } - - protected String generateLockString(int lockTimeout) { - final SessionFactoryImplementor factory = getLockable().getFactory(); - final LockOptions lockOptions = new LockOptions( getLockMode() ); - lockOptions.setTimeOut( lockTimeout ); - final SimpleSelect select = new SimpleSelect( factory ) - .setLockOptions( lockOptions ) - .setTableName( getLockable().getRootTableName() ) - .addColumn( getLockable().getRootTableIdentifierColumnNames()[0] ) - .addRestriction( getLockable().getRootTableIdentifierColumnNames() ); - if ( getLockable().isVersioned() ) { - select.addRestriction( getLockable().getVersionColumnName() ); - } - if ( factory.getSessionFactoryOptions().isCommentsEnabled() ) { - select.setComment( getLockMode() + " lock " + getLockable().getEntityName() ); - } - return select.toStatementString(); + protected HibernateException convertException(Object entity, JDBCException ex) { + return new PessimisticEntityLockException( entity, "could not obtain pessimistic lock", ex ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java index 83986c614a..222b9e5e19 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java @@ -6,32 +6,18 @@ */ package org.hibernate.dialect.lock; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - +import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.StaleObjectStateException; -import org.hibernate.engine.jdbc.spi.JdbcCoordinator; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.Lockable; -import org.hibernate.pretty.MessageHelper; -import org.hibernate.sql.SimpleSelect; -import org.hibernate.stat.spi.StatisticsImplementor; /** - * A pessimistic locking strategy where a lock is obtained via a - * select statement. + * A pessimistic locking strategy where {@link LockMode#PESSIMISTIC_WRITE} + * lock is obtained via a select statement. *

- * For non-read locks, this is achieved through the dialect's native - * {@code SELECT ... FOR UPDATE} syntax. - *

- * This strategy is valid for {@link LockMode#PESSIMISTIC_WRITE}. - *

- * This class is a clone of {@link SelectLockingStrategy}. + * Differs from {@link SelectLockingStrategy} in throwing + * {@link PessimisticEntityLockException}. * * @see org.hibernate.dialect.Dialect#getForUpdateString(LockMode) * @see org.hibernate.dialect.Dialect#appendLockHint(LockOptions, String) @@ -52,72 +38,7 @@ public class PessimisticWriteSelectLockingStrategy extends AbstractSelectLocking } @Override - public void lock(Object id, Object version, Object object, int timeout, EventSource session) { - final String sql = determineSql( timeout ); - final SessionFactoryImplementor factory = session.getFactory(); - try { - final Lockable lockable = getLockable(); - try { - final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); - final PreparedStatement st = jdbcCoordinator.getStatementPreparer().prepareStatement( sql ); - try { - lockable.getIdentifierType().nullSafeSet( st, id, 1, session ); - if ( lockable.isVersioned() ) { - lockable.getVersionType().nullSafeSet( - st, - version, - lockable.getIdentifierType().getColumnSpan( factory ) + 1, - session - ); - } - - final ResultSet rs = jdbcCoordinator.getResultSetReturn().extract( st, sql ); - try { - if ( !rs.next() ) { - final StatisticsImplementor statistics = factory.getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.optimisticFailure( lockable.getEntityName() ); - } - throw new StaleObjectStateException( lockable.getEntityName(), id ); - } - } - finally { - jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( rs, st ); - } - } - finally { - jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( st ); - jdbcCoordinator.afterStatementExecution(); - } - } - catch ( SQLException e ) { - throw session.getJdbcServices().getSqlExceptionHelper().convert( - e, - "could not lock: " + MessageHelper.infoString( lockable, id, session.getFactory() ), - sql - ); - } - } - catch (JDBCException e) { - throw new PessimisticEntityLockException( object, "could not obtain pessimistic lock", e ); - } - } - - protected String generateLockString(int lockTimeout) { - final SessionFactoryImplementor factory = getLockable().getFactory(); - final LockOptions lockOptions = new LockOptions( getLockMode() ); - lockOptions.setTimeOut( lockTimeout ); - final SimpleSelect select = new SimpleSelect( factory ) - .setLockOptions( lockOptions ) - .setTableName( getLockable().getRootTableName() ) - .addColumn( getLockable().getRootTableIdentifierColumnNames()[0] ) - .addRestriction( getLockable().getRootTableIdentifierColumnNames() ); - if ( getLockable().isVersioned() ) { - select.addRestriction( getLockable().getVersionColumnName() ); - } - if ( factory.getSessionFactoryOptions().isCommentsEnabled() ) { - select.setComment( getLockMode() + " lock " + getLockable().getEntityName() ); - } - return select.toStatementString(); + protected HibernateException convertException(Object entity, JDBCException ex) { + return new PessimisticEntityLockException( entity, "could not obtain pessimistic lock", ex ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java index 5e7eb58e10..19da8fd3dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java @@ -6,27 +6,19 @@ */ package org.hibernate.dialect.lock; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - +import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.StaleObjectStateException; -import org.hibernate.engine.jdbc.spi.JdbcCoordinator; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.Lockable; -import org.hibernate.pretty.MessageHelper; -import org.hibernate.sql.SimpleSelect; -import org.hibernate.stat.spi.StatisticsImplementor; -/** - * A locking strategy where a lock is obtained via a select statement. + /** + * A locking strategy where an optimistic lock is obtained via a select + * statement. *

- * For non-read locks, this is achieved through the dialect's native - * {@code SELECT ... FOR UPDATE} syntax. + * Differs from {@link PessimisticWriteSelectLockingStrategy} and + * {@link PessimisticReadSelectLockingStrategy} in throwing + * {@link OptimisticEntityLockException}. * * @see org.hibernate.dialect.Dialect#getForUpdateString(LockMode) * @see org.hibernate.dialect.Dialect#appendLockHint(LockOptions, String) @@ -46,73 +38,7 @@ public class SelectLockingStrategy extends AbstractSelectLockingStrategy { } @Override - public void lock( - Object id, - Object version, - Object object, - int timeout, - EventSource session) throws StaleObjectStateException, JDBCException { - final String sql = determineSql( timeout ); - final SessionFactoryImplementor factory = session.getFactory(); - final Lockable lockable = getLockable(); - try { - final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); - final PreparedStatement st = jdbcCoordinator.getStatementPreparer().prepareStatement( sql ); - try { - lockable.getIdentifierType().nullSafeSet( st, id, 1, session ); - if ( lockable.isVersioned() ) { - lockable.getVersionType().nullSafeSet( - st, - version, - lockable.getIdentifierType().getColumnSpan( factory ) + 1, - session - ); - } - - final ResultSet rs = jdbcCoordinator.getResultSetReturn().extract( st, sql ); - try { - if ( !rs.next() ) { - final StatisticsImplementor statistics = factory.getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.optimisticFailure( lockable.getEntityName() ); - } - throw new StaleObjectStateException( lockable.getEntityName(), id ); - } - } - finally { - jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( rs, st ); - } - } - finally { - jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( st ); - jdbcCoordinator.afterStatementExecution(); - } - - } - catch ( SQLException sqle ) { - throw session.getJdbcServices().getSqlExceptionHelper().convert( - sqle, - "could not lock: " + MessageHelper.infoString( lockable, id, session.getFactory() ), - sql - ); - } - } - - protected String generateLockString(int timeout) { - final SessionFactoryImplementor factory = getLockable().getFactory(); - final LockOptions lockOptions = new LockOptions( getLockMode() ); - lockOptions.setTimeOut( timeout ); - final SimpleSelect select = new SimpleSelect( factory ) - .setLockOptions( lockOptions ) - .setTableName( getLockable().getRootTableName() ) - .addColumn( getLockable().getRootTableIdentifierColumnNames()[0] ) - .addRestriction( getLockable().getRootTableIdentifierColumnNames() ); - if ( getLockable().isVersioned() ) { - select.addRestriction( getLockable().getVersionColumnName() ); - } - if ( factory.getSessionFactoryOptions().isCommentsEnabled() ) { - select.setComment( getLockMode() + " lock " + getLockable().getEntityName() ); - } - return select.toStatementString(); + protected HibernateException convertException(Object entity, JDBCException ex) { + return new OptimisticEntityLockException( entity, "could not obtain optimistic lock", ex ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java index d6e2fc51b1..62050a7492 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java @@ -51,8 +51,7 @@ public class LoaderHelper { public static void upgradeLock(Object object, EntityEntry entry, LockOptions lockOptions, EventSource session) { final LockMode requestedLockMode = lockOptions.getLockMode(); if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) { - // The user requested a "greater" (i.e. more restrictive) form of - // pessimistic lock + // Request is for a more restrictive lock than the lock already held if ( entry.getStatus().isDeletedOrGone()) { throw new ObjectDeletedException(