[hibernate-commits] Hibernate SVN: r18864 - in core/trunk: core/src/main/java/org/hibernate/event/def and 3 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Tue Feb 23 17:18:45 EST 2010


Author: gbadner
Date: 2010-02-23 17:18:43 -0500 (Tue, 23 Feb 2010)
New Revision: 18864

Added:
   core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Info.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Party.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Plan.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/AbstractReadOnlyTest.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Course.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Enrolment.hbm.xml
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Enrolment.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyCriteriaQueryTest.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Student.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/StudentDTO.java
Removed:
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/criteria/
Modified:
   core/trunk/core/src/main/java/org/hibernate/engine/EntityEntry.java
   core/trunk/core/src/main/java/org/hibernate/event/def/DefaultFlushEntityEventListener.java
   core/trunk/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Contract.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/ContractVariation.hbm.xml
   core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/ImmutableTest.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/DataPoint.hbm.xml
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyProxyTest.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlySessionLazyNonLazyTest.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlySessionTest.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyTest.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyVersionedNodesTest.java
Log:
HHH-4810 : Persistent immutable and read-only entities are updated before being deleted

Modified: core/trunk/core/src/main/java/org/hibernate/engine/EntityEntry.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/engine/EntityEntry.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/core/src/main/java/org/hibernate/engine/EntityEntry.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -46,6 +46,7 @@
 
 	private LockMode lockMode;
 	private Status status;
+	private Status previousStatus;
 	private final Serializable id;
 	private Object[] loadedState;
 	private Object[] deletedState;
@@ -72,6 +73,7 @@
 			final boolean disableVersionIncrement,
 			final boolean lazyPropertiesAreUnfetched) {
 		this.status=status;
+		this.previousStatus = null;
 		this.loadedState=loadedState;
 		this.id=id;
 		this.rowId=rowId;
@@ -91,6 +93,7 @@
 			final Serializable id,
 			final EntityMode entityMode,
 			final Status status,
+			final Status previousStatus,
 			final Object[] loadedState,
 	        final Object[] deletedState,
 			final Object version,
@@ -104,6 +107,7 @@
 		this.id = id;
 		this.entityMode = entityMode;
 		this.status = status;
+		this.previousStatus = previousStatus;
 		this.loadedState = loadedState;
 		this.deletedState = deletedState;
 		this.version = version;
@@ -130,7 +134,10 @@
 		if (status==Status.READ_ONLY) {
 			loadedState = null; //memory optimization
 		}
-		this.status = status;
+		if ( this.status != status ) {
+			this.previousStatus = this.status;
+			this.status = status;
+		}
 	}
 
 	public Serializable getId() {
@@ -220,6 +227,7 @@
 	 * exists in the database
 	 */
 	public void postDelete() {
+		previousStatus = status;
 		status = Status.GONE;
 		existsInDatabase = false;
 	}
@@ -246,20 +254,31 @@
 		return loadedState[propertyIndex];
 	}
 
