From 655be65063f861428503c58737cf23a4b8f7368d Mon Sep 17 00:00:00 2001 From: Scott Marlow Date: Thu, 12 Nov 2009 04:20:41 +0000 Subject: [PATCH] HHH-4546 - add JPA 2.0 locking. git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@17962 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../src/main/java/org/hibernate/LockMode.java | 10 +- .../org/hibernate/dialect/Cache71Dialect.java | 21 ++- .../java/org/hibernate/dialect/Dialect.java | 18 ++- .../hibernate/dialect/FrontBaseDialect.java | 21 ++- .../org/hibernate/dialect/HSQLDialect.java | 17 +- .../org/hibernate/dialect/MckoiDialect.java | 21 ++- .../hibernate/dialect/PointbaseDialect.java | 21 ++- .../hibernate/dialect/RDMSOS2200Dialect.java | 21 ++- .../hibernate/dialect/SQLServerDialect.java | 9 +- .../hibernate/dialect/TimesTenDialect.java | 21 ++- .../dialect/lock/LockingStrategy.java | 3 +- ...timisticForceIncrementLockingStrategy.java | 91 +++++++++++ .../lock/OptimisticLockingStrategy.java | 88 +++++++++++ ...simisticForceIncrementLockingStrategy.java | 93 +++++++++++ .../PessimisticReadSelectLockingStrategy.java | 148 ++++++++++++++++++ .../PessimisticReadUpdateLockingStrategy.java | 148 ++++++++++++++++++ ...PessimisticWriteSelectLockingStrategy.java | 148 ++++++++++++++++++ ...PessimisticWriteUpdateLockingStrategy.java | 148 ++++++++++++++++++ .../dialect/lock/SelectLockingStrategy.java | 1 + .../dialect/lock/UpdateLockingStrategy.java | 3 +- .../def/AbstractLockUpgradeEventListener.java | 16 +- .../entity/AbstractEntityPersister.java | 2 +- 22 files changed, 1014 insertions(+), 55 deletions(-) create mode 100644 core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java create mode 100644 core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java create mode 100644 core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java create mode 100644 core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java create mode 100644 core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java create mode 100644 core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java create mode 100644 core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java diff --git a/core/src/main/java/org/hibernate/LockMode.java b/core/src/main/java/org/hibernate/LockMode.java index b12411371c..1d27f3dea2 100644 --- a/core/src/main/java/org/hibernate/LockMode.java +++ b/core/src/main/java/org/hibernate/LockMode.java @@ -120,30 +120,30 @@ public final class LockMode implements Serializable { * Optimisticly assume that transaction will not experience contention for * entities. The entity version will be verified near the transaction end. */ - public static final LockMode OPTIMISTIC = new LockMode(3,"OPTIMISTIC"); + public static final LockMode OPTIMISTIC = new LockMode( 3, "OPTIMISTIC"); /** * Optimisticly assume that transaction will not experience contention for entities. * The entity version will be verified and incremented near the transaction end. */ - public static final LockMode OPTIMISTIC_FORCE_INCREMENT = new LockMode(7,"OPTIMISTIC_FORCE_INCREMENT"); + public static final LockMode OPTIMISTIC_FORCE_INCREMENT = new LockMode( 4, "OPTIMISTIC_FORCE_INCREMENT"); /** * Implemented as PESSIMISTIC_WRITE. * TODO: introduce separate support for PESSIMISTIC_READ */ - public static final LockMode PESSIMISTIC_READ = new LockMode(12,"PESSIMISTIC_READ"); + public static final LockMode PESSIMISTIC_READ = new LockMode( 12, "PESSIMISTIC_READ"); /** * Transaction will obtain a database lock immediately. * TODO: add PESSIMISTIC_WRITE_NOWAIT */ - public static final LockMode PESSIMISTIC_WRITE = new LockMode(13,"PESSIMISTIC_WRITE"); + public static final LockMode PESSIMISTIC_WRITE = new LockMode( 13, "PESSIMISTIC_WRITE"); /** * Transaction will immediately increment the entity version. */ - public static final LockMode PESSIMISTIC_FORCE_INCREMENT = new LockMode(17,"PESSIMISTIC_FORCE_INCREMENT"); + public static final LockMode PESSIMISTIC_FORCE_INCREMENT = new LockMode( 17, "PESSIMISTIC_FORCE_INCREMENT"); /** * end of javax.persistence.LockModeType modes diff --git a/core/src/main/java/org/hibernate/dialect/Cache71Dialect.java b/core/src/main/java/org/hibernate/dialect/Cache71Dialect.java index 377dc81f52..b222ee06ac 100644 --- a/core/src/main/java/org/hibernate/dialect/Cache71Dialect.java +++ b/core/src/main/java/org/hibernate/dialect/Cache71Dialect.java @@ -41,9 +41,7 @@ import org.hibernate.dialect.function.VarArgsSQLFunction; import org.hibernate.dialect.function.StandardJDBCEscapeFunction; import org.hibernate.dialect.function.ConvertFunction; import org.hibernate.dialect.function.ConditionalParenthesisFunction; -import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; -import org.hibernate.dialect.lock.UpdateLockingStrategy; +import org.hibernate.dialect.lock.*; import org.hibernate.exception.CacheSQLStateConverter; import org.hibernate.exception.SQLExceptionConverter; import org.hibernate.exception.TemplatedViolatedConstraintNameExtracter; @@ -564,7 +562,22 @@ public class Cache71Dialect extends Dialect { public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { // InterSystems Cache' does not current support "SELECT ... FOR UPDATE" syntax... // Set your transaction mode to READ_COMMITTED before using - if ( lockMode.greaterThan( LockMode.READ ) ) { + if ( lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { + return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_WRITE) { + return new PessimisticWriteUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_READ) { + return new PessimisticReadUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC) { + return new OptimisticLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC_FORCE_INCREMENT) { + return new OptimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode.greaterThan( LockMode.READ ) ) { return new UpdateLockingStrategy( lockable, lockMode ); } else { diff --git a/core/src/main/java/org/hibernate/dialect/Dialect.java b/core/src/main/java/org/hibernate/dialect/Dialect.java index f82d3844e3..4daa74033c 100644 --- a/core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/core/src/main/java/org/hibernate/dialect/Dialect.java @@ -48,8 +48,7 @@ import org.hibernate.dialect.function.CastFunction; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.dialect.function.SQLFunctionTemplate; import org.hibernate.dialect.function.StandardSQLFunction; -import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; +import org.hibernate.dialect.lock.*; import org.hibernate.engine.Mapping; import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.exception.SQLExceptionConverter; @@ -952,6 +951,21 @@ public abstract class Dialect { * @since 3.2 */ public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { + if ( lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { + return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_WRITE) { + return new PessimisticWriteSelectLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_READ) { + return new PessimisticReadSelectLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC) { + return new OptimisticLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC_FORCE_INCREMENT) { + return new OptimisticForceIncrementLockingStrategy( lockable, lockMode); + } return new SelectLockingStrategy( lockable, lockMode ); } diff --git a/core/src/main/java/org/hibernate/dialect/FrontBaseDialect.java b/core/src/main/java/org/hibernate/dialect/FrontBaseDialect.java index 84b8a5b782..afc10f6e22 100644 --- a/core/src/main/java/org/hibernate/dialect/FrontBaseDialect.java +++ b/core/src/main/java/org/hibernate/dialect/FrontBaseDialect.java @@ -24,9 +24,7 @@ */ package org.hibernate.dialect; -import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.UpdateLockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; +import org.hibernate.dialect.lock.*; import org.hibernate.persister.entity.Lockable; import org.hibernate.LockMode; @@ -104,7 +102,22 @@ public class FrontBaseDialect extends Dialect { public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { // Frontbase has no known variation of a "SELECT ... FOR UPDATE" syntax... - if ( lockMode.greaterThan( LockMode.READ ) ) { + if ( lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { + return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_WRITE) { + return new PessimisticWriteUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_READ) { + return new PessimisticReadUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC) { + return new OptimisticLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC_FORCE_INCREMENT) { + return new OptimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode.greaterThan( LockMode.READ ) ) { return new UpdateLockingStrategy( lockable, lockMode ); } else { diff --git a/core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/core/src/main/java/org/hibernate/dialect/HSQLDialect.java index 8e0e9fb12d..a0914af0d7 100644 --- a/core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -38,8 +38,7 @@ import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.NoArgSQLFunction; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.dialect.function.VarArgsSQLFunction; -import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; +import org.hibernate.dialect.lock.*; import org.hibernate.exception.JDBCExceptionHelper; import org.hibernate.exception.TemplatedViolatedConstraintNameExtracter; import org.hibernate.exception.ViolatedConstraintNameExtracter; @@ -292,7 +291,17 @@ public class HSQLDialect extends Dialect { public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { // HSQLDB only supports READ_UNCOMMITTED transaction isolation + if ( lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { + return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC) { + return new OptimisticLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC_FORCE_INCREMENT) { + return new OptimisticForceIncrementLockingStrategy( lockable, lockMode); + } return new ReadUncommittedLockingStrategy( lockable, lockMode ); + } public static class ReadUncommittedLockingStrategy extends SelectLockingStrategy { @@ -300,12 +309,12 @@ public class HSQLDialect extends Dialect { super( lockable, lockMode ); } - public void lock(Serializable id, Object version, Object object, SessionImplementor session) + public void lock(Serializable id, Object version, Object object, int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException { if ( getLockMode().greaterThan( LockMode.READ ) ) { log.warn( "HSQLDB supports only READ_UNCOMMITTED isolation" ); } - super.lock( id, version, object, session ); + super.lock( id, version, object, timeout, session ); } } diff --git a/core/src/main/java/org/hibernate/dialect/MckoiDialect.java b/core/src/main/java/org/hibernate/dialect/MckoiDialect.java index 5750086462..b1c6d695dc 100644 --- a/core/src/main/java/org/hibernate/dialect/MckoiDialect.java +++ b/core/src/main/java/org/hibernate/dialect/MckoiDialect.java @@ -31,9 +31,7 @@ import org.hibernate.LockMode; import org.hibernate.persister.entity.Lockable; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.StandardSQLFunction; -import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.UpdateLockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; +import org.hibernate.dialect.lock.*; import org.hibernate.sql.CaseFragment; import org.hibernate.sql.MckoiCaseFragment; @@ -111,7 +109,22 @@ public class MckoiDialect extends Dialect { public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { // Mckoi has no known variation of a "SELECT ... FOR UPDATE" syntax... - if ( lockMode.greaterThan( LockMode.READ ) ) { + if ( lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { + return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_WRITE) { + return new PessimisticWriteUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_READ) { + return new PessimisticReadUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC) { + return new OptimisticLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC_FORCE_INCREMENT) { + return new OptimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode.greaterThan( LockMode.READ ) ) { return new UpdateLockingStrategy( lockable, lockMode ); } else { diff --git a/core/src/main/java/org/hibernate/dialect/PointbaseDialect.java b/core/src/main/java/org/hibernate/dialect/PointbaseDialect.java index e73cf343f6..14480689ef 100644 --- a/core/src/main/java/org/hibernate/dialect/PointbaseDialect.java +++ b/core/src/main/java/org/hibernate/dialect/PointbaseDialect.java @@ -24,9 +24,7 @@ */ package org.hibernate.dialect; -import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.UpdateLockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; +import org.hibernate.dialect.lock.*; import org.hibernate.persister.entity.Lockable; import org.hibernate.LockMode; @@ -81,7 +79,22 @@ public class PointbaseDialect extends org.hibernate.dialect.Dialect { public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { // Pointbase has no known variation of a "SELECT ... FOR UPDATE" syntax... - if ( lockMode.greaterThan( LockMode.READ ) ) { + if ( lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { + return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_WRITE) { + return new PessimisticWriteUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_READ) { + return new PessimisticReadUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC) { + return new OptimisticLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC_FORCE_INCREMENT) { + return new OptimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode.greaterThan( LockMode.READ ) ) { return new UpdateLockingStrategy( lockable, lockMode ); } else { diff --git a/core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java b/core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java index ca4d02a61f..61bce02f97 100755 --- a/core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java +++ b/core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java @@ -27,9 +27,7 @@ package org.hibernate.dialect; import org.hibernate.dialect.function.NoArgSQLFunction; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.dialect.function.SQLFunctionTemplate; -import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.UpdateLockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; +import org.hibernate.dialect.lock.*; import java.sql.Types; import org.hibernate.Hibernate; @@ -336,7 +334,22 @@ public class RDMSOS2200Dialect extends Dialect { public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { // RDMS has no known variation of a "SELECT ... FOR UPDATE" syntax... - if ( lockMode.greaterThan( LockMode.READ ) ) { + if ( lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { + return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_WRITE) { + return new PessimisticWriteUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_READ) { + return new PessimisticReadUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC) { + return new OptimisticLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC_FORCE_INCREMENT) { + return new OptimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode.greaterThan( LockMode.READ ) ) { return new UpdateLockingStrategy( lockable, lockMode ); } else { diff --git a/core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index 37fd359f78..4c9a44baf9 100644 --- a/core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -111,10 +111,15 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { } public String appendLockHint(LockMode mode, String tableName) { - if ( mode.greaterThan( LockMode.READ ) ) { - // does this need holdlock also? : return tableName + " with (updlock, rowlock, holdlock)"; + if ( ( mode == LockMode.UPGRADE ) || + ( mode == LockMode.UPGRADE_NOWAIT ) || + ( mode == LockMode.PESSIMISTIC_WRITE ) || + ( mode == LockMode.WRITE ) ) { return tableName + " with (updlock, rowlock)"; } + else if ( mode == LockMode.PESSIMISTIC_READ ) { + return tableName + " with (holdlock, rowlock)"; + } else { return tableName; } diff --git a/core/src/main/java/org/hibernate/dialect/TimesTenDialect.java b/core/src/main/java/org/hibernate/dialect/TimesTenDialect.java index 2a3bc82dbb..f3af6bf7d3 100644 --- a/core/src/main/java/org/hibernate/dialect/TimesTenDialect.java +++ b/core/src/main/java/org/hibernate/dialect/TimesTenDialect.java @@ -32,9 +32,7 @@ import org.hibernate.persister.entity.Lockable; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.NoArgSQLFunction; import org.hibernate.dialect.function.StandardSQLFunction; -import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.UpdateLockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; +import org.hibernate.dialect.lock.*; import org.hibernate.sql.JoinFragment; import org.hibernate.sql.OracleJoinFragment; @@ -217,7 +215,22 @@ public class TimesTenDialect extends Dialect { public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) { // TimesTen has no known variation of a "SELECT ... FOR UPDATE" syntax... - if ( lockMode.greaterThan( LockMode.READ ) ) { + if ( lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { + return new PessimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_WRITE) { + return new PessimisticWriteUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.PESSIMISTIC_READ) { + return new PessimisticReadUpdateLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC) { + return new OptimisticLockingStrategy( lockable, lockMode); + } + else if ( lockMode==LockMode.OPTIMISTIC_FORCE_INCREMENT) { + return new OptimisticForceIncrementLockingStrategy( lockable, lockMode); + } + else if ( lockMode.greaterThan( LockMode.READ ) ) { return new UpdateLockingStrategy( lockable, lockMode ); } else { diff --git a/core/src/main/java/org/hibernate/dialect/lock/LockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/LockingStrategy.java index 167a9cc7a9..b839853e96 100644 --- a/core/src/main/java/org/hibernate/dialect/lock/LockingStrategy.java +++ b/core/src/main/java/org/hibernate/dialect/lock/LockingStrategy.java @@ -51,11 +51,12 @@ public interface LockingStrategy { * @param id The id of the row to be locked * @param version The current version (or null if not versioned) * @param object The object logically being locked (currently not used) + * @param timeout timeout in milliseconds, 0 = no wait, -1 = wait indefinitely * @param session The session from which the lock request originated * @throws StaleObjectStateException Indicates an optimisitic lock failure * as part of acquiring the requested database lock. * @throws JDBCException */ - public void lock(Serializable id, Object version, Object object, SessionImplementor session) + public void lock(Serializable id, Object version, Object object, int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException; } diff --git a/core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java new file mode 100644 index 0000000000..55fdb27a5e --- /dev/null +++ b/core/src/main/java/org/hibernate/dialect/lock/OptimisticForceIncrementLockingStrategy.java @@ -0,0 +1,91 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.dialect.lock; + +import java.io.Serializable; + +import org.hibernate.HibernateException; +import org.hibernate.JDBCException; +import org.hibernate.LockMode; +import org.hibernate.StaleObjectStateException; +import org.hibernate.event.EventSource; +import org.hibernate.action.EntityIncrementVersionProcess; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.EntityEntry; +import org.hibernate.persister.entity.Lockable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An optimistic locking strategy that forces an increment of the version (after verifying that version hasn't changed). + * This takes place just prior to transaction commit. + *

+ * This strategy is valid for LockMode.OPTIMISTIC_FORCE_INCREMENT + * + * @since 3.5 + * + * @author Scott Marlow + */ +public class OptimisticForceIncrementLockingStrategy implements LockingStrategy { + private static final Logger log = LoggerFactory.getLogger( OptimisticForceIncrementLockingStrategy.class ); + + private final Lockable lockable; + private final LockMode lockMode; + /** + * Construct locking strategy. + * + * @param lockable The metadata for the entity to be locked. + * @param lockMode Indictates the type of lock to be acquired. + */ + public OptimisticForceIncrementLockingStrategy(Lockable lockable, LockMode lockMode) { + this.lockable = lockable; + this.lockMode = lockMode; + if ( lockMode.lessThan( LockMode.OPTIMISTIC_FORCE_INCREMENT ) ) { + throw new HibernateException( "[" + lockMode + "] not valid for [" + lockable.getEntityName() + "]" ); + } + } + + /** + * @see LockingStrategy#lock + */ + public void lock( + Serializable id, + Object version, + Object object, + int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException { + if ( !lockable.isVersioned() ) { + throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); + } + EntityEntry entry = session.getPersistenceContext().getEntry(object); + EntityIncrementVersionProcess incrementVersion = new EntityIncrementVersionProcess(object, entry); + EventSource source = (EventSource)session; + // Register the EntityIncrementVersionProcess action to run just prior to transaction commit. + source.getActionQueue().registerProcess(incrementVersion); + } + + protected LockMode getLockMode() { + return lockMode; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java new file mode 100644 index 0000000000..da6dac66f2 --- /dev/null +++ b/core/src/main/java/org/hibernate/dialect/lock/OptimisticLockingStrategy.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.dialect.lock; + +import java.io.Serializable; + +import org.hibernate.*; +import org.hibernate.event.EventSource; +import org.hibernate.action.EntityVerifyVersionProcess; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.EntityEntry; +import org.hibernate.persister.entity.Lockable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An optimistic locking strategy that verifies that the version hasn't changed (prior to transaction commit). + *

+ * This strategy is valid for LockMode.OPTIMISTIC + * + * @since 3.5 + * + * @author Scott Marlow + */ +public class OptimisticLockingStrategy implements LockingStrategy { + private static final Logger log = LoggerFactory.getLogger( OptimisticLockingStrategy.class ); + + private final Lockable lockable; + private final LockMode lockMode; + + /** + * Construct locking strategy. + * + * @param lockable The metadata for the entity to be locked. + * @param lockMode Indictates the type of lock to be acquired. + */ + public OptimisticLockingStrategy(Lockable lockable, LockMode lockMode) { + this.lockable = lockable; + this.lockMode = lockMode; + if ( lockMode.lessThan( LockMode.OPTIMISTIC ) ) { + throw new HibernateException( "[" + lockMode + "] not valid for [" + lockable.getEntityName() + "]" ); + } + } + + /** + * @see org.hibernate.dialect.lock.LockingStrategy#lock + */ + public void lock( + Serializable id, + Object version, + Object object, + int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException { + if ( !lockable.isVersioned() ) { + throw new OptimisticLockException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); + } + EntityEntry entry = session.getPersistenceContext().getEntry(object); + EventSource source = (EventSource)session; + EntityVerifyVersionProcess verifyVersion = new EntityVerifyVersionProcess(object, entry); + // Register the EntityVerifyVersionProcess action to run just prior to transaction commit. + source.getActionQueue().registerProcess(verifyVersion); + } + + protected LockMode getLockMode() { + return lockMode; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java new file mode 100644 index 0000000000..69fdd2eebe --- /dev/null +++ b/core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java @@ -0,0 +1,93 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.dialect.lock; + +import java.io.Serializable; + +import org.hibernate.HibernateException; +import org.hibernate.JDBCException; +import org.hibernate.LockMode; +import org.hibernate.StaleObjectStateException; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.EntityEntry; +import org.hibernate.persister.entity.Lockable; +import org.hibernate.persister.entity.EntityPersister; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A pessimistic locking strategy that increments the version immediately (obtaining an exclusive write lock). + *

+ * This strategy is valid for LockMode.PESSIMISTIC_FORCE_INCREMENT + * + * @since 3.5 + * + * @author Scott Marlow + */ +public class PessimisticForceIncrementLockingStrategy implements LockingStrategy { + private static final Logger log = LoggerFactory.getLogger( PessimisticForceIncrementLockingStrategy.class ); + + private final Lockable lockable; + private final LockMode lockMode; + + /** + * Construct locking strategy. + * + * @param lockable The metadata for the entity to be locked. + * @param lockMode Indictates the type of lock to be acquired. + */ + public PessimisticForceIncrementLockingStrategy(Lockable lockable, LockMode lockMode) { + this.lockable = lockable; + this.lockMode = lockMode; + // ForceIncrement can be used for PESSIMISTIC_READ, PESSIMISTIC_WRITE or PESSIMISTIC_FORCE_INCREMENT + if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) { + throw new HibernateException( "[" + lockMode + "] not valid for [" + lockable.getEntityName() + "]" ); + } + } + + /** + * @see org.hibernate.dialect.lock.LockingStrategy#lock + */ + public void lock( + Serializable id, + Object version, + Object object, + int timeout, + SessionImplementor session) throws StaleObjectStateException, JDBCException { + if ( !lockable.isVersioned() ) { + throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); + } + EntityEntry entry = session.getPersistenceContext().getEntry(object); + final EntityPersister persister = entry.getPersister(); + Object nextVersion = persister.forceVersionIncrement( + entry.getId(), entry.getVersion(), session + ); + entry.forceLocked( object, nextVersion ); + } + + protected LockMode getLockMode() { + return lockMode; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java new file mode 100644 index 0000000000..a6259db451 --- /dev/null +++ b/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java @@ -0,0 +1,148 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.dialect.lock; + +import org.hibernate.persister.entity.Lockable; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.StaleObjectStateException; +import org.hibernate.JDBCException; +import org.hibernate.LockMode; +import org.hibernate.sql.SimpleSelect; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.exception.JDBCExceptionHelper; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * A pessimistic locking strategy where the locks are obtained through select statements. + *

+ * For non-read locks, this is achieved through the Dialect's specific + * SELECT ... FOR UPDATE syntax. + * + * This strategy is valid for LockMode.PESSIMISTIC_READ + * + * This class is a clone of SelectLockingStrategy. + * + * @see org.hibernate.dialect.Dialect#getForUpdateString(org.hibernate.LockMode) + * @see org.hibernate.dialect.Dialect#appendLockHint(org.hibernate.LockMode, String) + * @since 3.5 + * + * @author Steve Ebersole + * @author Scott Marlow + */ +public class PessimisticReadSelectLockingStrategy implements LockingStrategy { + + private final Lockable lockable; + private final LockMode lockMode; + private final String sql; + + /** + * Construct a locking strategy based on SQL SELECT statements. + * + * @param lockable The metadata for the entity to be locked. + * @param lockMode Indictates the type of lock to be acquired. + */ + public PessimisticReadSelectLockingStrategy(Lockable lockable, LockMode lockMode) { + this.lockable = lockable; + this.lockMode = lockMode; + this.sql = generateLockString(); + } + + /** + * @see org.hibernate.dialect.lock.LockingStrategy#lock + */ + public void lock( + Serializable id, + Object version, + Object object, + int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException { + + SessionFactoryImplementor factory = session.getFactory(); + try { + PreparedStatement st = session.getBatcher().prepareSelectStatement( sql ); + try { + lockable.getIdentifierType().nullSafeSet( st, id, 1, session ); + if ( lockable.isVersioned() ) { + lockable.getVersionType().nullSafeSet( + st, + version, + lockable.getIdentifierType().getColumnSpan( factory ) + 1, + session + ); + } + + ResultSet rs = st.executeQuery(); + try { + if ( !rs.next() ) { + if ( factory.getStatistics().isStatisticsEnabled() ) { + factory.getStatisticsImplementor() + .optimisticFailure( lockable.getEntityName() ); + } + throw new StaleObjectStateException( lockable.getEntityName(), id ); + } + } + finally { + rs.close(); + } + } + finally { + session.getBatcher().closeStatement( st ); + } + + } + catch ( SQLException sqle ) { + throw JDBCExceptionHelper.convert( + session.getFactory().getSQLExceptionConverter(), + sqle, + "could not lock: " + MessageHelper.infoString( lockable, id, session.getFactory() ), + sql + ); + } + } + + protected LockMode getLockMode() { + return lockMode; + } + + protected String generateLockString() { + SessionFactoryImplementor factory = lockable.getFactory(); + SimpleSelect select = new SimpleSelect( factory.getDialect() ) + .setLockMode( lockMode ) + .setTableName( lockable.getRootTableName() ) + .addColumn( lockable.getRootTableIdentifierColumnNames()[0] ) + .addCondition( lockable.getRootTableIdentifierColumnNames(), "=?" ); + if ( lockable.isVersioned() ) { + select.addCondition( lockable.getVersionColumnName(), "=?" ); + } + if ( factory.getSettings().isCommentsEnabled() ) { + select.setComment( lockMode + " lock " + lockable.getEntityName() ); + } + return select.toStatementString(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java new file mode 100644 index 0000000000..d671fe8d4c --- /dev/null +++ b/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java @@ -0,0 +1,148 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.dialect.lock; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.HibernateException; +import org.hibernate.JDBCException; +import org.hibernate.LockMode; +import org.hibernate.StaleObjectStateException; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.exception.JDBCExceptionHelper; +import org.hibernate.persister.entity.Lockable; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.sql.Update; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A pessimistic locking strategy where the locks are obtained through update statements. + *

+ * This strategy is valid for LockMode.PESSIMISTIC_READ + * + * This class is a clone of UpdateLockingStrategy. + * + * @since 3.5 + * + * @author Steve Ebersole + * @author Scott Marlow + */ +public class PessimisticReadUpdateLockingStrategy implements LockingStrategy { + private static final Logger log = LoggerFactory.getLogger( PessimisticReadUpdateLockingStrategy.class ); + + private final Lockable lockable; + private final LockMode lockMode; + private final String sql; + + /** + * Construct a locking strategy based on SQL UPDATE statements. + * + * @param lockable The metadata for the entity to be locked. + * @param lockMode Indictates the type of lock to be acquired. Note that + * read-locks are not valid for this strategy. + */ + public PessimisticReadUpdateLockingStrategy(Lockable lockable, LockMode lockMode) { + this.lockable = lockable; + this.lockMode = lockMode; + if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) { + throw new HibernateException( "[" + lockMode + "] not valid for update statement" ); + } + if ( !lockable.isVersioned() ) { + log.warn( "write locks via update not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); + this.sql = null; + } + else { + this.sql = generateLockString(); + } + } + + /** + * @see org.hibernate.dialect.lock.LockingStrategy#lock + */ + public void lock( + Serializable id, + Object version, + Object object, + int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException { + if ( !lockable.isVersioned() ) { + throw new HibernateException( "write locks via update not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); + } + SessionFactoryImplementor factory = session.getFactory(); + try { + PreparedStatement st = session.getBatcher().prepareSelectStatement( sql ); + try { + lockable.getVersionType().nullSafeSet( st, version, 1, session ); + int offset = 2; + + lockable.getIdentifierType().nullSafeSet( st, id, offset, session ); + offset += lockable.getIdentifierType().getColumnSpan( factory ); + + if ( lockable.isVersioned() ) { + lockable.getVersionType().nullSafeSet( st, version, offset, session ); + } + + int affected = st.executeUpdate(); + if ( affected < 0 ) { // todo: should this instead check for exactly one row modified? + factory.getStatisticsImplementor().optimisticFailure( lockable.getEntityName() ); + throw new StaleObjectStateException( lockable.getEntityName(), id ); + } + + } + finally { + session.getBatcher().closeStatement( st ); + } + + } + catch ( SQLException sqle ) { + throw JDBCExceptionHelper.convert( + session.getFactory().getSQLExceptionConverter(), + sqle, + "could not lock: " + MessageHelper.infoString( lockable, id, session.getFactory() ), + sql + ); + } + } + + protected String generateLockString() { + SessionFactoryImplementor factory = lockable.getFactory(); + Update update = new Update( factory.getDialect() ); + update.setTableName( lockable.getRootTableName() ); + update.addPrimaryKeyColumns( lockable.getRootTableIdentifierColumnNames() ); + update.setVersionColumnName( lockable.getVersionColumnName() ); + update.addColumn( lockable.getVersionColumnName() ); + if ( factory.getSettings().isCommentsEnabled() ) { + update.setComment( lockMode + " lock " + lockable.getEntityName() ); + } + return update.toStatementString(); + } + + protected LockMode getLockMode() { + return lockMode; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java new file mode 100644 index 0000000000..2037376c73 --- /dev/null +++ b/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java @@ -0,0 +1,148 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.dialect.lock; + +import org.hibernate.persister.entity.Lockable; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.StaleObjectStateException; +import org.hibernate.JDBCException; +import org.hibernate.LockMode; +import org.hibernate.sql.SimpleSelect; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.exception.JDBCExceptionHelper; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * A pessimistic locking strategy where the locks are obtained through select statements. + *

+ * For non-read locks, this is achieved through the Dialect's specific + * SELECT ... FOR UPDATE syntax. + * + * This strategy is valid for LockMode.PESSIMISTIC_WRITE + * + * This class is a clone of SelectLockingStrategy. + * + * @see org.hibernate.dialect.Dialect#getForUpdateString(org.hibernate.LockMode) + * @see org.hibernate.dialect.Dialect#appendLockHint(org.hibernate.LockMode, String) + * @since 3.5 + * + * @author Steve Ebersole + * @author Scott Marlow + */ +public class PessimisticWriteSelectLockingStrategy implements LockingStrategy { + + private final Lockable lockable; + private final LockMode lockMode; + private final String sql; + + /** + * Construct a locking strategy based on SQL SELECT statements. + * + * @param lockable The metadata for the entity to be locked. + * @param lockMode Indictates the type of lock to be acquired. + */ + public PessimisticWriteSelectLockingStrategy(Lockable lockable, LockMode lockMode) { + this.lockable = lockable; + this.lockMode = lockMode; + this.sql = generateLockString(); + } + + /** + * @see LockingStrategy#lock + */ + public void lock( + Serializable id, + Object version, + Object object, + int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException { + + SessionFactoryImplementor factory = session.getFactory(); + try { + PreparedStatement st = session.getBatcher().prepareSelectStatement( sql ); + try { + lockable.getIdentifierType().nullSafeSet( st, id, 1, session ); + if ( lockable.isVersioned() ) { + lockable.getVersionType().nullSafeSet( + st, + version, + lockable.getIdentifierType().getColumnSpan( factory ) + 1, + session + ); + } + + ResultSet rs = st.executeQuery(); + try { + if ( !rs.next() ) { + if ( factory.getStatistics().isStatisticsEnabled() ) { + factory.getStatisticsImplementor() + .optimisticFailure( lockable.getEntityName() ); + } + throw new StaleObjectStateException( lockable.getEntityName(), id ); + } + } + finally { + rs.close(); + } + } + finally { + session.getBatcher().closeStatement( st ); + } + + } + catch ( SQLException sqle ) { + throw JDBCExceptionHelper.convert( + session.getFactory().getSQLExceptionConverter(), + sqle, + "could not lock: " + MessageHelper.infoString( lockable, id, session.getFactory() ), + sql + ); + } + } + + protected LockMode getLockMode() { + return lockMode; + } + + protected String generateLockString() { + SessionFactoryImplementor factory = lockable.getFactory(); + SimpleSelect select = new SimpleSelect( factory.getDialect() ) + .setLockMode( lockMode ) + .setTableName( lockable.getRootTableName() ) + .addColumn( lockable.getRootTableIdentifierColumnNames()[0] ) + .addCondition( lockable.getRootTableIdentifierColumnNames(), "=?" ); + if ( lockable.isVersioned() ) { + select.addCondition( lockable.getVersionColumnName(), "=?" ); + } + if ( factory.getSettings().isCommentsEnabled() ) { + select.setComment( lockMode + " lock " + lockable.getEntityName() ); + } + return select.toStatementString(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java new file mode 100644 index 0000000000..d0f0b04a31 --- /dev/null +++ b/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java @@ -0,0 +1,148 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.dialect.lock; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.HibernateException; +import org.hibernate.JDBCException; +import org.hibernate.LockMode; +import org.hibernate.StaleObjectStateException; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.exception.JDBCExceptionHelper; +import org.hibernate.persister.entity.Lockable; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.sql.Update; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A pessimistic locking strategy where the locks are obtained through update statements. + *

+ * This strategy is valid for LockMode.PESSIMISTIC_WRITE + * + * This class is a clone of UpdateLockingStrategy. + * + * @since 3.5 + * + * @author Steve Ebersole + * @author Scott Marlow + */ +public class PessimisticWriteUpdateLockingStrategy implements LockingStrategy { + private static final Logger log = LoggerFactory.getLogger( PessimisticWriteUpdateLockingStrategy.class ); + + private final Lockable lockable; + private final LockMode lockMode; + private final String sql; + + /** + * Construct a locking strategy based on SQL UPDATE statements. + * + * @param lockable The metadata for the entity to be locked. + * @param lockMode Indictates the type of lock to be acquired. Note that + * read-locks are not valid for this strategy. + */ + public PessimisticWriteUpdateLockingStrategy(Lockable lockable, LockMode lockMode) { + this.lockable = lockable; + this.lockMode = lockMode; + if ( lockMode.lessThan( LockMode.PESSIMISTIC_READ ) ) { + throw new HibernateException( "[" + lockMode + "] not valid for update statement" ); + } + if ( !lockable.isVersioned() ) { + log.warn( "write locks via update not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); + this.sql = null; + } + else { + this.sql = generateLockString(); + } + } + + /** + * @see LockingStrategy#lock + */ + public void lock( + Serializable id, + Object version, + Object object, + int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException { + if ( !lockable.isVersioned() ) { + throw new HibernateException( "write locks via update not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); + } + SessionFactoryImplementor factory = session.getFactory(); + try { + PreparedStatement st = session.getBatcher().prepareSelectStatement( sql ); + try { + lockable.getVersionType().nullSafeSet( st, version, 1, session ); + int offset = 2; + + lockable.getIdentifierType().nullSafeSet( st, id, offset, session ); + offset += lockable.getIdentifierType().getColumnSpan( factory ); + + if ( lockable.isVersioned() ) { + lockable.getVersionType().nullSafeSet( st, version, offset, session ); + } + + int affected = st.executeUpdate(); + if ( affected < 0 ) { // todo: should this instead check for exactly one row modified? + factory.getStatisticsImplementor().optimisticFailure( lockable.getEntityName() ); + throw new StaleObjectStateException( lockable.getEntityName(), id ); + } + + } + finally { + session.getBatcher().closeStatement( st ); + } + + } + catch ( SQLException sqle ) { + throw JDBCExceptionHelper.convert( + session.getFactory().getSQLExceptionConverter(), + sqle, + "could not lock: " + MessageHelper.infoString( lockable, id, session.getFactory() ), + sql + ); + } + } + + protected String generateLockString() { + SessionFactoryImplementor factory = lockable.getFactory(); + Update update = new Update( factory.getDialect() ); + update.setTableName( lockable.getRootTableName() ); + update.addPrimaryKeyColumns( lockable.getRootTableIdentifierColumnNames() ); + update.setVersionColumnName( lockable.getVersionColumnName() ); + update.addColumn( lockable.getVersionColumnName() ); + if ( factory.getSettings().isCommentsEnabled() ) { + update.setComment( lockMode + " lock " + lockable.getEntityName() ); + } + return update.toStatementString(); + } + + protected LockMode getLockMode() { + return lockMode; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java index 6b26369f99..c8fadc5113 100644 --- a/core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java +++ b/core/src/main/java/org/hibernate/dialect/lock/SelectLockingStrategy.java @@ -76,6 +76,7 @@ public class SelectLockingStrategy implements LockingStrategy { Serializable id, Object version, Object object, + int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException { SessionFactoryImplementor factory = session.getFactory(); diff --git a/core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java index da1b19a552..0aad187208 100644 --- a/core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java +++ b/core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java @@ -83,9 +83,10 @@ public class UpdateLockingStrategy implements LockingStrategy { * @see LockingStrategy#lock */ public void lock( - Serializable id, + Serializable id, Object version, Object object, + int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException { if ( !lockable.isVersioned() ) { throw new HibernateException( "write locks via update not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); diff --git a/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java b/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java index 08ef969c72..ddf827d959 100644 --- a/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java +++ b/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java @@ -100,27 +100,13 @@ public class AbstractLockUpgradeEventListener extends AbstractReassociateEventLi } try { - if ( persister.isVersioned() && (requestedLockMode == LockMode.FORCE || requestedLockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) ) { + if ( persister.isVersioned() && requestedLockMode == LockMode.FORCE ) { // todo : should we check the current isolation mode explicitly? Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), source ); entry.forceLocked( object, nextVersion ); } - else if ( requestedLockMode == LockMode.OPTIMISTIC_FORCE_INCREMENT ) { - if(!persister.isVersioned()) { - throw new OptimisticLockException("force: Version column is not mapped for " + entry.getPersister().getEntityName(), object); - } - EntityIncrementVersionProcess incrementVersion = new EntityIncrementVersionProcess(object, entry); - source.getActionQueue().registerProcess(incrementVersion); - } - else if ( requestedLockMode == LockMode.OPTIMISTIC ) { - if(!persister.isVersioned()) { - throw new OptimisticLockException("Version column is not mapped for " + entry.getPersister().getEntityName(), object); - } - EntityVerifyVersionProcess verifyVersion = new EntityVerifyVersionProcess(object, entry); - source.getActionQueue().registerProcess(verifyVersion); - } else { persister.lock( entry.getId(), entry.getVersion(), object, requestedLockMode, source ); } diff --git a/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 2f623c49e0..c8c5602b6d 100644 --- a/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -1412,7 +1412,7 @@ public abstract class AbstractEntityPersister Object object, LockMode lockMode, SessionImplementor session) throws HibernateException { - getLocker( lockMode ).lock( id, version, object, session ); + getLocker( lockMode ).lock( id, version, object, -1, session ); } public String getRootTableName() {