From d82c1e859bec6845bc89726bdb7637bc2844957e Mon Sep 17 00:00:00 2001 From: Scott Marlow Date: Fri, 6 Nov 2009 13:59:37 +0000 Subject: [PATCH] HHH-4546 - add JPA 2.0 locking git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@17944 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../src/main/java/org/hibernate/LockMode.java | 44 +++++ .../hibernate/OptimisticLockException.java | 51 ++++++ .../hibernate/PessimisticLockException.java | 50 ++++++ .../action/EntityIncrementVersionProcess.java | 58 +++++++ .../action/EntityVerifyVersionProcess.java | 64 +++++++ .../hibernate/cfg/ResultSetMappingBinder.java | 15 ++ .../java/org/hibernate/dialect/Dialect.java | 4 +- .../org/hibernate/engine/EntityEntry.java | 2 +- .../def/AbstractLockUpgradeEventListener.java | 23 ++- .../event/def/DefaultLoadEventListener.java | 2 +- .../event/def/DefaultLockEventListener.java | 4 +- .../entity/AbstractEntityPersister.java | 27 ++- .../ejb/AbstractEntityManagerImpl.java | 161 ++++++++++++------ .../org/hibernate/ejb/AbstractQueryImpl.java | 11 ++ .../java/org/hibernate/ejb/QueryImpl.java | 17 +- .../org/hibernate/ejb/test/lock/LockTest.java | 90 +++++++++- .../hibernate/test/jpa/lock/JPALockTest.java | 2 +- 17 files changed, 544 insertions(+), 81 deletions(-) create mode 100644 core/src/main/java/org/hibernate/OptimisticLockException.java create mode 100644 core/src/main/java/org/hibernate/PessimisticLockException.java create mode 100644 core/src/main/java/org/hibernate/action/EntityIncrementVersionProcess.java create mode 100644 core/src/main/java/org/hibernate/action/EntityVerifyVersionProcess.java diff --git a/core/src/main/java/org/hibernate/LockMode.java b/core/src/main/java/org/hibernate/LockMode.java index c598a1910e..b12411371c 100644 --- a/core/src/main/java/org/hibernate/LockMode.java +++ b/core/src/main/java/org/hibernate/LockMode.java @@ -87,6 +87,7 @@ public final class LockMode implements Serializable { /** * An upgrade lock. Objects loaded in this lock mode are * materialized using an SQL select ... for update. + * @deprecated instead use PESSIMISTIC_WRITE */ public static final LockMode UPGRADE = new LockMode(10, "UPGRADE"); /** @@ -107,9 +108,47 @@ public final class LockMode implements Serializable { /** * Similiar to {@link #UPGRADE} except that, for versioned entities, * it results in a forced version increment. + * @deprecated instead use PESSIMISTIC_FORCE_INCREMENT */ public static final LockMode FORCE = new LockMode( 15, "FORCE" ); + /** + * start of javax.persistence.LockModeType equivalent modes + */ + + /** + * 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"); + + /** + * 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"); + + /** + * Implemented as PESSIMISTIC_WRITE. + * TODO: introduce separate support for 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"); + + /** + * Transaction will immediately increment the entity version. + */ + public static final LockMode PESSIMISTIC_FORCE_INCREMENT = new LockMode(17,"PESSIMISTIC_FORCE_INCREMENT"); + + /** + * end of javax.persistence.LockModeType modes + */ + static { INSTANCES.put( NONE.name, NONE ); INSTANCES.put( READ.name, READ ); @@ -117,6 +156,11 @@ public final class LockMode implements Serializable { INSTANCES.put( UPGRADE_NOWAIT.name, UPGRADE_NOWAIT ); INSTANCES.put( WRITE.name, WRITE ); INSTANCES.put( FORCE.name, FORCE ); + INSTANCES.put( OPTIMISTIC.name, OPTIMISTIC); + INSTANCES.put( OPTIMISTIC_FORCE_INCREMENT.name, OPTIMISTIC_FORCE_INCREMENT); + INSTANCES.put( PESSIMISTIC_READ. name, PESSIMISTIC_READ); + INSTANCES.put( PESSIMISTIC_WRITE.name, PESSIMISTIC_WRITE); + INSTANCES.put( PESSIMISTIC_FORCE_INCREMENT.name, PESSIMISTIC_FORCE_INCREMENT); } private Object readResolve() { diff --git a/core/src/main/java/org/hibernate/OptimisticLockException.java b/core/src/main/java/org/hibernate/OptimisticLockException.java new file mode 100644 index 0000000000..44e56efb18 --- /dev/null +++ b/core/src/main/java/org/hibernate/OptimisticLockException.java @@ -0,0 +1,51 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, 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; + +/** + * + * Throw when an optimistic locking conflict occurs. + * + * @author Scott Marlow + */ +public class OptimisticLockException extends HibernateException { + + Object entity; + + public OptimisticLockException(String s) { + super(s); + } + + public OptimisticLockException(String s, Object entity) { + super(s); + this.entity = entity; + } + + public Object getEntity() { + return entity; + } + + +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/PessimisticLockException.java b/core/src/main/java/org/hibernate/PessimisticLockException.java new file mode 100644 index 0000000000..947e0b2ac0 --- /dev/null +++ b/core/src/main/java/org/hibernate/PessimisticLockException.java @@ -0,0 +1,50 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, 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; + +/** + * + * Throw when an pessimistic locking conflict occurs. + * + * @author Scott Marlow + */ +public class PessimisticLockException extends HibernateException { + + Object entity; + + public PessimisticLockException(String s) { + super(s); + } + + public PessimisticLockException(String s, Object entity) { + super(s); + this.entity = entity; + } + + public Object getEntity() { + return entity; + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/action/EntityIncrementVersionProcess.java b/core/src/main/java/org/hibernate/action/EntityIncrementVersionProcess.java new file mode 100644 index 0000000000..41e052dc3a --- /dev/null +++ b/core/src/main/java/org/hibernate/action/EntityIncrementVersionProcess.java @@ -0,0 +1,58 @@ +/* + * 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.action; + +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.EntityEntry; +import org.hibernate.persister.entity.EntityPersister; + +/** + * Verify/Increment the entity version + * + * @author Scott Marlow + */ +public class EntityIncrementVersionProcess implements BeforeTransactionCompletionProcess +{ + private final Object object; + private final EntityEntry entry; + + public EntityIncrementVersionProcess(Object object, EntityEntry entry) { + this.object = object; + this.entry = entry; + } + + /** + * Perform whatever processing is encapsulated here before completion of the transaction. + * + * @param session The session on which the transaction is preparing to complete. + */ + public void doBeforeTransactionCompletion(SessionImplementor session) { + final EntityPersister persister = entry.getPersister(); + Object nextVersion = persister.forceVersionIncrement( + entry.getId(), entry.getVersion(), session + ); + entry.forceLocked( object, nextVersion ); + } +} diff --git a/core/src/main/java/org/hibernate/action/EntityVerifyVersionProcess.java b/core/src/main/java/org/hibernate/action/EntityVerifyVersionProcess.java new file mode 100644 index 0000000000..96cd5ffd87 --- /dev/null +++ b/core/src/main/java/org/hibernate/action/EntityVerifyVersionProcess.java @@ -0,0 +1,64 @@ +/* + * 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.action; + +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.EntityEntry; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.HibernateException; +import org.hibernate.OptimisticLockException; + +/** + * Verify/Increment the entity version + * + * @author Scott Marlow + */ +public class EntityVerifyVersionProcess implements BeforeTransactionCompletionProcess +{ + private final Object object; + private final EntityEntry entry; + + public EntityVerifyVersionProcess(Object object, EntityEntry entry) { + this.object = object; + this.entry = entry; + } + + /** + * Perform whatever processing is encapsulated here before completion of the transaction. + * + * @param session The session on which the transaction is preparing to complete. + */ + public void doBeforeTransactionCompletion(SessionImplementor session) { + final EntityPersister persister = entry.getPersister(); + + Object latestVersion = persister.getCurrentVersion( + entry.getId(), session + ); + if(!entry.getVersion().equals(latestVersion)) + throw new OptimisticLockException( + "Newer version ("+ latestVersion+ + ") of entity ("+entry.getEntityName()+") found in database. id=" + entry.getId()); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/cfg/ResultSetMappingBinder.java b/core/src/main/java/org/hibernate/cfg/ResultSetMappingBinder.java index 0829c1766e..63013d7847 100644 --- a/core/src/main/java/org/hibernate/cfg/ResultSetMappingBinder.java +++ b/core/src/main/java/org/hibernate/cfg/ResultSetMappingBinder.java @@ -382,6 +382,21 @@ public abstract class ResultSetMappingBinder { else if ( "force".equals( lockMode ) ) { return LockMode.FORCE; } + else if ( "optimistic".equals( lockMode ) ) { + return LockMode.OPTIMISTIC; + } + else if ( "optimistic_force_increment".equals( lockMode ) ) { + return LockMode.OPTIMISTIC_FORCE_INCREMENT; + } + else if ( "pessimistic_read".equals( lockMode ) ) { + return LockMode.PESSIMISTIC_READ; + } + else if ( "pessimistic_write".equals( lockMode ) ) { + return LockMode.PESSIMISTIC_WRITE; + } + else if ( "pessimistic_force_increment".equals( lockMode ) ) { + return LockMode.PESSIMISTIC_FORCE_INCREMENT; + } else { throw new MappingException( "unknown lockmode" ); } diff --git a/core/src/main/java/org/hibernate/dialect/Dialect.java b/core/src/main/java/org/hibernate/dialect/Dialect.java index 70d12c70e5..f82d3844e3 100644 --- a/core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/core/src/main/java/org/hibernate/dialect/Dialect.java @@ -962,13 +962,13 @@ public abstract class Dialect { * @return The appropriate for update fragment. */ public String getForUpdateString(LockMode lockMode) { - if ( lockMode==LockMode.UPGRADE ) { + if ( lockMode==LockMode.UPGRADE || lockMode==LockMode.PESSIMISTIC_READ || lockMode==LockMode.PESSIMISTIC_WRITE) { return getForUpdateString(); } else if ( lockMode==LockMode.UPGRADE_NOWAIT ) { return getForUpdateNowaitString(); } - else if ( lockMode==LockMode.FORCE ) { + else if ( lockMode==LockMode.FORCE || lockMode==LockMode.PESSIMISTIC_FORCE_INCREMENT) { return getForUpdateNowaitString(); } else { diff --git a/core/src/main/java/org/hibernate/engine/EntityEntry.java b/core/src/main/java/org/hibernate/engine/EntityEntry.java index 38b11fe936..f220ab5f77 100644 --- a/core/src/main/java/org/hibernate/engine/EntityEntry.java +++ b/core/src/main/java/org/hibernate/engine/EntityEntry.java @@ -252,7 +252,7 @@ public final class EntityEntry implements Serializable { public void forceLocked(Object entity, Object nextVersion) { version = nextVersion; loadedState[ persister.getVersionProperty() ] = version; - setLockMode( LockMode.FORCE ); + setLockMode( LockMode.FORCE ); // TODO: use LockMode.PESSIMISTIC_FORCE_INCREMENT persister.setPropertyValue( entity, getPersister().getVersionProperty(), 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 71960a7287..08ef969c72 100644 --- a/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java +++ b/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java @@ -29,11 +29,14 @@ import org.slf4j.LoggerFactory; import org.hibernate.LockMode; import org.hibernate.ObjectDeletedException; +import org.hibernate.OptimisticLockException; +import org.hibernate.event.EventSource; +import org.hibernate.action.EntityIncrementVersionProcess; +import org.hibernate.action.EntityVerifyVersionProcess; import org.hibernate.cache.CacheKey; import org.hibernate.cache.access.SoftLock; import org.hibernate.engine.EntityEntry; import org.hibernate.engine.Status; -import org.hibernate.engine.SessionImplementor; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; @@ -55,7 +58,7 @@ public class AbstractLockUpgradeEventListener extends AbstractReassociateEventLi * @param requestedLockMode The lock mode being requested for locking. * @param source The session which is the source of the event being processed. */ - protected void upgradeLock(Object object, EntityEntry entry, LockMode requestedLockMode, SessionImplementor source) { + protected void upgradeLock(Object object, EntityEntry entry, LockMode requestedLockMode, EventSource source) { if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) { // The user requested a "greater" (i.e. more restrictive) form of @@ -97,13 +100,27 @@ public class AbstractLockUpgradeEventListener extends AbstractReassociateEventLi } try { - if ( persister.isVersioned() && requestedLockMode == LockMode.FORCE ) { + if ( persister.isVersioned() && (requestedLockMode == LockMode.FORCE || requestedLockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) ) { // 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/event/def/DefaultLoadEventListener.java b/core/src/main/java/org/hibernate/event/def/DefaultLoadEventListener.java index ef932b1cc6..2fb87805de 100644 --- a/core/src/main/java/org/hibernate/event/def/DefaultLoadEventListener.java +++ b/core/src/main/java/org/hibernate/event/def/DefaultLoadEventListener.java @@ -485,7 +485,7 @@ public class DefaultLoadEventListener extends AbstractLockUpgradeEventListener i return INCONSISTENT_RTN_CLASS_MARKER; } } - upgradeLock( old, oldEntry, event.getLockMode(), session ); + upgradeLock( old, oldEntry, event.getLockMode(), event.getSession() ); } return old; diff --git a/core/src/main/java/org/hibernate/event/def/DefaultLockEventListener.java b/core/src/main/java/org/hibernate/event/def/DefaultLockEventListener.java index a18a319948..a3a5ff8bd5 100644 --- a/core/src/main/java/org/hibernate/event/def/DefaultLockEventListener.java +++ b/core/src/main/java/org/hibernate/event/def/DefaultLockEventListener.java @@ -80,11 +80,11 @@ public class DefaultLockEventListener extends AbstractLockUpgradeEventListener i } entry = reassociate(event, entity, id, persister); - + // TODO: make cascadeOnLock optional based on SCOPE cascadeOnLock(event, persister, entity); } - upgradeLock( entity, entry, event.getLockMode(), source ); + upgradeLock( entity, entry, event.getLockMode(), event.getSession() ); } private void cascadeOnLock(LockEvent event, EntityPersister persister, Object entity) { 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 1697f77bd3..2f623c49e0 100644 --- a/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -1391,6 +1391,11 @@ public abstract class AbstractEntityPersister lockers.put( LockMode.UPGRADE, generateLocker( LockMode.UPGRADE ) ); lockers.put( LockMode.UPGRADE_NOWAIT, generateLocker( LockMode.UPGRADE_NOWAIT ) ); lockers.put( LockMode.FORCE, generateLocker( LockMode.FORCE ) ); + lockers.put( LockMode.PESSIMISTIC_READ, generateLocker( LockMode.PESSIMISTIC_READ ) ); + lockers.put( LockMode.PESSIMISTIC_WRITE, generateLocker( LockMode.PESSIMISTIC_WRITE ) ); + lockers.put( LockMode.PESSIMISTIC_FORCE_INCREMENT, generateLocker( LockMode.PESSIMISTIC_FORCE_INCREMENT ) ); + lockers.put( LockMode.OPTIMISTIC, generateLocker( LockMode.OPTIMISTIC ) ); + lockers.put( LockMode.OPTIMISTIC_FORCE_INCREMENT, generateLocker( LockMode.OPTIMISTIC_FORCE_INCREMENT ) ); } protected LockingStrategy generateLocker(LockMode lockMode) { @@ -3129,7 +3134,27 @@ public abstract class AbstractEntityPersister readLoader : createEntityLoader( LockMode.FORCE ) ); - + loaders.put( + LockMode.PESSIMISTIC_READ, + disableForUpdate ? + readLoader : + createEntityLoader( LockMode.PESSIMISTIC_READ ) + ); + loaders.put( + LockMode.PESSIMISTIC_WRITE, + disableForUpdate ? + readLoader : + createEntityLoader( LockMode.PESSIMISTIC_WRITE ) + ); + loaders.put( + LockMode.PESSIMISTIC_FORCE_INCREMENT, + disableForUpdate ? + readLoader : + createEntityLoader( LockMode.PESSIMISTIC_FORCE_INCREMENT ) + ); + loaders.put( LockMode.OPTIMISTIC, readLoader ); + loaders.put( LockMode.OPTIMISTIC_FORCE_INCREMENT, readLoader ); + loaders.put( "merge", new CascadeEntityLoader( this, CascadingAction.MERGE, getFactory() ) diff --git a/entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java b/entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java index 02fa0c989c..5617006105 100755 --- a/entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java +++ b/entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java @@ -38,6 +38,7 @@ import javax.persistence.NonUniqueResultException; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceContextType; import javax.persistence.PersistenceException; +import javax.persistence.PessimisticLockException; import javax.persistence.Query; import javax.persistence.TransactionRequiredException; import javax.persistence.TypedQuery; @@ -53,22 +54,7 @@ import javax.transaction.TransactionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.hibernate.AssertionFailure; -import org.hibernate.FlushMode; -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.MappingException; -import org.hibernate.ObjectDeletedException; -import org.hibernate.ObjectNotFoundException; -import org.hibernate.QueryException; -import org.hibernate.SQLQuery; -import org.hibernate.Session; -import org.hibernate.StaleObjectStateException; -import org.hibernate.StaleStateException; -import org.hibernate.Transaction; -import org.hibernate.TransientObjectException; -import org.hibernate.TypeMismatchException; -import org.hibernate.UnresolvableObjectException; +import org.hibernate.*; import org.hibernate.cfg.Environment; import org.hibernate.ejb.transaction.JoinableCMTTransaction; import org.hibernate.ejb.util.ConfigurationHelper; @@ -253,8 +239,21 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage @SuppressWarnings("unchecked") public A find(Class entityClass, Object primaryKey) { + LockModeType lmt = null; + return find( entityClass, primaryKey, lmt); + } + + public T find(Class entityClass, Object primaryKey, Map properties) { + return find(entityClass, primaryKey); + } + + @SuppressWarnings("unchecked") + public A find(Class entityClass, Object primaryKey, LockModeType lockModeType) { try { - return ( A ) getSession().get( entityClass, ( Serializable ) primaryKey ); + if ( lockModeType != null ) + return ( A ) getSession().get( entityClass, ( Serializable ) primaryKey, getLockMode(lockModeType) ); + else + return ( A ) getSession().get( entityClass, ( Serializable ) primaryKey ); } catch ( ObjectDeletedException e ) { //the spec is silent about people doing remove() find() on the same PC @@ -278,19 +277,8 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage } } - public T find(Class tClass, Object o, Map stringObjectMap) { - //FIXME - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public T find(Class tClass, Object o, LockModeType lockModeType) { - //FIXME - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public T find(Class tClass, Object o, LockModeType lockModeType, Map stringObjectMap) { - //FIXME - return null; //To change body of implemented methods use File | Settings | File Templates. + public A find(Class entityClass, Object primaryKey, LockModeType lockModeType, Map properties) { + return find(entityClass, primaryKey, lockModeType); } private void checkTransactionNeeded() { @@ -346,12 +334,25 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage } public void refresh(Object entity) { + LockModeType lmt = null; + refresh(entity, lmt); + } + + public void refresh(Object entity, Map properties) { + LockModeType lmt = null; + refresh(entity, lmt); + } + + public void refresh(Object entity, LockModeType lockModeType) { checkTransactionNeeded(); try { if ( !getSession().contains( entity ) ) { throw new IllegalArgumentException( "Entity not managed" ); } - getSession().refresh( entity ); + if(lockModeType != null) + getSession().refresh( entity, getLockMode(lockModeType) ); + else + getSession().refresh( entity ); } catch ( MappingException e ) { throw new IllegalArgumentException( e.getMessage(), e ); @@ -361,19 +362,8 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage } } - public void refresh(Object o, Map stringObjectMap) { - //FIXME - //To change body of implemented methods use File | Settings | File Templates. - } - - public void refresh(Object o, LockModeType lockModeType) { - //FIXME - //To change body of implemented methods use File | Settings | File Templates. - } - - public void refresh(Object o, LockModeType lockModeType, Map stringObjectMap) { - //FIXME - //To change body of implemented methods use File | Settings | File Templates. + public void refresh(Object entity, LockModeType lockModeType, Map properties) { + refresh(entity, lockModeType); } public boolean contains(Object entity) { @@ -393,9 +383,11 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage } } - public LockModeType getLockMode(Object o) { - //FIXME - return null; //To change body of implemented methods use File | Settings | File Templates. + public LockModeType getLockMode(Object entity) { + if ( !contains( entity ) ) { + throw new IllegalArgumentException( "entity not in the persistence context" ); + } + return this.getLockModeType(getSession().getCurrentLockMode(entity)); } public void setProperty(String s, Object o) { @@ -527,21 +519,57 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage getSession().lock( entity, getLockMode( lockMode ) ); } catch ( HibernateException he ) { - throwPersistenceException( he ); + throw convert( he ); } } - public void lock(Object o, LockModeType lockModeType, Map stringObjectMap) { - //FIXME - //To change body of implemented methods use File | Settings | File Templates. + public void lock(Object o, LockModeType lockModeType, Map properties) { + // todo: support different properties passed in + lock(o,lockModeType); } + + private LockModeType getLockModeType(LockMode lockMode) + { + if ( lockMode == LockMode.NONE ) + return LockModeType.NONE; + else if ( lockMode == LockMode.OPTIMISTIC || lockMode == LockMode.READ ) + return LockModeType.OPTIMISTIC; + else if ( lockMode == LockMode.OPTIMISTIC_FORCE_INCREMENT || lockMode == LockMode.WRITE ) + return LockModeType.OPTIMISTIC_FORCE_INCREMENT; + else if ( lockMode == LockMode.PESSIMISTIC_READ ) + return LockModeType.PESSIMISTIC_READ; + else if ( lockMode == LockMode.PESSIMISTIC_WRITE ) + return LockModeType.PESSIMISTIC_WRITE; + else if ( lockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) + return LockModeType.PESSIMISTIC_FORCE_INCREMENT; + throw new AssertionFailure("unhandled lock mode " + lockMode ); + } + + private LockMode getLockMode(LockModeType lockMode) { switch ( lockMode ) { + case READ: - return LockMode.UPGRADE; //assuming we are on read-commited and we need to prevent non repeteable read + case OPTIMISTIC: + return LockMode.OPTIMISTIC; + + case OPTIMISTIC_FORCE_INCREMENT: case WRITE: - return LockMode.FORCE; + return LockMode.OPTIMISTIC_FORCE_INCREMENT; + + case PESSIMISTIC_READ: + return LockMode.PESSIMISTIC_READ; + + case PESSIMISTIC_WRITE: + return LockMode.PESSIMISTIC_WRITE; + + case PESSIMISTIC_FORCE_INCREMENT: + return LockMode.PESSIMISTIC_FORCE_INCREMENT; + + case NONE: + return LockMode.NONE; + default: throw new AssertionFailure( "Unknown LockModeType: " + lockMode ); } @@ -772,6 +800,16 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage handlePersistenceException( converted ); return converted; } + else if ( e instanceof org.hibernate.OptimisticLockException ) { + PersistenceException converted = wrapLockException(e); + handlePersistenceException( converted ); + return converted; + } + else if ( e instanceof org.hibernate.PessimisticLockException ) { + PersistenceException converted = wrapLockException(e); + handlePersistenceException( converted ); + return converted; + } else if ( e instanceof ObjectNotFoundException ) { EntityNotFoundException converted = new EntityNotFoundException( e.getMessage() ); handlePersistenceException( converted ); @@ -846,4 +884,21 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage } return pe; } + + public PersistenceException wrapLockException(HibernateException e) { + PersistenceException pe; + if ( e instanceof org.hibernate.OptimisticLockException ) { + org.hibernate.OptimisticLockException ole = (org.hibernate.OptimisticLockException)e; + pe = new OptimisticLockException(ole.getMessage(), ole, ole.getEntity()); + } + else if ( e instanceof org.hibernate.PessimisticLockException ) { + org.hibernate.PessimisticLockException ple = (org.hibernate.PessimisticLockException)e; + pe = new PessimisticLockException(ple.getMessage(), ple, ple.getEntity()); + } + else { + pe = new OptimisticLockException( e ); + } + return pe; + } + } diff --git a/entitymanager/src/main/java/org/hibernate/ejb/AbstractQueryImpl.java b/entitymanager/src/main/java/org/hibernate/ejb/AbstractQueryImpl.java index 67a18811e4..e1d82b714a 100644 --- a/entitymanager/src/main/java/org/hibernate/ejb/AbstractQueryImpl.java +++ b/entitymanager/src/main/java/org/hibernate/ejb/AbstractQueryImpl.java @@ -242,6 +242,17 @@ public abstract class AbstractQueryImpl implements TypedQuery { return QueryHints.getDefinedHints(); } + private javax.persistence.LockModeType jpaLockMode = javax.persistence.LockModeType.NONE; + + public TypedQuery setLockMode(javax.persistence.LockModeType lockModeType) { + this.jpaLockMode = lockModeType; + return this; + } + + public javax.persistence.LockModeType getLockMode() { + return jpaLockMode; + } + private FlushModeType jpaFlushMode; public TypedQuery setFlushMode(FlushModeType jpaFlushMode) { diff --git a/entitymanager/src/main/java/org/hibernate/ejb/QueryImpl.java b/entitymanager/src/main/java/org/hibernate/ejb/QueryImpl.java index 81c8ce0a02..c7f0c76c5b 100755 --- a/entitymanager/src/main/java/org/hibernate/ejb/QueryImpl.java +++ b/entitymanager/src/main/java/org/hibernate/ejb/QueryImpl.java @@ -29,7 +29,7 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; -import javax.persistence.LockModeType; + import javax.persistence.NoResultException; import javax.persistence.NonUniqueResultException; import javax.persistence.Parameter; @@ -547,21 +547,6 @@ public class QueryImpl extends org.hibernate.ejb.AbstractQueryImpl impleme return param; } - /** - * {@inheritDoc} - */ - public TypedQuery setLockMode(LockModeType lockModeType) { - // TODO : aye aye aye - throw new UnsupportedOperationException( "Not yet implemented" ); - } - - /** - * {@inheritDoc} - */ - public LockModeType getLockMode() { - return LockModeType.NONE; - } - /** * {@inheritDoc} */ diff --git a/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java b/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java index 9810681746..d153e0819b 100644 --- a/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java +++ b/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java @@ -3,6 +3,7 @@ package org.hibernate.ejb.test.lock; import javax.persistence.EntityManager; import javax.persistence.LockModeType; +import javax.persistence.OptimisticLockException; import org.hibernate.ejb.test.TestCase; @@ -34,6 +35,29 @@ public class LockTest extends TestCase { em.close(); } + public void testLockOptimistic() throws Exception { + Lock lock = new Lock(); + lock.setName( "name" ); + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( lock ); + em.getTransaction().commit(); + + em.getTransaction().begin(); + lock = em.getReference( Lock.class, lock.getId() ); + em.lock( lock, LockModeType.OPTIMISTIC ); + lock.setName( "surname" ); + em.getTransaction().commit(); + + em.getTransaction().begin(); + lock = em.find( Lock.class, lock.getId() ); + assertEquals( "surname", lock.getName() ); + em.remove( lock ); + em.getTransaction().commit(); + + em.close(); + } + public void testLockWrite() throws Exception { Lock lock = new Lock(); lock.setName( "second" ); @@ -70,7 +94,19 @@ public class LockTest extends TestCase { em.getTransaction().begin(); lock = em.getReference( UnversionedLock.class, lock.getId() ); - em.lock( lock, LockModeType.READ ); + try { + // getting a READ (optimistic) lock on unversioned entity is not expected to work. + // To get the same functionality as prior release, change the LockModeType.READ lock to: + // em.lock(lock,LockModeType.PESSIMISTIC_READ); + em.lock( lock, LockModeType.READ ); + fail("expected OptimisticLockException exception"); + } catch(OptimisticLockException expected) {} + em.getTransaction().rollback(); + + // the previous code block can be rewritten as follows (to get the previous behavior) + em.getTransaction().begin(); + lock = em.getReference( UnversionedLock.class, lock.getId() ); + em.lock( lock, LockModeType.PESSIMISTIC_READ ); em.getTransaction().commit(); em.getTransaction().begin(); @@ -80,6 +116,58 @@ public class LockTest extends TestCase { em.close(); } + public void testLockPessimisticForceIncrement() throws Exception { + Lock lock = new Lock(); + lock.setName( "force" ); + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( lock ); + em.getTransaction().commit(); + + em.getTransaction().begin(); + lock = em.getReference( Lock.class, lock.getId() ); + Integer version = lock.getVersion(); + em.lock( lock, LockModeType.PESSIMISTIC_FORCE_INCREMENT ); + em.getTransaction().commit(); + + em.getTransaction().begin(); + lock = em.getReference( Lock.class, lock.getId() ); + try { + assertEquals( "should increase the version number ", 1, lock.getVersion() - version ); + } + finally { + em.remove( lock ); + em.getTransaction().commit(); + } + em.close(); + } + + public void testLockOptimisticForceIncrement() throws Exception { + Lock lock = new Lock(); + lock.setName( "force" ); + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( lock ); + em.getTransaction().commit(); + + em.getTransaction().begin(); + lock = em.getReference( Lock.class, lock.getId() ); + Integer version = lock.getVersion(); + em.lock( lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT ); + em.getTransaction().commit(); + + em.getTransaction().begin(); + lock = em.getReference( Lock.class, lock.getId() ); + try { + assertEquals( "should increase the version number ", 1, lock.getVersion() - version ); + } + finally { + em.remove( lock ); + em.getTransaction().commit(); + } + em.close(); + } + public Class[] getAnnotatedClasses() { return new Class[]{ Lock.class, diff --git a/testsuite/src/test/java/org/hibernate/test/jpa/lock/JPALockTest.java b/testsuite/src/test/java/org/hibernate/test/jpa/lock/JPALockTest.java index 903a6757e8..88c618052e 100644 --- a/testsuite/src/test/java/org/hibernate/test/jpa/lock/JPALockTest.java +++ b/testsuite/src/test/java/org/hibernate/test/jpa/lock/JPALockTest.java @@ -51,7 +51,7 @@ public class JPALockTest extends AbstractJPATest { * must always prevent the phenomena P1 and P2. Applications that call lock(entity, LockModeType.READ) * on non-versioned objects will not be portable. *

- * Odd as it may sound, EJB3 LockModeType.READ actually maps to the Hibernate LockMode.UPGRADE + * EJB3 LockModeType.READ actually maps to the Hibernate LockMode.OPTIMISTIC */ public void testLockModeTypeRead() { if ( !readCommittedIsolationMaintained( "ejb3 lock tests" ) ) {