-	public boolean requiresDirtyCheck(Object entity) {
-		
-		boolean isMutableInstance = 
-				status != Status.READ_ONLY && 
-				persister.isMutable();
-		
-		return isMutableInstance && (
+	public boolean requiresDirtyCheck(Object entity) {		
+		return isModifiableEntity() && (
 				getPersister().hasMutableProperties() ||
 				!FieldInterceptionHelper.isInstrumented( entity ) ||
 				FieldInterceptionHelper.extractFieldInterceptor( entity).isDirty()
 			);
-		
 	}
 
+	/**
+	 * Can the entity be modified?
+	 *
+	 * The entity is modifiable if all of the following are true:
+	 * <ul>
+	 * <li>the entity class is mutable</li>
+	 * <li>the entity is not read-only</li>
+	 * <li>if the current status is Status.DELETED, then the entity was not read-only when it was deleted</li>
+	 * </ul>
+	 * @return true, if the entity is modifiable; false, otherwise,
+	 */
+	public boolean isModifiableEntity() {
+		return ( status != Status.READ_ONLY ) &&
+				! ( status == Status.DELETED && previousStatus == Status.READ_ONLY ) &&
+				getPersister().isMutable();
+	}
+
 	public void forceLocked(Object entity, Object nextVersion) {
 		version = nextVersion;
 		loadedState[ persister.getVersionProperty() ] = version;
@@ -318,6 +337,7 @@
 		oos.writeObject( id );
 		oos.writeObject( entityMode.toString() );
 		oos.writeObject( status.toString() );
+		oos.writeObject( ( previousStatus == null ? "" : previousStatus.toString() ) );
 		// todo : potentially look at optimizing these two arrays
 		oos.writeObject( loadedState );
 		oos.writeObject( deletedState );
@@ -344,12 +364,17 @@
 	static EntityEntry deserialize(
 			ObjectInputStream ois,
 	        SessionImplementor session) throws IOException, ClassNotFoundException {
+		String previousStatusString = null;
 		return new EntityEntry(
 				( session == null ? null : session.getFactory() ),
 		        ( String ) ois.readObject(),
 				( Serializable ) ois.readObject(),
 	            EntityMode.parse( ( String ) ois.readObject() ),
 				Status.parse( ( String ) ois.readObject() ),
+				( ( previousStatusString = ( String ) ois.readObject() ).length() == 0 ?
+							null :
+							Status.parse( previousStatusString ) 
+				),
 	            ( Object[] ) ois.readObject(),
 	            ( Object[] ) ois.readObject(),
 	            ois.readObject(),

Modified: core/trunk/core/src/main/java/org/hibernate/event/def/DefaultFlushEntityEventListener.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/event/def/DefaultFlushEntityEventListener.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/core/src/main/java/org/hibernate/event/def/DefaultFlushEntityEventListener.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -38,7 +38,6 @@
 import org.hibernate.engine.EntityEntry;
 import org.hibernate.engine.EntityKey;
 import org.hibernate.engine.Nullability;
-import org.hibernate.engine.SessionFactoryImplementor;
 import org.hibernate.engine.SessionImplementor;
 import org.hibernate.engine.Status;
 import org.hibernate.engine.Versioning;
@@ -254,10 +253,24 @@
 		
 		if ( log.isTraceEnabled() ) {
 			if ( status == Status.DELETED ) {
-				log.trace(
+				if ( ! persister.isMutable() ) {
+					log.trace(
+						"Updating immutable, deleted entity: " +
+						MessageHelper.infoString( persister, entry.getId(), session.getFactory() )
+					);
+				}
+				else if ( ! entry.isModifiableEntity() ) {
+					log.trace(
+						"Updating non-modifiable, deleted entity: " +
+						MessageHelper.infoString( persister, entry.getId(), session.getFactory() )
+					);
+				}
+				else {
+					log.trace(
 						"Updating deleted entity: " +
 						MessageHelper.infoString( persister, entry.getId(), session.getFactory() )
 					);
+				}
 			}
 			else {
 				log.trace(
@@ -303,7 +316,9 @@
 						values,
 						dirtyProperties,
 						event.hasDirtyCollection(),
-						entry.getLoadedState(),
+						( status == Status.DELETED && ! entry.isModifiableEntity() ?
+								persister.getPropertyValues( entity, entityMode ) :
+								entry.getLoadedState() ),
 						entry.getVersion(),
 						nextVersion,
 						entity,
@@ -447,7 +462,7 @@
 	}
 
 	private boolean hasDirtyCollections(FlushEntityEvent event, EntityPersister persister, Status status) {
-		if ( isCollectionDirtyCheckNecessary(persister, status) ) {
+		if ( isCollectionDirtyCheckNecessary(persister, status ) ) {
 			DirtyCollectionSearchVisitor visitor = new DirtyCollectionSearchVisitor( 
 					event.getSession(),
 					persister.getPropertyVersionability()
@@ -463,8 +478,8 @@
 	}
 
 	private boolean isCollectionDirtyCheckNecessary(EntityPersister persister, Status status) {
-		return status==Status.MANAGED && 
-				persister.isVersioned() && 
+		return status==Status.MANAGED &&
+				persister.isVersioned() &&
 				persister.hasCollections();
 	}
 	
@@ -503,8 +518,28 @@
 			if ( !cannotDirtyCheck ) {
 				// dirty check against the usual snapshot of the entity
 				dirtyProperties = persister.findDirty( values, loadedState, entity, session );
-				
 			}
+			else if ( entry.getStatus() == Status.DELETED && ! event.getEntityEntry().isModifiableEntity() ) {
+				// A non-modifiable (e.g., read-only or immutable) entity needs to be have
+				// references to transient entities set to null before being deleted. No other
+				// fields should be updated.
+				if ( values != entry.getDeletedState() ) {
+					throw new IllegalStateException(
+							"Entity has status Status.DELETED but values != entry.getDeletedState"
+					);
+				}
+				// Even if loadedState == null, we can dirty-check by comparing currentState and
+				// entry.getDeletedState() because the only fields to be updated are those that
+				// refer to transient entities that are being set to null.
+				// - currentState contains the entity's current property values.
+				// - entry.getDeletedState() contains the entity's current property values with
+				//   references to transient entities set to null.
+				// - dirtyProperties will only contain properties that refer to transient entities
+				final Object[] currentState =
+						persister.getPropertyValues( event.getEntity(), event.getSession().getEntityMode() );
+				dirtyProperties = persister.findDirty( entry.getDeletedState(), currentState, entity, session );
+				cannotDirtyCheck = false;
+			}
 			else {
 				// dirty check against the database snapshot, if possible/necessary
 				final Object[] databaseSnapshot = getDatabaseSnapshot(session, persister, id);

Modified: core/trunk/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -63,6 +63,7 @@
 import org.hibernate.engine.Mapping;
 import org.hibernate.engine.SessionFactoryImplementor;
 import org.hibernate.engine.SessionImplementor;
+import org.hibernate.engine.Status;
 import org.hibernate.engine.ValueInclusion;
 import org.hibernate.engine.Versioning;
 import org.hibernate.exception.JDBCExceptionHelper;
@@ -2751,8 +2752,21 @@
 
 		final boolean[] propsToUpdate;
 		final String[] updateStrings;
-		if ( entityMetamodel.isDynamicUpdate() && dirtyFields != null ) {
-			// For the case of dynamic-update="true", we need to generate the UPDATE SQL
+		EntityEntry entry = session.getPersistenceContext().getEntry( object );
+
+		// Ensure that an immutable or non-modifiable entity is not being updated unless it is
+		// in the process of being deleted.
+		if ( entry == null && ! isMutable() ) {
+			throw new IllegalStateException( "Updating immutable entity that is not in session yet!" );
+		}
+		if ( entry != null && ! isModifiableEntity( entry ) && entry.getStatus() != Status.DELETED ) {
+			throw new IllegalStateException( "Updating non-modifiable entity that is not being deleted!" );
+		}
+		if ( ( entityMetamodel.isDynamicUpdate() || ! isModifiableEntity( entry ) ) && dirtyFields != null ) {
+			// For the following cases we need to generate the UPDATE SQL
+			// - dynamic-update="true"
+			// - a non-modifiable entity (e.g., read-only or immutable) needs to have
+			//   references to transient entities set to null before being deleted
 			propsToUpdate = getPropertiesToUpdate( dirtyFields, hasDirtyCollection );
 			// don't need to check laziness (dirty checking algorithm handles that)
 			updateStrings = new String[span];
@@ -3595,6 +3609,11 @@
 		return entityMetamodel.isMutable();
 	}
 
+	private boolean isModifiableEntity(EntityEntry entry) {
+
+		return ( entry == null ? isMutable() : entry.isModifiableEntity() );
+	}
+
 	public boolean isAbstract() {
 		return entityMetamodel.isAbstract();
 	}

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Contract.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Contract.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Contract.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -3,7 +3,10 @@
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 public class Contract implements Serializable {
 	
@@ -11,17 +14,38 @@
 	private String customerName;
 	private String type;
 	private List variations;
+	private Contract parent;
+	private Set subcontracts;
+	private Set plans;
+	private Set parties;
+	private Set infos;
 
 	public Contract() {
 		super();
 	}
 
-	public Contract(String customerName, String type) {
+	public Contract(Plan plan, String customerName, String type) {
+		plans = new HashSet();
+		plans.add( plan );
+		if ( plan != null ) {
+			plan.getContracts().add( this );
+		}
 		this.customerName = customerName;
 		this.type = type;
 		variations = new ArrayList();
+		subcontracts = new HashSet();
+		parties = new HashSet();
+		infos = new HashSet();
 	}
 
+	public Set getPlans() {
+		return plans;
+	}
+
+	public void setPlans(Set plans) {
+		this.plans = plans;
+	}
+
 	public String getCustomerName() {
 		return customerName;
 	}
@@ -54,4 +78,55 @@
 		this.variations = variations;
 	}
 
+	public Contract getParent() {
+		return parent;
+	}
+
+	public void setParent(Contract parent) {
+		this.parent = parent;
+	}
+
+	public Set getSubcontracts() {
+		return subcontracts;
+	}
+
+	public void setSubcontracts(Set subcontracts) {
+		this.subcontracts = subcontracts;
+	}
+
+	public void addSubcontract(Contract subcontract) {
+		subcontracts.add( subcontract );
+		subcontract.setParent( this );
+	}
+
+	public Set getParties() {
+		return parties;
+	}
+
+	public void setParties(Set parties) {
+		this.parties = parties;
+	}
+
+	public void addParty(Party party) {
+		parties.add( party );
+		party.setContract( this );
+	}
+
+	public void removeParty(Party party) {
+		parties.remove( party );
+		party.setContract( null );
+	}
+
+	public Set getInfos() {
+		return infos;
+	}
+
+	public void setInfos(Set infos) {
+		this.infos = infos;
+	}
+
+	public void addInfo(Info info) {
+		infos.add( info );
+		info.setContract( this );
+	}
 }

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/ContractVariation.hbm.xml
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/ContractVariation.hbm.xml	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/ContractVariation.hbm.xml	2010-02-23 22:18:43 UTC (rev 18864)
@@ -10,7 +10,34 @@
 -->
 
 <hibernate-mapping package="org.hibernate.test.immutable">
-	
+    <class name="Info" mutable="true">
+        <id name="id">
+            <generator class="increment"/>
+        </id>
+        <property name="text"/>        
+        <many-to-one name="contract" not-null="false"/>
+    </class>
+
+    <class name="Plan" mutable="false">
+        <id name="id">
+            <generator class="increment"/>
+        </id>
+        <property name="description" not-null="true"/>
+        <set name="contracts" table="plan_contract" inverse="false" mutable="true" cascade="all" fetch="join">
+            <key column="plan"/>
+            <many-to-many column="contract" class="Contract"/>
+        </set>
+    </class>
+
+    <class name="Party" mutable="false">
+        <id name="id">
+            <generator class="increment"/>
+        </id>
+        <!-- <many-to-one name="contract" update="false" insert="false"/> -->
+        <many-to-one name="contract" not-null="true"/>
+        <property name="name" not-null="true"/>
+    </class>
+
 	<class name="Contract" mutable="false">
 		<id name="id">
 			<generator class="increment"/>
@@ -22,8 +49,27 @@
 			<key column="contract"/>
 			<one-to-many class="ContractVariation"/>
 		</bag>
+        <!-- <many-to-one name="parent" /> -->
+        <many-to-one name="parent" update="false" insert="false"/>
+        <set name="subcontracts" inverse="false"
+                mutable="true" cascade="all" fetch="join">
+            <key column="parent"/>
+            <one-to-many class="Contract"/>
+        </set>
+        <set name="plans" table="plan_contract" inverse="true" mutable="true" cascade="none">
+            <key column="contract"/>
+            <many-to-many column="plan" class="Plan"/>
+        </set>
+        <set name="parties" inverse="true"  mutable="true" cascade="all" fetch="join">
+            <key column="contract"/>
+            <one-to-many class="Party"/>
+        </set>
+        <set name="infos" inverse="true"  mutable="true" cascade="all" fetch="join">
+            <key column="contract"/>
+            <one-to-many class="Info"/>
+        </set>
 	</class>
-	
+
 	<class name="ContractVariation" mutable="false">
 		<composite-id>
 			<key-many-to-one name="contract"/>

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/ImmutableTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/ImmutableTest.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/ImmutableTest.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -33,6 +33,8 @@
 import org.hibernate.HibernateException;
 import org.hibernate.Session;
 import org.hibernate.Transaction;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
 import org.hibernate.criterion.Projections;
 import org.hibernate.junit.functional.FunctionalTestCase;
 import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
@@ -46,6 +48,11 @@
 		super(str);
 	}
 
+	public void configure(Configuration cfg) {
+		cfg.setProperty( Environment.GENERATE_STATISTICS, "true");
+		cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
+	}	
+
 	public String[] getMappings() {
 		return new String[] { "immutable/ContractVariation.hbm.xml" };
 	}
@@ -55,11 +62,14 @@
 	}
 
 	public void testPersistImmutable() {
-		Contract c = new Contract("gavin", "phone");
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
 		cv2.setText("more expensive");
+
+		clearCounts();
+
 		Session s = openSession();
 		Transaction t = s.beginTransaction();
 		s.persist(c);
@@ -70,6 +80,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -90,10 +104,64 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
+	public void testPersistUpdateImmutableInSameTransaction() {
+		Contract c = new Contract( null, "gavin", "phone");
+		ContractVariation cv1 = new ContractVariation(1, c);
+		cv1.setText("expensive");
+		ContractVariation cv2 = new ContractVariation(2, c);
+		cv2.setText("more expensive");
+
+		clearCounts();
+
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+		s.persist(c);
+		// c, cv1, and cv2 were added to s by s.persist(c) (not hibernate), so they are modifiable
+		assertFalse( s.isReadOnly( c ) );
+		assertFalse( s.isReadOnly( cv1 ) );
+		assertFalse( s.isReadOnly( cv2 ) );
+		c.setCustomerName( "gail" );
+		t.commit();
+		s.close();
+
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
+		s = openSession();
+		t = s.beginTransaction();
+		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
+		// c was loaded into s by hibernate, so it should be read-only
+		assertTrue( s.isReadOnly( c ) );
+		assertEquals( c.getCustomerName(), "gavin" );
+		assertEquals( c.getVariations().size(), 2 );
+		Iterator it = c.getVariations().iterator();
+		cv1 = (ContractVariation) it.next();
+		assertEquals( cv1.getText(), "expensive" );
+		cv2 = (ContractVariation) it.next();
+		assertEquals( cv2.getText(), "more expensive" );
+		// cv1 and cv2 were loaded into s by hibernate, so they should be read-only
+		assertTrue( s.isReadOnly( cv1 ) );
+		assertTrue( s.isReadOnly( cv2 ) );
+		s.delete(c);
+		assertEquals( s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
+		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
+		t.commit();
+		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
+	}
+
 	public void testSaveImmutable() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -108,6 +176,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -128,10 +200,15 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testSaveOrUpdateImmutable() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -146,6 +223,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -166,10 +247,15 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testImmutable() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -183,7 +269,11 @@
 		assertFalse( s.isReadOnly( cv2 ) );
 		t.commit();
 		s.close();
-		
+
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -201,7 +291,11 @@
 		assertTrue( s.isReadOnly( cv1 ) );
 		assertFalse( s.contains( cv2 ) );
 		s.close();
-		
+
+		assertInsertCount( 0 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -222,10 +316,15 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testPersistAndUpdateImmutable() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -241,6 +340,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -259,6 +362,10 @@
 		assertFalse( s.contains( cv2 ) );
 		s.close();
 
+		assertInsertCount( 0 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -279,10 +386,15 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testUpdateAndDeleteManagedImmutable() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -293,6 +405,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -314,10 +430,15 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testGetAndDeleteManagedImmutable() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -328,6 +449,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.get( Contract.class, c.getId() );
@@ -349,10 +474,15 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testDeleteDetachedImmutable() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -363,33 +493,26 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		s.delete( c );
-		/*
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
-		// c was loaded into s by hibernate, so it should be read-only
-		assertTrue( s.isReadOnly( c ) );
-		assertEquals( c.getCustomerName(), "gavin" );
-		assertEquals( c.getVariations().size(), 2 );
-		Iterator it = c.getVariations().iterator();
-		cv1 = (ContractVariation) it.next();
-		assertEquals( cv1.getText(), "expensive" );
-		cv2 = (ContractVariation) it.next();
-		assertEquals( cv2.getText(), "more expensive" );
-		// cv1 and cv2 were loaded into s by hibernate, so they should be read-only
-		assertTrue( s.isReadOnly( cv1 ) );
-		assertTrue( s.isReadOnly( cv2 ) );
-		s.delete(c);
-		assertEquals( s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
-		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
-		*/
+		assertNull( c );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testDeleteDetachedModifiedImmutable() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -400,35 +523,26 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c.setCustomerName( "sherman" );
 		s.delete( c );
-		/*
-		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
-		// c was loaded into s by hibernate, so it should be read-only
-		assertTrue( s.isReadOnly( c ) );
-		assertEquals( c.getCustomerName(), "gavin" );
-		assertEquals( c.getVariations().size(), 2 );
-		Iterator it = c.getVariations().iterator();
-		cv1 = (ContractVariation) it.next();
-		assertEquals( cv1.getText(), "expensive" );
-		cv2 = (ContractVariation) it.next();
-		assertEquals( cv2.getText(), "more expensive" );
-		// cv1 and cv2 were loaded into s by hibernate, so they should be read-only
-		assertTrue( s.isReadOnly( cv1 ) );
-		assertTrue( s.isReadOnly( cv2 ) );
-		s.delete(c);
-		assertEquals( s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
-		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
-		*/
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );		
 	}
 
 
 	public void testImmutableParentEntityWithUpdate() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -439,6 +553,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c.setCustomerName("foo bar");
@@ -454,6 +572,8 @@
 		assertFalse( s.isReadOnly( cv2 ) );		
 		s.close();
 
+		assertUpdateCount( 0 );
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -469,10 +589,15 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testImmutableChildEntityWithUpdate() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -483,6 +608,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		cv1 = (ContractVariation) c.getVariations().iterator().next();
@@ -498,6 +627,8 @@
 		assertFalse( s.isReadOnly( cv2 ) );
 		s.close();
 
+		assertUpdateCount( 0 );
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -513,10 +644,15 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testImmutableCollectionWithUpdate() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -527,6 +663,9 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+
 		s = openSession();
 		t = s.beginTransaction();
 		c.getVariations().add( new ContractVariation(3, c) );
@@ -542,6 +681,8 @@
 			s.close();
 		}
 
+		assertUpdateCount( 0 );
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -557,10 +698,70 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
+	public void testUnmodifiedImmutableParentEntityWithMerge() {
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
+		ContractVariation cv1 = new ContractVariation(1, c);
+		cv1.setText("expensive");
+		ContractVariation cv2 = new ContractVariation(2, c);
+		cv2.setText("more expensive");
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+		s.persist(c);
+		t.commit();
+		s.close();
+
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
+		s = openSession();
+		t = s.beginTransaction();
+		c = ( Contract ) s.merge( c );
+		// c was loaded into s by hibernate in the merge process, so it is read-only
+		assertTrue( s.isReadOnly( c ) );
+		assertTrue( Hibernate.isInitialized( c.getVariations() ) );
+		Iterator it = c.getVariations().iterator();
+		cv1 = (ContractVariation) it.next();
+		cv2 = (ContractVariation) it.next();
+		// cv1 and cv2 were loaded into s by hibernate in the merge process, so they are read-only
+		assertTrue( s.isReadOnly( cv1 ) );
+		assertTrue( s.isReadOnly( cv2 ) );
+		t.commit();
+		s.close();
+
+		assertUpdateCount( 0 );
+
+		s = openSession();
+		t = s.beginTransaction();
+		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
+		assertEquals( c.getCustomerName(), "gavin" );
+		assertEquals( c.getVariations().size(), 2 );
+		it = c.getVariations().iterator();
+		cv1 = (ContractVariation) it.next();
+		assertEquals( cv1.getText(), "expensive" );
+		cv2 = (ContractVariation) it.next();
+		assertEquals( cv2.getText(), "more expensive" );
+		s.delete(c);
+		assertEquals( s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
+		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
+		t.commit();
+		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
+	}
+
 	public void testImmutableParentEntityWithMerge() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -571,6 +772,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c.setCustomerName("foo bar");
@@ -587,6 +792,8 @@
 		t.commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -602,10 +809,16 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
+
 	}
 
 	public void testImmutableChildEntityWithMerge() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -616,6 +829,10 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		cv1 = (ContractVariation) c.getVariations().iterator().next();
@@ -633,6 +850,8 @@
 		t.commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+
 		s = openSession();
 		t = s.beginTransaction();
 		c = (Contract) s.createCriteria(Contract.class).uniqueResult();
@@ -648,10 +867,15 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );
 	}
 
 	public void testImmutableCollectionWithMerge() {
-		Contract c = new Contract("gavin", "phone");
+		clearCounts();
+
+		Contract c = new Contract( null, "gavin", "phone");
 		ContractVariation cv1 = new ContractVariation(1, c);
 		cv1.setText("expensive");
 		ContractVariation cv2 = new ContractVariation(2, c);
@@ -662,6 +886,11 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 3 );
+		assertUpdateCount( 0 );
+
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		c.getVariations().add( new ContractVariation(3, c) );
@@ -693,6 +922,28 @@
 		assertEquals( s.createCriteria(ContractVariation.class).setProjection( Projections.rowCount() ).uniqueResult(), new Long(0) );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 3 );		
 	}
+
+	protected void clearCounts() {
+		getSessions().getStatistics().clear();
+	}
+
+	protected void assertInsertCount(int expected) {
+		int inserts = ( int ) getSessions().getStatistics().getEntityInsertCount();
+		assertEquals( "unexpected insert count", expected, inserts );
+	}
+
+	protected void assertUpdateCount(int expected) {
+		int updates = ( int ) getSessions().getStatistics().getEntityUpdateCount();
+		assertEquals( "unexpected update counts", expected, updates );
+	}
+
+	protected void assertDeleteCount(int expected) {
+		int deletes = ( int ) getSessions().getStatistics().getEntityDeleteCount();
+		assertEquals( "unexpected delete counts", expected, deletes );
+	}
 }
 

Added: core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Info.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Info.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Info.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,43 @@
+//$Id: Contract.java 7222 2005-06-19 17:22:01Z oneovthafew $
+package org.hibernate.test.immutable;
+
+import java.io.Serializable;
+
+public class Info implements Serializable {
+
+	private long id;
+	private String text;
+	private Contract contract;
+
+	public Info() {
+		super();
+	}
+
+	public Info(String text) {
+		this.text = text;
+	}
+
+	public String getText() {
+		return text;
+	}
+
+	public void setText(String text) {
+		this.text = text;
+	}
+
+	public long getId() {
+		return id;
+	}
+
+	public void setId(long id) {
+		this.id = id;
+	}
+
+	public Contract getContract() {
+		return contract;
+	}
+
+	public void setContract(Contract contract ) {
+		this.contract = contract;
+	}
+}
\ No newline at end of file

Added: core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Party.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Party.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Party.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,47 @@
+//$Id: Contract.java 7222 2005-06-19 17:22:01Z oneovthafew $
+package org.hibernate.test.immutable;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class Party implements Serializable {
+
+	private long id;
+	private Contract contract;
+	private String name;
+
+	public Party() {
+		super();
+	}
+
+	public Party(String name) {
+		this.name = name;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public long getId() {
+		return id;
+	}
+
+	public void setId(long id) {
+		this.id = id;
+	}
+
+	public Contract getContract() {
+		return contract;
+	}
+
+	public void setContract(Contract contract) {
+		this.contract = contract;
+	}
+}
\ No newline at end of file

Added: core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Plan.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Plan.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/immutable/Plan.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,80 @@
+//$Id: ContractVariation.java 7222 2005-06-19 17:22:01Z oneovthafew $
+package org.hibernate.test.immutable;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+public class Plan implements Serializable {
+
+	private long id;
+	private String description;
+	private Set contracts;
+
+	public Plan() {
+		this( null );
+	}
+
+	public Plan(String description) {
+		this.description = description;
+		contracts = new HashSet();
+	}
+
+	public long getId() {
+		return id;
+	}
+
+	public void setId(long id) {
+		this.id = id;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	public Set getContracts() {
+		return contracts;
+	}
+
+	public void setContracts(Set contracts) {
+		this.contracts = contracts;
+	}
+
+	public void addContract(Contract contract) {
+		if ( ! contracts.add( contract ) ) {
+			return;
+		}
+		if ( contract.getParent() != null ) {
+			addContract( contract.getParent() );
+		}
+		contract.getPlans().add( this );
+		for ( Iterator it=contract.getSubcontracts().iterator(); it.hasNext(); ) {
+			Contract sub = ( Contract ) it.next();
+			addContract( sub );
+		}
+	}
+
+	public void removeContract(Contract contract) {
+		if ( contract.getParent() != null ) {
+			contract.getParent().getSubcontracts().remove( contract );
+			contract.setParent( null );			
+		}
+		removeSubcontracts( contract );
+		contract.getPlans().remove( this );
+		contracts.remove( contract );
+	}
+
+	public void removeSubcontracts(Contract contract) {
+		for ( Iterator it=contract.getSubcontracts().iterator(); it.hasNext(); ) {
+			Contract sub = ( Contract ) it.next();
+			removeSubcontracts( sub );
+			sub.getPlans().remove( this );
+			contracts.remove( sub );
+		}
+	}
+}
\ No newline at end of file

Added: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/AbstractReadOnlyTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/AbstractReadOnlyTest.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/AbstractReadOnlyTest.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,79 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, 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.test.readonly;
+
+import org.hibernate.CacheMode;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+import org.hibernate.junit.functional.FunctionalTestCase;
+
+/**
+ *
+ * @author Gail Badner
+ */
+public abstract class AbstractReadOnlyTest extends FunctionalTestCase {
+
+	public AbstractReadOnlyTest(String str) {
+		super(str);
+	}
+
+	public void configure(Configuration cfg) {
+		cfg.setProperty( Environment.GENERATE_STATISTICS, "true");
+		cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
+	}
+
+	public org.hibernate.classic.Session openSession() {
+		org.hibernate.classic.Session s = super.openSession();
+		s.setCacheMode( getSessionCacheMode() );
+		return s;
+	}
+
+	protected CacheMode getSessionCacheMode() {
+		return CacheMode.IGNORE;
+	}
+
+	public String getCacheConcurrencyStrategy() {
+		return null;
+	}
+
+	protected void clearCounts() {
+		getSessions().getStatistics().clear();
+	}
+
+	protected void assertInsertCount(int expected) {
+		int inserts = ( int ) getSessions().getStatistics().getEntityInsertCount();
+		assertEquals( "unexpected insert count", expected, inserts );
+	}
+
+	protected void assertUpdateCount(int expected) {
+		int updates = ( int ) getSessions().getStatistics().getEntityUpdateCount();
+		assertEquals( "unexpected update counts", expected, updates );
+	}
+
+	protected void assertDeleteCount(int expected) {
+		int deletes = ( int ) getSessions().getStatistics().getEntityDeleteCount();
+		assertEquals( "unexpected delete counts", expected, deletes );
+	}
+}
\ No newline at end of file

Copied: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Course.java (from rev 18852, core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/criteria/Course.java)
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Course.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Course.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,22 @@
+//$Id: Course.java 5686 2005-02-12 07:27:32Z steveebersole $
+package org.hibernate.test.readonly;
+
+/**
+ * @author Gavin King
+ */
+public class Course {
+	private String courseCode;
+	private String description;
+	public String getCourseCode() {
+		return courseCode;
+	}
+	public void setCourseCode(String courseCode) {
+		this.courseCode = courseCode;
+	}
+	public String getDescription() {
+		return description;
+	}
+	public void setDescription(String description) {
+		this.description = description;
+	}
+}

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/DataPoint.hbm.xml
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/DataPoint.hbm.xml	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/DataPoint.hbm.xml	2010-02-23 22:18:43 UTC (rev 18864)
@@ -13,10 +13,10 @@
 			<generator class="increment"/>
 		</id>
 		<property name="x">
-			<column name="xval" not-null="true" precision="25" scale="19" unique-key="xy"/>
+			<column name="xval" not-null="true" precision="25" scale="20" unique-key="xy"/>
 		</property>
 		<property name="y">
-			<column name="yval" not-null="true" precision="25" scale="19" unique-key="xy"/>
+			<column name="yval" not-null="true" precision="25" scale="20" unique-key="xy"/>
 		</property>
 		<property name="description"/>
 	</class>

Copied: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Enrolment.hbm.xml (from rev 18852, core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/criteria/Enrolment.hbm.xml)
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Enrolment.hbm.xml	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Enrolment.hbm.xml	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC 
+	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+	"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+<hibernate-mapping package="org.hibernate.test.readonly">
+	
+	<class name="Course">
+		<id name="courseCode">
+			<generator class="assigned"/>
+		</id>
+		<property name="description"/>
+	</class>
+	
+	<class name="Student">
+		<id name="studentNumber">
+		    <column name="studentId"/>
+			<generator class="assigned"/>
+		</id>
+		<property name="name" not-null="true"/>
+		<set name="enrolments" inverse="true" cascade="delete">
+			<key column="studentId"/>
+			<one-to-many class="Enrolment"/>
+		</set>
+        <many-to-one name="preferredCourse" column="preferredCourseCode"/>
+	</class>
+	
+	<class name="Enrolment">
+		<composite-id>
+			<key-property name="studentNumber">
+				<column name="studentId"/>
+			</key-property>
+			<key-property name="courseCode"/>
+		</composite-id>
+		<many-to-one name="student" insert="false" update="false">
+			<column name="studentId"/>
+		</many-to-one>
+		<many-to-one name="course" insert="false" update="false">
+			<column name="courseCode"/>
+		</many-to-one>
+		<property name="semester" not-null="true"/>
+		<property name="year" not-null="true"/>
+	</class>
+
+</hibernate-mapping>

Copied: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Enrolment.java (from rev 18852, core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/criteria/Enrolment.java)
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Enrolment.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Enrolment.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,63 @@
+//$Id: Enrolment.java 6970 2005-05-31 20:24:41Z oneovthafew $
+package org.hibernate.test.readonly;
+
+import java.io.Serializable;
+
+/**
+ * @author Gavin King
+ */
+public class Enrolment implements Serializable {
+	private Student student;
+	private Course course;
+	private long studentNumber;
+	private String courseCode;
+	private short year;
+	private short semester;
+	public String getCourseCode() {
+		return courseCode;
+	}
+	public void setCourseCode(String courseId) {
+		this.courseCode = courseId;
+	}
+	public long getStudentNumber() {
+		return studentNumber;
+	}
+	public void setStudentNumber(long studentId) {
+		this.studentNumber = studentId;
+	}
+	public Course getCourse() {
+		return course;
+	}
+	public void setCourse(Course course) {
+		this.course = course;
+	}
+	public Student getStudent() {
+		return student;
+	}
+	public void setStudent(Student student) {
+		this.student = student;
+	}
+	public short getSemester() {
+		return semester;
+	}
+	public void setSemester(short semester) {
+		this.semester = semester;
+	}
+	public short getYear() {
+		return year;
+	}
+	public void setYear(short year) {
+		this.year = year;
+	}
+	
+	public boolean equals(Object other) {
+		if ( !(other instanceof Enrolment) ) return false;
+		Enrolment that = (Enrolment) other;
+		return studentNumber==that.studentNumber &&
+			courseCode.equals(that.courseCode);
+	}
+	
+	public int hashCode() {
+		return courseCode.hashCode();
+	}
+}

Copied: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyCriteriaQueryTest.java (from rev 18852, core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/criteria/ReadOnlyCriteriaQueryTest.java)
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyCriteriaQueryTest.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyCriteriaQueryTest.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,1666 @@
+//$Id: CriteriaQueryTest.java 10976 2006-12-12 23:22:26Z steve.ebersole at jboss.com $
+package org.hibernate.test.readonly;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.Test;
+
+import org.hibernate.CacheMode;
+import org.hibernate.Criteria;
+import org.hibernate.FetchMode;
+import org.hibernate.Hibernate;
+import org.hibernate.ScrollableResults;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+import org.hibernate.criterion.DetachedCriteria;
+import org.hibernate.criterion.Example;
+import org.hibernate.criterion.MatchMode;
+import org.hibernate.criterion.Order;
+import org.hibernate.criterion.Projection;
+import org.hibernate.criterion.Projections;
+import org.hibernate.criterion.Property;
+import org.hibernate.criterion.Restrictions;
+import org.hibernate.criterion.Subqueries;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+import org.hibernate.proxy.HibernateProxy;
+import org.hibernate.proxy.LazyInitializer;
+import org.hibernate.test.hql.Animal;
+import org.hibernate.test.hql.Reptile;
+import org.hibernate.test.readonly.AbstractReadOnlyTest;
+import org.hibernate.transform.Transformers;
+import org.hibernate.type.Type;
+import org.hibernate.util.SerializationHelper;
+
+/**
+ * @author Gail Badner (adapted from org.hibernate.test.criteria.CriteriaQueryTest by Gavin King)
+ */
+public class ReadOnlyCriteriaQueryTest extends AbstractReadOnlyTest {
+	
+	public ReadOnlyCriteriaQueryTest(String str) {
+		super(str);
+	}
+
+	public String[] getMappings() {
+		return new String[] { "readonly/Enrolment.hbm.xml" };
+	}
+
+	public void configure(Configuration cfg) {
+		super.configure( cfg );
+		cfg.setProperty( Environment.USE_QUERY_CACHE, "true" );
+		cfg.setProperty( Environment.CACHE_REGION_PREFIX, "criteriaquerytest" );
+		cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "true" );
+		cfg.setProperty( Environment.GENERATE_STATISTICS, "true" );
+	}
+	
+	public static Test suite() {
+		return new FunctionalTestClassTestSuite( ReadOnlyCriteriaQueryTest.class );
+	}
+
+	public void testModifiableSessionDefaultCriteria() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		assertInsertCount( 4 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
+		s = openSession();
+		t = s.beginTransaction();
+		Criteria criteria = s.createCriteria( Student.class );
+		assertFalse( s.isDefaultReadOnly() );
+		assertFalse( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertFalse( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		assertFalse( s.isReadOnly( gavin ) );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), false );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		Hibernate.initialize( gavin.getPreferredCourse() );
+		assertTrue( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), false );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+
+		assertUpdateCount( 1 );
+		assertDeleteCount( 4 );
+	}
+
+	public void testModifiableSessionReadOnlyCriteria() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		Criteria criteria = s.createCriteria( Student.class ).setReadOnly( true );
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		assertTrue( s.isReadOnly( gavin ) );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), true );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		Hibernate.initialize( gavin.getPreferredCourse() );
+		assertTrue( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), true );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testModifiableSessionModifiableCriteria() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		Criteria criteria = s.createCriteria( Student.class );
+		assertFalse( s.isDefaultReadOnly() );
+		assertFalse( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		criteria.setReadOnly( false );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		assertFalse( s.isReadOnly( gavin ) );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), false );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		Hibernate.initialize( gavin.getPreferredCourse() );
+		assertTrue( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), false );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testReadOnlySessionDefaultCriteria() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		s.setDefaultReadOnly( true );
+		Criteria criteria = s.createCriteria( Student.class );
+		assertTrue( s.isDefaultReadOnly() );
+		assertFalse( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertTrue( s.isDefaultReadOnly() );
+		assertFalse( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		assertTrue( s.isReadOnly( gavin ) );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), true );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		Hibernate.initialize( gavin.getPreferredCourse() );
+		assertTrue( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), true );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertTrue( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testReadOnlySessionReadOnlyCriteria() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		s.setDefaultReadOnly( true );
+		Criteria criteria = s.createCriteria( Student.class );
+		assertTrue( s.isDefaultReadOnly() );
+		assertFalse( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		criteria.setReadOnly( true );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertTrue( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		assertTrue( s.isReadOnly( gavin ) );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), true );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		Hibernate.initialize( gavin.getPreferredCourse() );
+		assertTrue( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), true );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertTrue( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testReadOnlySessionModifiableCriteria() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		s.setDefaultReadOnly( true );
+		Criteria criteria = s.createCriteria( Student.class );
+		assertTrue( s.isDefaultReadOnly() );
+		assertFalse( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		criteria.setReadOnly( false );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertTrue( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		assertFalse( s.isReadOnly( gavin ) );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), false );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		Hibernate.initialize( gavin.getPreferredCourse() );
+		assertTrue( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), false);
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertTrue( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testReadOnlyCriteriaReturnsModifiableExistingEntity() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		assertFalse( s.isDefaultReadOnly() );
+		coursePreferred = ( Course ) s.get( Course.class, coursePreferred.getCourseCode() );
+		assertFalse( s.isReadOnly( coursePreferred ) );
+		Criteria criteria = s.createCriteria( Student.class ).setReadOnly( true );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		assertTrue( s.isReadOnly( gavin ) );
+		assertFalse( s.isReadOnly( coursePreferred ) );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testReadOnlyCriteriaReturnsExistingModifiableProxyNotInit() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		assertFalse( s.isDefaultReadOnly() );
+		coursePreferred = ( Course ) s.load( Course.class, coursePreferred.getCourseCode() );
+		assertFalse( Hibernate.isInitialized( coursePreferred ) );
+		checkProxyReadOnly( s, coursePreferred, false );
+		Criteria criteria = s.createCriteria( Student.class ).setReadOnly( true );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		assertTrue( s.isReadOnly( gavin ) );
+		assertFalse( Hibernate.isInitialized( coursePreferred ) );
+		checkProxyReadOnly( s, coursePreferred, false );
+		Hibernate.initialize( coursePreferred );
+		checkProxyReadOnly( s, coursePreferred, false );		
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testReadOnlyCriteriaReturnsExistingModifiableProxyInit() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		assertFalse( s.isDefaultReadOnly() );
+		coursePreferred = ( Course ) s.load( Course.class, coursePreferred.getCourseCode() );
+		assertFalse( Hibernate.isInitialized( coursePreferred ) );
+		checkProxyReadOnly( s, coursePreferred, false );
+		Hibernate.initialize( coursePreferred );
+		checkProxyReadOnly( s, coursePreferred, false );
+		Criteria criteria = s.createCriteria( Student.class ).setReadOnly( true );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertTrue( criteria.isReadOnly() );
+		assertTrue( s.isReadOnly( gavin ) );
+		assertTrue( Hibernate.isInitialized( coursePreferred ) );
+		checkProxyReadOnly( s, coursePreferred, false );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testModifiableCriteriaReturnsExistingReadOnlyEntity() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		assertFalse( s.isDefaultReadOnly() );
+		coursePreferred = ( Course ) s.get( Course.class, coursePreferred.getCourseCode() );
+		assertFalse( s.isReadOnly( coursePreferred ) );
+		s.setReadOnly( coursePreferred, true );
+		Criteria criteria = s.createCriteria( Student.class ).setReadOnly( false );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		assertFalse( s.isReadOnly( gavin ) );
+		assertTrue( s.isReadOnly( coursePreferred ) );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testModifiableCriteriaReturnsExistingReadOnlyProxyNotInit() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		assertFalse( s.isDefaultReadOnly() );
+		coursePreferred = ( Course ) s.load( Course.class, coursePreferred.getCourseCode() );
+		assertFalse( Hibernate.isInitialized( coursePreferred ) );
+		checkProxyReadOnly( s, coursePreferred, false );
+		s.setReadOnly( coursePreferred, true );
+		checkProxyReadOnly( s, coursePreferred, true );
+		Criteria criteria = s.createCriteria( Student.class ).setReadOnly( false );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		assertFalse( s.isReadOnly( gavin ) );
+		assertFalse( Hibernate.isInitialized( coursePreferred ) );
+		checkProxyReadOnly( s, coursePreferred, true );
+		Hibernate.initialize( coursePreferred );
+		checkProxyReadOnly( s, coursePreferred, true );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+
+	public void testModifiableCriteriaReturnsExistingReadOnlyProxyInit() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist(  coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add( enrolment );
+		s.persist( enrolment );
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		assertFalse( s.isDefaultReadOnly() );
+		coursePreferred = ( Course ) s.load( Course.class, coursePreferred.getCourseCode() );
+		assertFalse( Hibernate.isInitialized( coursePreferred ) );
+		checkProxyReadOnly( s, coursePreferred, false );
+		Hibernate.initialize( coursePreferred );
+		checkProxyReadOnly( s, coursePreferred, false );
+		s.setReadOnly( coursePreferred, true );
+		checkProxyReadOnly( s, coursePreferred, true );
+		Criteria criteria = s.createCriteria( Student.class ).setReadOnly( false );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		gavin = ( Student ) criteria.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( criteria.isReadOnlyInitialized() );
+		assertFalse( criteria.isReadOnly() );
+		assertFalse( s.isReadOnly( gavin ) );
+		assertTrue( Hibernate.isInitialized( coursePreferred ) );
+		checkProxyReadOnly( s, coursePreferred, true );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();
+	}
+	
+	public void testScrollCriteria() {
+		Session session = openSession();
+		Transaction t = session.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		session.persist(course);
+		session.flush();
+		session.clear();
+		ScrollableResults sr = session.createCriteria(Course.class).setReadOnly( true ).scroll();
+		assertTrue( sr.next() );
+		course = (Course) sr.get(0);
+		assertNotNull(course);
+		assertTrue( session.isReadOnly( course ) );
+		sr.close();
+		session.delete(course);
+		
+		t.commit();
+		session.close();
+		
+	}
+	
+	public void testSubselect() {
+
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.persist(course);
+		
+		Course coursePreferred = new Course();
+		coursePreferred.setCourseCode( "JBOSS" );
+		coursePreferred.setDescription( "JBoss" );
+		s.persist( coursePreferred );
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse( coursePreferred );
+		s.persist(gavin);
+
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add(enrolment);
+		s.persist(enrolment);
+
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		DetachedCriteria dc = DetachedCriteria.forClass(Student.class)
+			.add( Property.forName("studentNumber").eq( new Long(232) ) )
+			.setProjection( Property.forName("name") );
+		gavin = ( Student ) s.createCriteria(Student.class)
+			.add( Subqueries.exists(dc) )
+			.setReadOnly( true )
+			.uniqueResult();
+		assertFalse( s.isDefaultReadOnly() );
+		assertTrue( s.isReadOnly( gavin ) );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), true );
+		assertFalse( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		Hibernate.initialize( gavin.getPreferredCourse() );
+		assertTrue( Hibernate.isInitialized( gavin.getPreferredCourse() ) );
+		checkProxyReadOnly( s, gavin.getPreferredCourse(), true );
+		assertFalse( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		Hibernate.initialize( gavin.getEnrolments() );
+		assertTrue( Hibernate.isInitialized( gavin.getEnrolments() ) );
+		assertEquals( 1, gavin.getEnrolments().size() );
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		assertFalse( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		Hibernate.initialize( enrolment.getCourse() );
+		checkProxyReadOnly( s, enrolment.getCourse(), false );
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		DetachedCriteria dc2 = DetachedCriteria.forClass(Student.class, "st")
+			.add( Property.forName("st.studentNumber").eqProperty("e.studentNumber") )
+			.setProjection( Property.forName("name") );
+		enrolment = ( Enrolment ) s.createCriteria(Enrolment.class, "e")
+			.add( Subqueries.eq("Gavin King", dc2) )
+			.setReadOnly( true )
+			.uniqueResult();
+		assertTrue( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		Hibernate.initialize( enrolment.getCourse() );
+		assertTrue( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		assertFalse( Hibernate.isInitialized( enrolment.getStudent() ) );
+		checkProxyReadOnly( s, enrolment.getStudent(), true );
+		Hibernate.initialize( enrolment.getStudent() );
+		assertTrue( Hibernate.isInitialized( enrolment.getStudent() ) );
+		checkProxyReadOnly( s, enrolment.getStudent(), true );
+		assertFalse( Hibernate.isInitialized( enrolment.getStudent().getPreferredCourse() ) );
+		checkProxyReadOnly( s, enrolment.getStudent().getPreferredCourse(), false );
+		Hibernate.initialize( enrolment.getStudent().getPreferredCourse() );
+		assertTrue( Hibernate.isInitialized( enrolment.getStudent().getPreferredCourse() ) );
+		checkProxyReadOnly( s, enrolment.getStudent().getPreferredCourse(), false );
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		DetachedCriteria dc3 = DetachedCriteria.forClass(Student.class, "st")
+			.createCriteria("enrolments")
+				.createCriteria("course")
+					.add( Property.forName("description").eq("Hibernate Training") )
+					.setProjection( Property.forName("st.name") );
+		enrolment = ( Enrolment ) s.createCriteria(Enrolment.class, "e")
+			.add( Subqueries.eq("Gavin King", dc3) )
+			.setReadOnly( true )
+			.uniqueResult();
+		assertTrue( s.isReadOnly( enrolment ) );
+		assertFalse( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		Hibernate.initialize( enrolment.getCourse() );
+		assertTrue( Hibernate.isInitialized( enrolment.getCourse() ) );
+		checkProxyReadOnly( s, enrolment.getCourse(), true );
+		assertFalse( Hibernate.isInitialized( enrolment.getStudent() ) );
+		checkProxyReadOnly( s, enrolment.getStudent(), true );
+		Hibernate.initialize( enrolment.getStudent() );
+		assertTrue( Hibernate.isInitialized( enrolment.getStudent() ) );
+		checkProxyReadOnly( s, enrolment.getStudent(), true );
+		assertFalse( Hibernate.isInitialized( enrolment.getStudent().getPreferredCourse() ) );
+		checkProxyReadOnly( s, enrolment.getStudent().getPreferredCourse(), false );
+		Hibernate.initialize( enrolment.getStudent().getPreferredCourse() );
+		assertTrue( Hibernate.isInitialized( enrolment.getStudent().getPreferredCourse() ) );
+		checkProxyReadOnly( s, enrolment.getStudent().getPreferredCourse(), false );
+		t.commit();
+		s.close();
+
+		s = openSession();
+		t = s.beginTransaction();
+		s.delete(gavin.getPreferredCourse());
+		s.delete(gavin);
+		enrolment = ( Enrolment ) gavin.getEnrolments().iterator().next();
+		s.delete( enrolment.getCourse() );
+		s.delete(enrolment);
+		t.commit();
+		s.close();		
+	}
+	
+	public void testDetachedCriteria() {
+		
+		DetachedCriteria dc = DetachedCriteria.forClass(Student.class)
+			.add( Property.forName("name").eq("Gavin King") )
+			.addOrder( Order.asc("studentNumber") );
+		
+		byte[] bytes = SerializationHelper.serialize(dc);
+		
+		dc = (DetachedCriteria) SerializationHelper.deserialize(bytes);
+		
+		Session session = openSession();
+		Transaction t = session.beginTransaction();
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		Student bizarroGavin = new Student();
+		bizarroGavin.setName("Gavin King");
+		bizarroGavin.setStudentNumber(666);
+		session.persist(bizarroGavin);
+		session.persist(gavin);
+
+		t.commit();
+		session.close();
+
+		session = openSession();
+		t = session.beginTransaction();
+
+		List result = dc.getExecutableCriteria(session)
+			.setMaxResults(3)
+			.setReadOnly( true )
+			.list();
+
+		assertEquals( result.size(), 2 );
+		gavin = ( Student ) result.get( 0 );
+		bizarroGavin = ( Student ) result.get( 1 );
+		assertEquals( 232, gavin.getStudentNumber() );
+		assertEquals( 666, bizarroGavin.getStudentNumber() );
+		assertTrue( session.isReadOnly( gavin ) );
+		assertTrue( session.isReadOnly( bizarroGavin ) );
+
+		session.delete(gavin);
+		session.delete(bizarroGavin);
+		t.commit();
+		session.close();
+	}
+	
+		public void testTwoAliasesCache() {
+			Session s = openSession();
+			Transaction t = s.beginTransaction();
+			
+			Course course = new Course();
+			course.setCourseCode("HIB");
+			course.setDescription("Hibernate Training");
+			s.save(course);
+			
+			Student gavin = new Student();
+			gavin.setName("Gavin King");
+			gavin.setStudentNumber(666);
+			s.save(gavin);
+			
+			Student xam = new Student();
+			xam.setName("Max Rydahl Andersen");
+			xam.setStudentNumber(101);
+			s.save(xam);
+			
+			Enrolment enrolment1 = new Enrolment();
+			enrolment1.setCourse(course);
+			enrolment1.setCourseCode(course.getCourseCode());
+			enrolment1.setSemester((short) 1);
+			enrolment1.setYear((short) 1999);
+			enrolment1.setStudent(xam);
+			enrolment1.setStudentNumber(xam.getStudentNumber());
+			xam.getEnrolments().add(enrolment1);
+			s.save(enrolment1);
+			
+			Enrolment enrolment2 = new Enrolment();
+			enrolment2.setCourse(course);
+			enrolment2.setCourseCode(course.getCourseCode());
+			enrolment2.setSemester((short) 3);
+			enrolment2.setYear((short) 1998);
+			enrolment2.setStudent(gavin);
+			enrolment2.setStudentNumber(gavin.getStudentNumber());
+			gavin.getEnrolments().add(enrolment2);
+			s.save(enrolment2);
+			t.commit();
+			s.close();
+
+			s = openSession();
+			t = s.beginTransaction();
+			
+			List list = s.createCriteria(Enrolment.class)
+				.createAlias("student", "s")
+				.createAlias("course", "c")
+				.add( Restrictions.isNotEmpty("s.enrolments") )
+				.setCacheable(true)
+				.setReadOnly( true )
+				.list();
+			
+			assertEquals( list.size(), 2 );
+
+			Enrolment e = ( Enrolment ) list.get( 0 );
+			if ( e.getStudent().getStudentNumber() == xam.getStudentNumber() ) {
+				enrolment1 = e;
+				enrolment2 = ( Enrolment ) list.get( 1 );
+			}
+			else if ( e.getStudent().getStudentNumber() == xam.getStudentNumber() ) {
+				enrolment2 = e;
+				enrolment1 = ( Enrolment ) list.get( 1 );
+			}
+			else {
+				fail( "Enrolment has unknown student number: " + e.getStudent().getStudentNumber() );
+			}
+
+			assertTrue( s.isReadOnly( enrolment1 ) );
+			assertTrue( s.isReadOnly( enrolment2 ) );
+			assertTrue( s.isReadOnly( enrolment1.getCourse() ) );
+			assertTrue( s.isReadOnly( enrolment2.getCourse() ) );
+			assertSame( enrolment1.getCourse(), enrolment2.getCourse() );
+			assertTrue( s.isReadOnly( enrolment1.getStudent() ) );
+			assertTrue( s.isReadOnly( enrolment2.getStudent() ) );
+
+			t.commit();
+			s.close();
+	
+			s = openSession();
+			t = s.beginTransaction();
+			
+			list = s.createCriteria(Enrolment.class)
+				.createAlias("student", "s")
+				.createAlias("course", "c")
+				.setReadOnly( true )
+				.add( Restrictions.isNotEmpty("s.enrolments") )
+				.setCacheable(true)
+				.setReadOnly( true )
+				.list();
+		
+			assertEquals( list.size(), 2 );
+
+			e = ( Enrolment ) list.get( 0 );
+			if ( e.getStudent().getStudentNumber() == xam.getStudentNumber() ) {
+				enrolment1 = e;
+				enrolment2 = ( Enrolment ) list.get( 1 );
+			}
+			else if ( e.getStudent().getStudentNumber() == xam.getStudentNumber() ) {
+				enrolment2 = e;
+				enrolment1 = ( Enrolment ) list.get( 1 );
+			}
+			else {
+				fail( "Enrolment has unknown student number: " + e.getStudent().getStudentNumber() );
+			}
+
+			assertTrue( s.isReadOnly( enrolment1 ) );
+			assertTrue( s.isReadOnly( enrolment2 ) );
+			assertTrue( s.isReadOnly( enrolment1.getCourse() ) );
+			assertTrue( s.isReadOnly( enrolment2.getCourse() ) );
+			assertSame( enrolment1.getCourse(), enrolment2.getCourse() );
+			assertTrue( s.isReadOnly( enrolment1.getStudent() ) );
+			assertTrue( s.isReadOnly( enrolment2.getStudent() ) );
+
+			t.commit();
+			s.close();
+	
+			s = openSession();
+			t = s.beginTransaction();
+			
+			list = s.createCriteria(Enrolment.class)
+				.setReadOnly( true )
+				.createAlias("student", "s")
+				.createAlias("course", "c")
+				.add( Restrictions.isNotEmpty("s.enrolments") )
+				.setCacheable(true)
+				.list();
+			
+			assertEquals( list.size(), 2 );
+
+			e = ( Enrolment ) list.get( 0 );
+			if ( e.getStudent().getStudentNumber() == xam.getStudentNumber() ) {
+				enrolment1 = e;
+				enrolment2 = ( Enrolment ) list.get( 1 );
+			}
+			else if ( e.getStudent().getStudentNumber() == xam.getStudentNumber() ) {
+				enrolment2 = e;
+				enrolment1 = ( Enrolment ) list.get( 1 );
+			}
+			else {
+				fail( "Enrolment has unknown student number: " + e.getStudent().getStudentNumber() );
+			}
+
+			assertTrue( s.isReadOnly( enrolment1 ) );
+			assertTrue( s.isReadOnly( enrolment2 ) );
+			assertTrue( s.isReadOnly( enrolment1.getCourse() ) );
+			assertTrue( s.isReadOnly( enrolment2.getCourse() ) );
+			assertSame( enrolment1.getCourse(), enrolment2.getCourse() );
+			assertTrue( s.isReadOnly( enrolment1.getStudent() ) );
+			assertTrue( s.isReadOnly( enrolment2.getStudent() ) );
+
+			s.delete( enrolment1 );
+			s.delete( enrolment2 );
+			s.delete( enrolment1.getCourse() );
+			s.delete( enrolment1.getStudent() );
+			s.delete( enrolment2.getStudent() );
+		
+			t.commit();
+			s.close();
+	}
+
+	/*
+	public void testProjectionsUsingProperty() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+		
+		Course course = new Course();
+		course.setCourseCode("HIB");
+		course.setDescription("Hibernate Training");
+		s.save(course);
+		
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(667);
+		s.save(gavin);
+		
+		Student xam = new Student();
+		xam.setName("Max Rydahl Andersen");
+		xam.setStudentNumber(101);
+		s.save(xam);
+		
+		Enrolment enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 1);
+		enrolment.setYear((short) 1999);
+		enrolment.setStudent(xam);
+		enrolment.setStudentNumber(xam.getStudentNumber());
+		xam.getEnrolments().add(enrolment);
+		s.save(enrolment);
+		
+		enrolment = new Enrolment();
+		enrolment.setCourse(course);
+		enrolment.setCourseCode(course.getCourseCode());
+		enrolment.setSemester((short) 3);
+		enrolment.setYear((short) 1998);
+		enrolment.setStudent(gavin);
+		enrolment.setStudentNumber(gavin.getStudentNumber());
+		gavin.getEnrolments().add(enrolment);
+		s.save(enrolment);
+		
+		s.flush();
+		
+		Long count = (Long) s.createCriteria(Enrolment.class)
+			.setProjection( Property.forName("studentNumber").count().setDistinct() )
+			.uniqueResult();
+		assertEquals(count, new Long(2));
+		
+		Object object = s.createCriteria(Enrolment.class)
+			.setProjection( Projections.projectionList()
+					.add( Property.forName("studentNumber").count() )
+					.add( Property.forName("studentNumber").max() )
+					.add( Property.forName("studentNumber").min() )
+					.add( Property.forName("studentNumber").avg() )
+			)
+			.uniqueResult();
+		Object[] result = (Object[])object; 
+		
+		assertEquals(new Long(2),result[0]);
+		assertEquals(new Long(667),result[1]);
+		assertEquals(new Long(101),result[2]);
+		assertEquals(384.0, ( (Double) result[3] ).doubleValue(), 0.01);
+		
+		
+		s.createCriteria(Enrolment.class)
+		    .add( Property.forName("studentNumber").gt( new Long(665) ) )
+		    .add( Property.forName("studentNumber").lt( new Long(668) ) )
+		    .add( Property.forName("courseCode").like("HIB", MatchMode.START) )
+		    .add( Property.forName("year").eq( new Short( (short) 1999 ) ) )
+		    .addOrder( Property.forName("studentNumber").asc() )
+			.uniqueResult();
+	
+		List resultWithMaps = s.createCriteria(Enrolment.class)
+			.setProjection( Projections.projectionList()
+					.add( Property.forName("studentNumber").as("stNumber") )
+					.add( Property.forName("courseCode").as("cCode") )
+			)
+		    .add( Property.forName("studentNumber").gt( new Long(665) ) )
+		    .add( Property.forName("studentNumber").lt( new Long(668) ) )
+		    .addOrder( Property.forName("studentNumber").asc() )
+			.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
+			.list();
+		
+		assertEquals(1, resultWithMaps.size());
+		Map m1 = (Map) resultWithMaps.get(0);
+		
+		assertEquals(new Long(667), m1.get("stNumber"));
+		assertEquals(course.getCourseCode(), m1.get("cCode"));		
+
+		resultWithMaps = s.createCriteria(Enrolment.class)
+			.setProjection( Property.forName("studentNumber").as("stNumber") )
+		    .addOrder( Order.desc("stNumber") )
+			.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
+			.list();
+		
+		assertEquals(2, resultWithMaps.size());
+		Map m0 = (Map) resultWithMaps.get(0);
+		m1 = (Map) resultWithMaps.get(1);
+		
+		assertEquals(new Long(101), m1.get("stNumber"));
+		assertEquals(new Long(667), m0.get("stNumber"));
+
+	
+		List resultWithAliasedBean = s.createCriteria(Enrolment.class)
+			.createAlias("student", "st")
+			.createAlias("course", "co")
+			.setProjection( Projections.projectionList()
+					.add( Property.forName("st.name").as("studentName") )
+					.add( Property.forName("co.description").as("courseDescription") )
+			)
+			.addOrder( Order.desc("studentName") )
+			.setResultTransformer( Transformers.aliasToBean(StudentDTO.class) )
+			.list();
+		
+		assertEquals(2, resultWithAliasedBean.size());
+		
+		StudentDTO dto = (StudentDTO) resultWithAliasedBean.get(0);
+		assertNotNull(dto.getDescription());
+		assertNotNull(dto.getName());
+	
+		s.createCriteria(Student.class)
+			.add( Restrictions.like("name", "Gavin", MatchMode.START) )
+			.addOrder( Order.asc("name") )
+			.createCriteria("enrolments", "e")
+				.addOrder( Order.desc("year") )
+				.addOrder( Order.desc("semester") )
+			.createCriteria("course","c")
+				.addOrder( Order.asc("description") )
+				.setProjection( Projections.projectionList()
+					.add( Property.forName("this.name") )
+					.add( Property.forName("e.year") )
+					.add( Property.forName("e.semester") )
+					.add( Property.forName("c.courseCode") )
+					.add( Property.forName("c.description") )
+				)
+			.uniqueResult();
+			
+		Projection p1 = Projections.projectionList()
+			.add( Property.forName("studentNumber").count() )
+			.add( Property.forName("studentNumber").max() )
+			.add( Projections.rowCount() );
+		
+		Projection p2 = Projections.projectionList()
+			.add( Property.forName("studentNumber").min() )
+			.add( Property.forName("studentNumber").avg() )
+			.add( Projections.sqlProjection(
+					"1 as constOne, count(*) as countStar", 
+					new String[] { "constOne", "countStar" }, 
+					new Type[] { Hibernate.INTEGER, Hibernate.INTEGER }
+			) );
+	
+		Object[] array = (Object[]) s.createCriteria(Enrolment.class)
+			.setProjection( Projections.projectionList().add(p1).add(p2) )
+			.uniqueResult();
+		
+		assertEquals( array.length, 7 );
+		
+		List list = s.createCriteria(Enrolment.class)
+			.createAlias("student", "st")
+			.createAlias("course", "co")
+			.setProjection( Projections.projectionList()
+					.add( Property.forName("co.courseCode").group() )
+					.add( Property.forName("st.studentNumber").count().setDistinct() )
+					.add( Property.forName("year").group() )
+			)
+			.list();
+		
+		assertEquals( list.size(), 2 );
+		
+		s.delete(gavin);
+		s.delete(xam);
+		s.delete(course);
+		
+		t.commit();
+		s.close();
+	}
+
+	public void testRestrictionOnSubclassCollection() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		s.createCriteria( Reptile.class )
+				.add( Restrictions.isEmpty( "offspring" ) )
+				.list();
+
+		s.createCriteria( Reptile.class )
+				.add( Restrictions.isNotEmpty( "offspring" ) )
+				.list();
+
+		t.rollback();
+		s.close();
+	}
+
+	public void testClassProperty() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+
+		// HQL: from Animal a where a.mother.class = Reptile
+		Criteria c = s.createCriteria(Animal.class,"a")
+			.createAlias("mother","m")
+			.add( Property.forName("m.class").eq(Reptile.class) );
+		c.list();
+		t.rollback();
+		s.close();
+	}
+
+	public void testProjectedId() {
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+		s.createCriteria(Course.class).setProjection( Projections.property("courseCode") ).list();
+		s.createCriteria(Course.class).setProjection( Projections.id() ).list();
+		t.rollback();
+		s.close();
+	}
+
+	public void testSubcriteriaJoinTypes() {
+		Session session = openSession();
+		Transaction t = session.beginTransaction();
+
+		Course courseA = new Course();
+		courseA.setCourseCode("HIB-A");
+		courseA.setDescription("Hibernate Training A");
+		session.persist(courseA);
+
+		Course courseB = new Course();
+		courseB.setCourseCode("HIB-B");
+		courseB.setDescription("Hibernate Training B");
+		session.persist(courseB);
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse(courseA);
+		session.persist(gavin);
+
+		Student leonardo = new Student();
+		leonardo.setName("Leonardo Quijano");
+		leonardo.setStudentNumber(233);
+		leonardo.setPreferredCourse(courseB);
+		session.persist(leonardo);
+
+		Student johnDoe = new Student();
+		johnDoe.setName("John Doe");
+		johnDoe.setStudentNumber(235);
+		johnDoe.setPreferredCourse(null);
+		session.persist(johnDoe);
+
+		List result = session.createCriteria( Student.class )
+				.setProjection( Property.forName("preferredCourse.courseCode") )
+				.createCriteria( "preferredCourse", Criteria.LEFT_JOIN )
+						.addOrder( Order.asc( "courseCode" ) )
+						.list();
+		assertEquals( 3, result.size() );
+		// can't be sure of NULL comparison ordering aside from they should
+		// either come first or last
+		if ( result.get( 0 ) == null ) {
+			assertEquals( "HIB-A", result.get(1) );
+			assertEquals( "HIB-B", result.get(2) );
+		}
+		else {
+			assertNull( result.get(2) );
+			assertEquals( "HIB-A", result.get(0) );
+			assertEquals( "HIB-B", result.get(1) );
+		}
+
+		result = session.createCriteria( Student.class )
+				.setFetchMode( "preferredCourse", FetchMode.JOIN )
+				.createCriteria( "preferredCourse", Criteria.LEFT_JOIN )
+						.addOrder( Order.asc( "courseCode" ) )
+						.list();
+		assertEquals( 3, result.size() );
+		assertNotNull( result.get(0) );
+		assertNotNull( result.get(1) );
+		assertNotNull( result.get(2) );
+
+		result = session.createCriteria( Student.class )
+				.setFetchMode( "preferredCourse", FetchMode.JOIN )
+				.createAlias( "preferredCourse", "pc", Criteria.LEFT_JOIN )
+				.addOrder( Order.asc( "pc.courseCode" ) )
+				.list();
+		assertEquals( 3, result.size() );
+		assertNotNull( result.get(0) );
+		assertNotNull( result.get(1) );
+		assertNotNull( result.get(2) );
+
+		session.delete(gavin);
+		session.delete(leonardo);
+		session.delete(johnDoe);
+		session.delete(courseA);
+		session.delete(courseB);
+		t.commit();
+		session.close();
+	}
+	
+	public void testAliasJoinCriterion() {
+		Session session = openSession();
+		Transaction t = session.beginTransaction();
+
+		Course courseA = new Course();
+		courseA.setCourseCode("HIB-A");
+		courseA.setDescription("Hibernate Training A");
+		session.persist(courseA);
+
+		Course courseB = new Course();
+		courseB.setCourseCode("HIB-B");
+		courseB.setDescription("Hibernate Training B");
+		session.persist(courseB);
+
+		Student gavin = new Student();
+		gavin.setName("Gavin King");
+		gavin.setStudentNumber(232);
+		gavin.setPreferredCourse(courseA);
+		session.persist(gavin);
+
+		Student leonardo = new Student();
+		leonardo.setName("Leonardo Quijano");
+		leonardo.setStudentNumber(233);
+		leonardo.setPreferredCourse(courseB);
+		session.persist(leonardo);
+
+		Student johnDoe = new Student();
+		johnDoe.setName("John Doe");
+		johnDoe.setStudentNumber(235);
+		johnDoe.setPreferredCourse(null);
+		session.persist(johnDoe);
+
+		// test == on one value exists
+		List result = session.createCriteria( Student.class )
+			.createAlias( "preferredCourse", "pc", Criteria.LEFT_JOIN, Restrictions.eq("pc.courseCode", "HIB-A") )
+			.setProjection( Property.forName("pc.courseCode") )
+			.addOrder(Order.asc("pc.courseCode"))
+			.list();
+		
+		assertEquals( 3, result.size() );
+		
+		// can't be sure of NULL comparison ordering aside from they should
+		// either come first or last
+		if ( result.get( 0 ) == null ) {
+			assertNull(result.get(1));
+			assertEquals( "HIB-A", result.get(2) );
+		}
+		else {
+			assertNull( result.get(2) );
+			assertNull( result.get(1) );
+			assertEquals( "HIB-A", result.get(0) );
+		}
+		
+		// test == on non existent value
+		result = session.createCriteria( Student.class )
+		.createAlias( "preferredCourse", "pc", Criteria.LEFT_JOIN, Restrictions.eq("pc.courseCode", "HIB-R") )
+		.setProjection( Property.forName("pc.courseCode") )
+		.addOrder(Order.asc("pc.courseCode"))
+		.list();
+	
+		assertEquals( 3, result.size() );
+		assertNull( result.get(2) );
+		assertNull( result.get(1) );
+		assertNull(result.get(0) );
+		
+		// test != on one existing value
+		result = session.createCriteria( Student.class )
+		.createAlias( "preferredCourse", "pc", Criteria.LEFT_JOIN, Restrictions.ne("pc.courseCode", "HIB-A") )
+		.setProjection( Property.forName("pc.courseCode") )
+		.addOrder(Order.asc("pc.courseCode"))
+		.list();
+	
+		assertEquals( 3, result.size() );
+		// can't be sure of NULL comparison ordering aside from they should
+		// either come first or last
+		if ( result.get( 0 ) == null ) {
+			assertNull( result.get(1) );
+			assertEquals( "HIB-B", result.get(2) );
+		}
+		else {
+			assertEquals( "HIB-B", result.get(0) );
+			assertNull( result.get(1) );
+			assertNull( result.get(2) );
+		}
+
+		session.delete(gavin);
+		session.delete(leonardo);
+		session.delete(johnDoe);
+		session.delete(courseA);
+		session.delete(courseB);
+		t.commit();
+		session.close();
+	}
+	*/
+	
+	private void checkProxyReadOnly(Session s, Object proxy, boolean expectedReadOnly) {
+		assertTrue( proxy instanceof HibernateProxy );
+		LazyInitializer li = ( ( HibernateProxy ) proxy ).getHibernateLazyInitializer();
+		assertSame( s, li.getSession() );
+		assertEquals( expectedReadOnly, s.isReadOnly( proxy ) );
+		assertEquals( expectedReadOnly, li.isReadOnly() );
+		assertEquals( Hibernate.isInitialized( proxy ), ! li.isUninitialized() );
+		if ( Hibernate.isInitialized( proxy ) ) {
+			assertEquals( expectedReadOnly, s.isReadOnly( li.getImplementation() ) );
+		}
+	}
+
+}
+

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyProxyTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyProxyTest.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyProxyTest.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -52,7 +52,7 @@
  * 
  * @author Gail Badner
  */
-public class ReadOnlyProxyTest extends FunctionalTestCase {
+public class ReadOnlyProxyTest extends AbstractReadOnlyTest {
 
 	public ReadOnlyProxyTest(String str) {
 		super(str);
@@ -62,14 +62,6 @@
 		return new String[] { "readonly/DataPoint.hbm.xml", "readonly/TextHolder.hbm.xml" };
 	}
 
-	public void configure(Configuration cfg) {
-		cfg.setProperty(Environment.STATEMENT_BATCH_SIZE, "20");
-	}
-
-	public String getCacheConcurrencyStrategy() {
-		return null;
-	}
-
 	public static Test suite() {
 		return new FunctionalTestClassTestSuite( ReadOnlyProxyTest.class );
 	}

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlySessionLazyNonLazyTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlySessionLazyNonLazyTest.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlySessionLazyNonLazyTest.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -66,7 +66,7 @@
  *
  * @author Gail Badner
  */
-public class ReadOnlySessionLazyNonLazyTest extends FunctionalTestCase {
+public class ReadOnlySessionLazyNonLazyTest extends AbstractReadOnlyTest {
 
 	public ReadOnlySessionLazyNonLazyTest(String str) {
 		super(str);
@@ -76,14 +76,6 @@
 		return new String[] { "readonly/DataPoint.hbm.xml" };
 	}
 
-	public void configure(Configuration cfg) {
-		cfg.setProperty(Environment.STATEMENT_BATCH_SIZE, "20");
-	}
-
-	public String getCacheConcurrencyStrategy() {
-		return null;
-	}
-
 	public static Test suite() {
 		return new FunctionalTestClassTestSuite( ReadOnlySessionLazyNonLazyTest.class );
 	}
@@ -106,7 +98,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -195,7 +186,6 @@
 		Set expectedReadOnlyObjects = new HashSet();
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -278,7 +268,6 @@
 		DataPoint lazyDataPointOrig = ( DataPoint ) cOrig.getLazyDataPoints().iterator().next();
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -362,7 +351,6 @@
 		DataPoint lazyDataPointOrig = ( DataPoint ) cOrig.getLazyDataPoints().iterator().next();
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -449,7 +437,6 @@
 		DataPoint lazyDataPointOrig = ( DataPoint ) cOrig.getLazyDataPoints().iterator().next();
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -533,7 +520,6 @@
 		DataPoint lazyDataPointOrig = ( DataPoint ) cOrig.getLazyDataPoints().iterator().next();
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -618,7 +604,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -697,7 +682,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -767,7 +751,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -847,7 +830,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -916,7 +898,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -961,7 +942,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -1045,7 +1025,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -1118,7 +1097,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -1199,7 +1177,6 @@
 
 		Session s = openSession();
 		assertFalse( s.isDefaultReadOnly() );
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		checkContainer( cOrig, expectedInitializedObjects, expectedReadOnlyObjects, s );
@@ -1257,14 +1234,12 @@
 
 		Container cOrig = createContainer();
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		t.commit();
 		s.close();
 
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		List list = s.createQuery( "from Container c left outer join c.nonLazyInfo where c.id = :id" )
 				.setLong( "id", cOrig.getId() )
@@ -1301,14 +1276,12 @@
 
 		Container cOrig = createContainer();
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		t.commit();
 		s.close();
 
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		List list = s.createQuery( "from Container c left join fetch c.nonLazyInfo where c.id = :id" )
 				.setLong( "id", cOrig.getId() )
@@ -1348,14 +1321,12 @@
 
 		Container cOrig = createContainer();
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		s.save( cOrig );
 		t.commit();
 		s.close();
 
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		Container c = ( Container ) s.get( Container.class, cOrig.getId() );
 		assertNotNull( c );

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlySessionTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlySessionTest.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlySessionTest.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -48,7 +48,7 @@
  *
  * @author Gail Badner
  */
-public class ReadOnlySessionTest extends FunctionalTestCase {
+public class ReadOnlySessionTest extends AbstractReadOnlyTest {
 
 	public ReadOnlySessionTest(String str) {
 		super(str);
@@ -58,14 +58,6 @@
 		return new String[] { "readonly/DataPoint.hbm.xml", "readonly/TextHolder.hbm.xml" };
 	}
 
-	public void configure(Configuration cfg) {
-		cfg.setProperty(Environment.STATEMENT_BATCH_SIZE, "20");
-	}
-
-	public String getCacheConcurrencyStrategy() {
-		return null;
-	}
-
 	public static Test suite() {
 		return new FunctionalTestClassTestSuite( ReadOnlySessionTest.class );
 	}

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyTest.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyTest.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -47,7 +47,7 @@
  * @author Gavin King
  * @author Gail Badner
  */
-public class ReadOnlyTest extends FunctionalTestCase {
+public class ReadOnlyTest extends AbstractReadOnlyTest {
 	
 	public ReadOnlyTest(String str) {
 		super(str);
@@ -57,21 +57,14 @@
 		return new String[] { "readonly/DataPoint.hbm.xml", "readonly/TextHolder.hbm.xml" };
 	}
 
-	public void configure(Configuration cfg) {
-		cfg.setProperty(Environment.STATEMENT_BATCH_SIZE, "20");
-	}
-
-	public String getCacheConcurrencyStrategy() {
-		return null;
-	}
-
 	public static Test suite() {
 		return new FunctionalTestClassTestSuite( ReadOnlyTest.class );
 	}
 
 	public void testReadOnlyOnProxies() {
+		clearCounts();
+
 		Session s = openSession();
-		s.setCacheMode( CacheMode.IGNORE );
 		s.beginTransaction();
 		DataPoint dp = new DataPoint();
 		dp.setX( new BigDecimal( 0.1d ).setScale(19, BigDecimal.ROUND_DOWN) );
@@ -82,8 +75,11 @@
 		s.getTransaction().commit();
 		s.close();
 
+		assertInsertCount( 1 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		s.beginTransaction();
 		dp = ( DataPoint ) s.load( DataPoint.class, new Long( dpId ) );
 		assertFalse( "was initialized", Hibernate.isInitialized( dp ) );
@@ -96,20 +92,27 @@
 		s.getTransaction().commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+
 		s = openSession();
 		s.beginTransaction();
 		List list = s.createQuery( "from DataPoint where description = 'changed'" ).list();
 		assertEquals( "change written to database", 0, list.size() );
-		s.createQuery("delete from DataPoint").executeUpdate();
+		assertEquals( 1, s.createQuery("delete from DataPoint").executeUpdate() );
 		s.getTransaction().commit();
 		s.close();
+		
+		assertUpdateCount( 0 );
+		//deletes from Query.executeUpdate() are not tracked
+		//assertDeleteCount( 1 );
 	}
 
 	public void testReadOnlyMode() {
-		
+
+		clearCounts();
+
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
-		Transaction t = s.beginTransaction();		
+		Transaction t = s.beginTransaction();
 		for ( int i=0; i<100; i++ ) {
 			DataPoint dp = new DataPoint();
 			dp.setX( new BigDecimal(i * 0.1d).setScale(19, BigDecimal.ROUND_DOWN) );
@@ -118,9 +121,12 @@
 		}
 		t.commit();
 		s.close();
-		
+
+		assertInsertCount( 100 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		int i = 0;
 		ScrollableResults sr = s.createQuery("from DataPoint dp order by dp.x asc")
@@ -134,20 +140,27 @@
 			dp.setDescription("done!");
 		}
 		t.commit();
+
+		assertUpdateCount( 1 );
+		clearCounts();
+
 		s.clear();
 		t = s.beginTransaction();
 		List single = s.createQuery("from DataPoint where description='done!'").list();
 		assertEquals( single.size(), 1 );
-		s.createQuery("delete from DataPoint").executeUpdate();
+		assertEquals( 100, s.createQuery("delete from DataPoint").executeUpdate() );
 		t.commit();
 		s.close();
-		
+
+		assertUpdateCount( 0 );
+		//deletes from Query.executeUpdate() are not tracked
+		//assertDeleteCount( 100 );
 	}
 
 	public void testReadOnlyModeAutoFlushOnQuery() {
+		clearCounts();
 
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		DataPoint dpFirst = null;
 		for ( int i=0; i<100; i++ ) {
@@ -156,9 +169,18 @@
 			dp.setY( new BigDecimal( Math.cos( dp.getX().doubleValue() ) ).setScale(19, BigDecimal.ROUND_DOWN) );
 			s.save(dp);
 		}
+
+		assertInsertCount( 0 );
+		assertUpdateCount( 0 );
+
 		ScrollableResults sr = s.createQuery("from DataPoint dp order by dp.x asc")
 				.setReadOnly(true)
 				.scroll(ScrollMode.FORWARD_ONLY);
+
+		assertInsertCount( 100 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		while ( sr.next() ) {
 			DataPoint dp = (DataPoint) sr.get(0);
 			assertFalse( s.isReadOnly( dp ) );
@@ -167,12 +189,62 @@
 		t.commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+		assertDeleteCount( 100 );
 	}
 
+	public void testSaveReadOnlyModifyInSaveTransaction() {
+		clearCounts();
+
+		Session s = openSession();
+		Transaction t = s.beginTransaction();
+		DataPoint dp = new DataPoint();
+		dp.setDescription( "original" );
+		dp.setX( new BigDecimal(0.1d).setScale(19, BigDecimal.ROUND_DOWN) );
+		dp.setY( new BigDecimal( Math.cos( dp.getX().doubleValue() ) ).setScale(19, BigDecimal.ROUND_DOWN) );
+		s.save(dp);
+		s.setReadOnly( dp, true );
+		dp.setDescription( "different" );
+		t.commit();
+		s.close();
+
+		assertInsertCount( 1 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
+		s = openSession();
+		t = s.beginTransaction();
+		dp = ( DataPoint ) s.get( DataPoint.class, dp.getId() );
+		s.setReadOnly( dp, true );
+		assertEquals( "original", dp.getDescription() );
+		dp.setDescription( "changed" );
+		assertEquals( "changed", dp.getDescription() );
+		s.refresh( dp );
+		assertEquals( "original", dp.getDescription() );
+		dp.setDescription( "changed" );
+		assertEquals( "changed", dp.getDescription() );
+		t.commit();
+
+		assertInsertCount( 0 );
+		assertUpdateCount( 0 );
+
+		s.clear();
+		t = s.beginTransaction();
+		dp = ( DataPoint ) s.get( DataPoint.class, dp.getId() );
+		assertEquals( "original", dp.getDescription() );
+		s.delete( dp );
+		t.commit();
+		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
+		clearCounts();
+	}
+
 	public void testReadOnlyRefresh() {
+		clearCounts();
 
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		DataPoint dp = new DataPoint();
 		dp.setDescription( "original" );
@@ -182,8 +254,11 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 1 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		dp = ( DataPoint ) s.get( DataPoint.class, dp.getId() );
 		s.setReadOnly( dp, true );
@@ -196,6 +271,9 @@
 		assertEquals( "changed", dp.getDescription() );
 		t.commit();
 
+		assertInsertCount( 0 );
+		assertUpdateCount( 0 );
+
 		s.clear();
 		t = s.beginTransaction();
 		dp = ( DataPoint ) s.get( DataPoint.class, dp.getId() );
@@ -204,12 +282,15 @@
 		t.commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
+		clearCounts();
 	}
 
 	public void testReadOnlyRefreshDetached() {
+		clearCounts();
 
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		DataPoint dp = new DataPoint();
 		dp.setDescription( "original" );
@@ -219,8 +300,11 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 1 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		dp.setDescription( "changed" );
 		assertEquals( "changed", dp.getDescription() );
@@ -236,6 +320,9 @@
 		assertFalse( s.isReadOnly( dp ) );
 		t.commit();
 
+		assertInsertCount( 0 );
+		assertUpdateCount( 0 );
+
 		s.clear();
 		t = s.beginTransaction();
 		dp = ( DataPoint ) s.get( DataPoint.class, dp.getId() );
@@ -243,12 +330,16 @@
 		s.delete( dp );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
 	}
 
 	public void testReadOnlyDelete() {
 
+		clearCounts();
+
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		DataPoint dp = new DataPoint();
 		dp.setX( new BigDecimal(0.1d).setScale(19, BigDecimal.ROUND_DOWN) );
@@ -257,8 +348,11 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 1 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		dp = ( DataPoint ) s.get( DataPoint.class, dp.getId() );
 		s.setReadOnly( dp, true );
@@ -266,6 +360,9 @@
 		t.commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
+
 		s = openSession();
 		t = s.beginTransaction();
 		List list = s.createQuery("from DataPoint where description='done!'").list();
@@ -276,9 +373,9 @@
 	}
 
 	public void testReadOnlyGetModifyAndDelete() {
+		clearCounts();
 
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		DataPoint dp = new DataPoint();
 		dp.setX( new BigDecimal(0.1d).setScale(19, BigDecimal.ROUND_DOWN) );
@@ -287,8 +384,11 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 1 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		dp = ( DataPoint ) s.get( DataPoint.class, dp.getId() );
 		s.setReadOnly( dp, true );
@@ -297,6 +397,10 @@
 		t.commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
+		clearCounts();
+
 		s = openSession();
 		t = s.beginTransaction();
 		List list = s.createQuery("from DataPoint where description='done!'").list();
@@ -307,9 +411,9 @@
 	}
 
 	public void testReadOnlyModeWithExistingModifiableEntity() {
+		clearCounts();
 
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		DataPoint dp = null;
 		for ( int i=0; i<100; i++ ) {
@@ -321,8 +425,11 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 100 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		DataPoint dpLast = ( DataPoint ) s.get( DataPoint.class,  dp.getId() );
 		assertFalse( s.isReadOnly( dpLast ) );
@@ -348,18 +455,25 @@
 		}
 		t.commit();
 		s.clear();
+
+		assertInsertCount( 0 );
+		assertUpdateCount( nExpectedChanges );
+		clearCounts();
+
 		t = s.beginTransaction();
 		List list = s.createQuery("from DataPoint where description='done!'").list();
 		assertEquals( list.size(), nExpectedChanges );
-		s.createQuery("delete from DataPoint").executeUpdate();
+		assertEquals( 100, s.createQuery("delete from DataPoint").executeUpdate() );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );				
 	}
 
 	public void testModifiableModeWithExistingReadOnlyEntity() {
+		clearCounts();
 
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		DataPoint dp = null;
 		for ( int i=0; i<100; i++ ) {
@@ -371,14 +485,21 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 100 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		DataPoint dpLast = ( DataPoint ) s.get( DataPoint.class,  dp.getId() );
 		assertFalse( s.isReadOnly( dpLast ) );
 		s.setReadOnly( dpLast, true );
 		assertTrue( s.isReadOnly( dpLast ) );
+		dpLast.setDescription( "oy" );
 		int i = 0;
+
+		assertUpdateCount( 0 );
+
 		ScrollableResults sr = s.createQuery("from DataPoint dp order by dp.x asc")
 				.setReadOnly(false)
 				.scroll(ScrollMode.FORWARD_ONLY);
@@ -400,30 +521,40 @@
 		}
 		t.commit();
 		s.clear();
+
+		assertUpdateCount( nExpectedChanges );
+		clearCounts();
+
 		t = s.beginTransaction();
 		List list = s.createQuery("from DataPoint where description='done!'").list();
 		assertEquals( list.size(), nExpectedChanges );
-		s.createQuery("delete from DataPoint").executeUpdate();
+		assertEquals( 100, s.createQuery("delete from DataPoint").executeUpdate() );
 		t.commit();
 		s.close();
+
+		assertUpdateCount( 0 );		
 	}
 
 	public void testReadOnlyOnTextType() {
 		final String origText = "some huge text string";
 		final String newText = "some even bigger text string";
 
+		clearCounts();
+
 		Session s = openSession();
 		s.beginTransaction();
-		s.setCacheMode( CacheMode.IGNORE );
 		TextHolder holder = new TextHolder( origText );
 		s.save( holder );
 		Long id = holder.getId();
 		s.getTransaction().commit();
 		s.close();
 
+		assertInsertCount( 1 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		s = openSession();
 		s.beginTransaction();
-		s.setCacheMode( CacheMode.IGNORE );
 		holder = ( TextHolder ) s.get( TextHolder.class, id );
 		s.setReadOnly( holder, true );
 		holder.setTheText( newText );
@@ -431,6 +562,8 @@
 		s.getTransaction().commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+
 		s = openSession();
 		s.beginTransaction();
 		holder = ( TextHolder ) s.get( TextHolder.class, id );
@@ -438,12 +571,15 @@
 		s.delete( holder );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
 	}
 
 	public void testMergeWithReadOnlyEntity() {
+		clearCounts();
 
 		Session s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		Transaction t = s.beginTransaction();
 		DataPoint dp = new DataPoint();
 		dp.setX( new BigDecimal(0.1d).setScale(19, BigDecimal.ROUND_DOWN) );
@@ -452,10 +588,13 @@
 		t.commit();
 		s.close();
 
+		assertInsertCount( 1 );
+		assertUpdateCount( 0 );
+		clearCounts();
+
 		dp.setDescription( "description" );
 
 		s = openSession();
-		s.setCacheMode(CacheMode.IGNORE);
 		t = s.beginTransaction();
 		DataPoint dpManaged = ( DataPoint ) s.get( DataPoint.class, new Long( dp.getId() ) );
 		s.setReadOnly( dpManaged, true );
@@ -464,6 +603,8 @@
 		t.commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+
 		s = openSession();
 		t = s.beginTransaction();
 		dpManaged = ( DataPoint ) s.get( DataPoint.class, new Long( dp.getId() ) );
@@ -472,6 +613,9 @@
 		t.commit();
 		s.close();
 
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
+
 	}
 }
 

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyVersionedNodesTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyVersionedNodesTest.java	2010-02-23 21:52:59 UTC (rev 18863)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/ReadOnlyVersionedNodesTest.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -36,7 +36,7 @@
 /**
  * @author Gail Badner
  */
-public class ReadOnlyVersionedNodesTest extends FunctionalTestCase {
+public class ReadOnlyVersionedNodesTest extends AbstractReadOnlyTest {
 
 	public ReadOnlyVersionedNodesTest(String str) {
 		super( str );
@@ -46,15 +46,6 @@
 		return new String[] { "readonly/VersionedNode.hbm.xml" };
 	}
 
-	public String getCacheConcurrencyStrategy() {
-		return null;
-	}
-
-	public void configure(Configuration cfg) {
-		cfg.setProperty( Environment.GENERATE_STATISTICS, "true");
-		cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
-	}
-
 	public static Test suite() {
 		return new FunctionalTestClassTestSuite( ReadOnlyVersionedNodesTest.class );
 	}
@@ -116,15 +107,20 @@
 
 		assertUpdateCount( 1 );
 		assertInsertCount( 0 );
+		clearCounts();
 
 		s = openSession();
 		s.beginTransaction();
 		node = ( VersionedNode ) s.get( VersionedNode.class, node.getId() );
 		assertEquals( "diff-node-name", node.getName() );
 		assertEquals( 1, node.getVersion() );
+		s.setReadOnly( node, true );
 		s.delete( node );
 		s.getTransaction().commit();
 		s.close();
+		
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
 	}
 
 	public void testUpdateSetReadOnlyTwice() throws Exception {
@@ -155,9 +151,13 @@
 		node = ( VersionedNode ) s.get( VersionedNode.class, node.getId() );
 		assertEquals( "node", node.getName() );
 		assertEquals( 0, node.getVersion() );
+		s.setReadOnly( node, true );
 		s.delete( node );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
 	}
 
 	public void testUpdateSetModifiable() throws Exception {
@@ -181,15 +181,20 @@
 
 		assertUpdateCount( 1 );
 		assertInsertCount( 0 );
+		clearCounts();
 
 		s = openSession();
 		s.beginTransaction();
 		node = ( VersionedNode ) s.get( VersionedNode.class, node.getId() );
 		assertEquals( "node-name", node.getName() );
 		assertEquals( 1, node.getVersion() );
+		s.setReadOnly( node, true );
 		s.delete( node );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
 	}
 
 	public void testUpdateSetReadOnlySetModifiableFailureExpected() throws Exception {
@@ -317,6 +322,7 @@
 
 		assertUpdateCount( 0 );
 		assertInsertCount( 1 );
+		clearCounts();
 
 		s = openSession();
 		s.beginTransaction();
@@ -328,10 +334,15 @@
 		assertSame( parent, child.getParent() );
 		assertSame( child, parent.getChildren().iterator().next() );
 		assertEquals( 0, child.getVersion() );
+		s.setReadOnly( parent, true );
+		s.setReadOnly( child, true );
 		s.delete( parent );
 		s.delete( child );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 2 );
 	}
 
 	public void testMergeDetachedParentWithNewChildCommitWithReadOnlyParent() throws Exception {
@@ -357,6 +368,7 @@
 
 		assertUpdateCount( 0 );
 		assertInsertCount( 1 );
+		clearCounts();
 
 		s = openSession();
 		s.beginTransaction();
@@ -368,10 +380,15 @@
 		assertSame( parent, child.getParent() );
 		assertSame( child, parent.getChildren().iterator().next() );
 		assertEquals( 0, child.getVersion() );
+		s.setReadOnly( parent, true );
+		s.setReadOnly( child, true );
 		s.delete( parent );
 		s.delete( child );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 2 );
 	}
 
 	public void testGetParentMakeReadOnlyThenMergeDetachedParentWithNewChildC() throws Exception {
@@ -399,6 +416,7 @@
 
 		assertUpdateCount( 0 );
 		assertInsertCount( 1 );
+		clearCounts();
 
 		s = openSession();
 		s.beginTransaction();
@@ -414,9 +432,74 @@
 		s.delete( child );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 2 );
 	}
 
+	public void testMergeUnchangedDetachedParentChildren() throws Exception {
+		Session s = openSession();
+		s.beginTransaction();
+		VersionedNode parent = new VersionedNode( "parent", "parent" );
+		VersionedNode child = new VersionedNode( "child", "child");
+		parent.addChild( child );
+		s.persist( parent );
+		s.getTransaction().commit();
+		s.close();
 
+		clearCounts();
+
+		s = openSession();
+		s.beginTransaction();
+		parent = ( VersionedNode ) s.merge( parent );
+		s.getTransaction().commit();
+		s.close();
+
+		assertUpdateCount( 0 );
+		assertInsertCount( 0 );
+		clearCounts();
+
+		s = openSession();
+		s.beginTransaction();
+		VersionedNode parentGet = ( VersionedNode ) s.get( parent.getClass(), parent.getId() );
+		s.merge( parent );
+		s.getTransaction().commit();
+		s.close();
+
+		assertUpdateCount( 0 );
+		assertInsertCount( 0 );
+		clearCounts();
+
+		s = openSession();
+		s.beginTransaction();
+		VersionedNode parentLoad = ( VersionedNode ) s.load( parent.getClass(), parent.getId() );
+		s.merge( parent );
+		s.getTransaction().commit();
+		s.close();
+
+		assertUpdateCount( 0 );
+		assertInsertCount( 0 );
+		clearCounts();
+
+		s = openSession();
+		s.beginTransaction();
+		parent = ( VersionedNode ) s.get( VersionedNode.class, parent.getId() );
+		child = ( VersionedNode ) s.get( VersionedNode.class, child.getId() );
+		assertEquals( parent.getName(), "parent" );
+		assertEquals( 1, parent.getChildren().size() );
+		assertEquals( 0, parent.getVersion() );
+		assertSame( parent, child.getParent() );
+		assertSame( child, parent.getChildren().iterator().next() );
+		assertEquals( 0, child.getVersion() );
+		s.delete( parent );
+		s.delete( child );
+		s.getTransaction().commit();
+		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 2 );
+	}
+
 	public void testAddNewParentToReadOnlyChild() throws Exception {
 		Session s = openSession();
 		s.beginTransaction();
@@ -448,9 +531,13 @@
 		assertEquals( 0, child.getVersion() );
 		parent = ( VersionedNode ) s.get( VersionedNode.class, parent.getId() );
 		assertNull( parent );
+		s.setReadOnly( child, true );
 		s.delete( child );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 1 );
 	}
 
 	public void testUpdateChildWithNewParentCommitWithReadOnlyChild() throws Exception {
@@ -476,6 +563,7 @@
 
 		assertUpdateCount( 0 );
 		assertInsertCount( 1 );
+		clearCounts();
 
 		s = openSession();
 		s.beginTransaction();
@@ -487,10 +575,15 @@
 		assertNotNull( parent );
 		assertEquals( 0, parent.getChildren().size() );
 		assertEquals( 0, parent.getVersion() );
+		s.setReadOnly( parent, true );
+		s.setReadOnly( child, true );
 		s.delete( parent );
 		s.delete( child );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 2 );
 	}
 
 	public void testMergeDetachedChildWithNewParentCommitWithReadOnlyChild() throws Exception {
@@ -516,6 +609,7 @@
 
 		assertUpdateCount( 1 );
 		assertInsertCount( 1 );
+		clearCounts();
 
 		s = openSession();
 		s.beginTransaction();
@@ -527,10 +621,15 @@
 		assertNotNull( parent );
 		assertEquals( 0, parent.getChildren().size() );
 		assertEquals( 1, parent.getVersion() );	// hmmm, why is was version updated?
+		s.setReadOnly( parent, true );
+		s.setReadOnly( child, true );
 		s.delete( parent );
 		s.delete( child );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 2 );
 	}
 
 	public void testGetChildMakeReadOnlyThenMergeDetachedChildWithNewParent() throws Exception {
@@ -558,6 +657,7 @@
 
 		assertUpdateCount( 1 );
 		assertInsertCount( 1 );
+		clearCounts();
 
 		s = openSession();
 		s.beginTransaction();
@@ -569,10 +669,15 @@
 		assertNotNull( parent );
 		assertEquals( 0, parent.getChildren().size() );
 		assertEquals( 1, parent.getVersion() ); // / hmmm, why is was version updated?
+		s.setReadOnly( parent, true );
+		s.setReadOnly( child, true );
 		s.delete( parent );
 		s.delete( child );
 		s.getTransaction().commit();
 		s.close();
+
+		assertUpdateCount( 0 );
+		assertDeleteCount( 2 );
 	}
 
 	protected void cleanupTest() throws Exception {
@@ -590,23 +695,4 @@
 		s.getTransaction().commit();
 		s.close();
 	}
-
-	protected void clearCounts() {
-		getSessions().getStatistics().clear();
-	}
-
-	protected void assertInsertCount(int expected) {
-		int inserts = ( int ) getSessions().getStatistics().getEntityInsertCount();
-		assertEquals( "unexpected insert count", expected, inserts );
-	}
-
-	protected void assertUpdateCount(int expected) {
-		int updates = ( int ) getSessions().getStatistics().getEntityUpdateCount();
-		assertEquals( "unexpected update counts", expected, updates );
-	}
-
-	protected void assertDeleteCount(int expected) {
-		int deletes = ( int ) getSessions().getStatistics().getEntityDeleteCount();
-		assertEquals( "unexpected delete counts", expected, deletes );
-	}
 }

Copied: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Student.java (from rev 18852, core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/criteria/Student.java)
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Student.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/Student.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,47 @@
+//$Id: Student.java 9116 2006-01-23 21:21:01Z steveebersole $
+package org.hibernate.test.readonly;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Gavin King
+ */
+public class Student {
+	private long studentNumber;
+	private String name;
+	private Course preferredCourse;
+	private Set enrolments = new HashSet();
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public long getStudentNumber() {
+		return studentNumber;
+	}
+
+	public void setStudentNumber(long studentNumber) {
+		this.studentNumber = studentNumber;
+	}
+
+	public Course getPreferredCourse() {
+		return preferredCourse;
+	}
+
+	public void setPreferredCourse(Course preferredCourse) {
+		this.preferredCourse = preferredCourse;
+	}
+
+	public Set getEnrolments() {
+		return enrolments;
+	}
+
+	public void setEnrolments(Set employments) {
+		this.enrolments = employments;
+	}
+}

Copied: core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/StudentDTO.java (from rev 18852, core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/criteria/StudentDTO.java)
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/StudentDTO.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/readonly/StudentDTO.java	2010-02-23 22:18:43 UTC (rev 18864)
@@ -0,0 +1,26 @@
+/*
+ * Created on 28-Jan-2005
+ *
+ */
+package org.hibernate.test.readonly;
+
+/**
+ * @author max
+ *
+ */
+public class StudentDTO {
+
+	private String studentName;
+	private String courseDescription;
+
+	public StudentDTO() { }
+	
+	public String getName() {
+		return studentName;
+	}
+	
+	public String getDescription() {
+		return courseDescription;
+	}
+	
+}



More information about the hibernate-commits mailing list