Author: ste...@jb... Date: 2006-05-24 17:14:56 -0400 (Wed, 24 May 2006) New Revision: 9944 Added: trunk/Hibernate3/test/org/hibernate/test/deletetransient/ trunk/Hibernate3/test/org/hibernate/test/deletetransient/Address.java trunk/Hibernate3/test/org/hibernate/test/deletetransient/DeleteTransientEntityTest.java trunk/Hibernate3/test/org/hibernate/test/deletetransient/Person.hbm.xml trunk/Hibernate3/test/org/hibernate/test/deletetransient/Person.java Modified: trunk/Hibernate3/src/org/hibernate/engine/BatchFetchQueue.java trunk/Hibernate3/src/org/hibernate/engine/Cascade.java trunk/Hibernate3/src/org/hibernate/engine/CascadingAction.java trunk/Hibernate3/src/org/hibernate/event/DeleteEventListener.java trunk/Hibernate3/src/org/hibernate/event/EventSource.java trunk/Hibernate3/src/org/hibernate/event/def/DefaultDeleteEventListener.java trunk/Hibernate3/src/org/hibernate/impl/IteratorImpl.java trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java Log: HHH-1779 : delete() on transient entity per jpa spec; HHH-1617 : BatchFetchQueue L2 cache peeking Modified: trunk/Hibernate3/src/org/hibernate/engine/BatchFetchQueue.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/engine/BatchFetchQueue.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/src/org/hibernate/engine/BatchFetchQueue.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -8,6 +8,7 @@ import org.apache.commons.collections.SequencedHashMap; import org.hibernate.EntityMode; +import org.hibernate.cache.CacheKey; import org.hibernate.collection.PersistentCollection; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -17,27 +18,27 @@ * Tracks entity and collection keys that are available for batch * fetching, and the queries which were used to load entities, which * can be re-used as a subquery for loading owned collections. - * + * * @author Gavin King */ public class BatchFetchQueue { public static final Object MARKER = new MarkerObject("MARKER"); - + // A set of entity keys that we predict we might need to load soon // TODO: this would be better as a SequencedReferenceSet, but no such beast exists! private final Map batchLoadableEntityKeys = new SequencedHashMap(8); //actually, a Set - + // The subqueries that were used to load the entity with the given key private final Map subselectsByEntityKey = new HashMap(8); //new ReferenceMap(ReferenceMap.HARD, ReferenceMap.SOFT); - + // The owning persistence context private final PersistenceContext context; - + public BatchFetchQueue(PersistenceContext context) { this.context = context; } - + public void clear() { batchLoadableEntityKeys.clear(); subselectsByEntityKey.clear(); @@ -54,26 +55,26 @@ public void clearSubselects() { subselectsByEntityKey.clear(); } - + /** - * After evicting or deleting or loading an entity, we don't + * After evicting or deleting or loading an entity, we don't * need to batch fetch it anymore, remove it from the queue * if necessary */ public void removeBatchLoadableEntityKey(EntityKey key) { if ( key.isBatchLoadable() ) batchLoadableEntityKeys.remove(key); } - + /** - * After evicting or deleting an entity, we don't need to - * know the query that was used to load it anymore (don't + * After evicting or deleting an entity, we don't need to + * know the query that was used to load it anymore (don't * call this after loading the entity, since we might still * need to load its collections) */ public void removeSubselect(EntityKey key) { subselectsByEntityKey.remove(key); } - + /** * If an EntityKey represents a batch loadable entity, add * it to the queue. @@ -83,18 +84,18 @@ } /** - * Get a batch of uninitialized collection keys for this role - * @param collectionPersister the collection role - * @param id a key that must be included + * Get a batch of uninitialized collection keys for a given role + * + * @param collectionPersister The persister for the collection role. + * @param id A key that must be included in the batch fetch * @param batchSize the maximum number of keys to return * @return an array of collection keys, of length batchSize (padded with nulls) */ public Serializable[] getCollectionBatch( - final CollectionPersister collectionPersister, - final Serializable id, + final CollectionPersister collectionPersister, + final Serializable id, final int batchSize, - final EntityMode entityMode - ) { + final EntityMode entityMode) { Serializable[] keys = new Serializable[batchSize]; keys[0] = id; int i = 1; @@ -107,37 +108,41 @@ Iterator iter = context.getCollectionEntries().entrySet().iterator(); //TODO: calling entrySet on an IdentityMap is SLOW!! while ( iter.hasNext() ) { Map.Entry me = (Map.Entry) iter.next(); - + CollectionEntry ce = (CollectionEntry) me.getValue(); PersistentCollection collection = (PersistentCollection) me.getKey(); if ( !collection.wasInitialized() && ce.getLoadedPersister() == collectionPersister ) { - - if ( checkForEnd && i == end ) return keys; //the first key found after the given key - + + if ( checkForEnd && i == end ) { + return keys; //the first key found after the given key + } + //if ( end == -1 && count > batchSize*10 ) return keys; //try out ten batches, max - - final boolean isEqual = collectionPersister.getKeyType().isEqual( - id, - ce.getLoadedKey(), - entityMode, - collectionPersister.getFactory() - ); - + + final boolean isEqual = collectionPersister.getKeyType().isEqual( + id, + ce.getLoadedKey(), + entityMode, + collectionPersister.getFactory() + ); + if ( isEqual ) { end = i; //checkForEnd = false; } - else { + else if ( !isCached( ce.getLoadedKey(), collectionPersister, entityMode ) ) { keys[i++] = ce.getLoadedKey(); //count++; } - + if ( i == batchSize ) { i = 1; //end of array, start filling again from start - if (end!=-1) checkForEnd = true; + if ( end != -1 ) { + checkForEnd = true; + } } } - + } return keys; //we ran out of keys to try } @@ -146,52 +151,79 @@ * Get a batch of unloaded identifiers for this class, using a slightly * complex algorithm that tries to grab keys registered immediately after * the given key. - * - * @param entityName The name of the persistent class - * @param id an identifier that must be included - * @param batchSize the maximum number of keys to return - * @return an array of identifiers, of length batchSize (padded with nulls) + * + * @param persister The persister for the entities being loaded. + * @param id The identifier of the entity currently demanding load. + * @param batchSize The maximum number of keys to return + * @return an array of identifiers, of length batchSize (possibly padded with nulls) */ public Serializable[] getEntityBatch( - final EntityPersister persister, - final Serializable id, - final int batchSize, - final EntityMode entityMode - ) { + final EntityPersister persister, + final Serializable id, + final int batchSize, + final EntityMode entityMode) { Serializable[] ids = new Serializable[batchSize]; ids[0] = id; //first element of array is reserved for the actual instance we are loading! int i = 1; int end = -1; boolean checkForEnd = false; - //int count = 0; + Iterator iter = batchLoadableEntityKeys.keySet().iterator(); while ( iter.hasNext() ) { - EntityKey key = (EntityKey) iter.next(); if ( key.getEntityName().equals( persister.getEntityName() ) ) { //TODO: this needn't exclude subclasses... - - if ( checkForEnd && i == end ) return ids; //the first id found after the given id - - //if ( end == -1 && count > batchSize*10 ) return ids; //try out ten batches, max - + if ( checkForEnd && i == end ) { + //the first id found after the given id + return ids; + } if ( persister.getIdentifierType().isEqual( id, key.getIdentifier(), entityMode ) ) { end = i; - //checkForEnd = false; } else { - ids[i++] = key.getIdentifier(); - //count++; + if ( !isCached( key, persister, entityMode ) ) { + ids[i++] = key.getIdentifier(); + } } - if ( i == batchSize ) { i = 1; //end of array, start filling again from start if (end!=-1) checkForEnd = true; } - } - } return ids; //we ran out of ids to try } + private boolean isCached( + EntityKey entityKey, + EntityPersister persister, + EntityMode entityMode) { + if ( persister.hasCache() ) { + CacheKey key = new CacheKey( + entityKey.getIdentifier(), + persister.getIdentifierType(), + entityKey.getEntityName(), + entityMode, + context.getSession().getFactory() + ); + return persister.getCache().getCache().get( key ) != null; + } + return false; + } + + private boolean isCached( + Serializable collectionKey, + CollectionPersister persister, + EntityMode entityMode) { + if ( persister.hasCache() ) { + CacheKey cacheKey = new CacheKey( + collectionKey, + persister.getKeyType(), + persister.getRole(), + entityMode, + context.getSession().getFactory() + ); + return persister.getCache().getCache().get( cacheKey ) != null; + } + return false; + } } Modified: trunk/Hibernate3/src/org/hibernate/engine/Cascade.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/engine/Cascade.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/src/org/hibernate/engine/Cascade.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -348,7 +348,7 @@ if ( log.isTraceEnabled() ) { log.trace("deleting orphaned entity instance: " + entityName); } - eventSource.delete(entityName, orphan, false); + eventSource.delete( entityName, orphan, false, null ); } } Modified: trunk/Hibernate3/src/org/hibernate/engine/CascadingAction.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/engine/CascadingAction.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/src/org/hibernate/engine/CascadingAction.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -3,6 +3,7 @@ import java.util.Iterator; import java.util.Map; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -42,10 +43,12 @@ public static final CascadingAction DELETE = new CascadingAction() { public void cascade(EventSource session, Object child, String entityName, Object anything, boolean isCascadeDeleteEnabled) throws HibernateException { - if ( log.isTraceEnabled() ) log.trace("cascading to delete: " + entityName); - if ( ForeignKeys.isNotTransient(entityName, child, null, session) ) { - session.delete(entityName, child, isCascadeDeleteEnabled); + if ( log.isTraceEnabled() ) { + log.trace("cascading to delete: " + entityName); } +// if ( ForeignKeys.isNotTransient(entityName, child, null, session) ) { + session.delete( entityName, child, isCascadeDeleteEnabled, ( Set ) anything ); +// } } public Iterator getCascadableChildrenIterator(EventSource session, CollectionType collectionType, Object collection) { // delete does cascade to uninitialized collections Modified: trunk/Hibernate3/src/org/hibernate/event/DeleteEventListener.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/event/DeleteEventListener.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/src/org/hibernate/event/DeleteEventListener.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -4,6 +4,7 @@ import org.hibernate.HibernateException; import java.io.Serializable; +import java.util.Set; /** * Defines the contract for handling of deletion events generated from a session. @@ -18,4 +19,6 @@ * @throws HibernateException */ public void onDelete(DeleteEvent event) throws HibernateException; + + public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateException; } Modified: trunk/Hibernate3/src/org/hibernate/event/EventSource.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/event/EventSource.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/src/org/hibernate/event/EventSource.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -3,6 +3,7 @@ import java.io.Serializable; import java.util.Map; +import java.util.Set; import org.hibernate.HibernateException; import org.hibernate.Session; @@ -57,6 +58,6 @@ /** * Cascade delete an entity instance */ - public void delete(String entityName, Object child, boolean isCascadeDeleteEnabled); + public void delete(String entityName, Object child, boolean isCascadeDeleteEnabled, Set transientEntities); } Modified: trunk/Hibernate3/src/org/hibernate/event/def/DefaultDeleteEventListener.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/event/def/DefaultDeleteEventListener.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/src/org/hibernate/event/def/DefaultDeleteEventListener.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -2,6 +2,7 @@ package org.hibernate.event.def; import java.io.Serializable; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -9,6 +10,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.TransientObjectException; +import org.hibernate.util.IdentitySet; import org.hibernate.action.EntityDeleteAction; import org.hibernate.classic.Lifecycle; import org.hibernate.engine.Cascade; @@ -44,10 +46,16 @@ * @throws HibernateException */ public void onDelete(DeleteEvent event) throws HibernateException { + onDelete( event, new IdentitySet() ); + } + + public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateException { + final EventSource source = event.getSession(); final PersistenceContext persistenceContext = source.getPersistenceContext(); Object entity = persistenceContext.unproxyAndReassociate( event.getObject() ); + EntityEntry entityEntry = persistenceContext.getEntry(entity); final EntityPersister persister; @@ -60,9 +68,17 @@ id = persister.getIdentifier( entity, source.getEntityMode() ); if ( id == null ) { - throw new TransientObjectException( - "the detached instance passed to delete() had a null identifier" - ); +// throw new TransientObjectException( +// "the detached instance passed to delete() had a null identifier" +// ); + if ( ForeignKeys.isNotTransient( persister.getEntityName(), entity, null, source ) ) { + throw new TransientObjectException( "the detached instance passed to delete() had a null identifier" ); + } + else { + deleteTransientEntity( source, entity, event.isCascadeDeleteEnabled(), persister, transientEntities ); + // EARLY EXIT!!! + return; + } } EntityKey key = new EntityKey( id, persister, source.getEntityMode() ); @@ -107,25 +123,53 @@ if ( invokeDeleteLifecycle( source, entity, persister ) ) return; - deleteEntity( source, entity, entityEntry, event.isCascadeDeleteEnabled(), persister ); + deleteEntity( source, entity, entityEntry, event.isCascadeDeleteEnabled(), persister, transientEntities ); if ( source.getFactory().getSettings().isIdentifierRollbackEnabled() ) { persister.resetIdentifier( entity, id, version, source.getEntityMode() ); } - } + /** + * We encountered a delete request on a transient instance. + * <p/> + * This is a deviation from historical Hibernate (pre-3.2) behavior to + * align with the JPA spec, which states that transient entities can be + * passed to remove operation in which case cascades still need to be + * performed. + * + * @param session The session which is the source of the event + * @param entity The entity being delete processed + * @param cascadeDeleteEnabled + * @param persister The entity persister + */ + protected void deleteTransientEntity( + EventSource session, + Object entity, + boolean cascadeDeleteEnabled, + EntityPersister persister, + Set transientEntities) { + log.info( "handling transient entity in delete processing" ); + if ( transientEntities.contains( entity ) ) { + log.trace( "already handled transient entity; skipping" ); + return; + } + transientEntities.add( entity ); + cascadeBeforeDelete( session, persister, entity, null, transientEntities ); + cascadeAfterDelete( session, persister, entity, transientEntities ); + } + protected final void deleteEntity( final EventSource session, final Object entity, final EntityEntry entityEntry, final boolean isCascadeDeleteEnabled, - final EntityPersister persister) - throws HibernateException { + final EntityPersister persister, + final Set transientEntities) throws HibernateException { if ( log.isTraceEnabled() ) { log.trace( - "deleting " + + "deleting " + MessageHelper.infoString( persister, entityEntry.getId(), session.getFactory() ) ); } @@ -143,13 +187,13 @@ else { currentState = entityEntry.getLoadedState(); } - + final Object[] deletedState = new Object[propTypes.length]; - TypeFactory.deepCopy( - currentState, - propTypes, - persister.getPropertyUpdateability(), - deletedState, + TypeFactory.deepCopy( + currentState, + propTypes, + persister.getPropertyUpdateability(), + deletedState, session ); entityEntry.setDeletedState(deletedState); @@ -166,7 +210,7 @@ persistenceContext.setEntryStatus(entityEntry, Status.DELETED); EntityKey key = new EntityKey( entityEntry.getId(), persister, session.getEntityMode() ); - cascadeBeforeDelete(session, persister, entity, entityEntry); + cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities ); new ForeignKeys.Nullifier(entity, true, false, session) .nullifyTransientReferences( entityEntry.getDeletedState(), propTypes ); @@ -175,19 +219,19 @@ // Ensures that containing deletions happen before sub-deletions session.getActionQueue().addAction( - new EntityDeleteAction( - entityEntry.getId(), - deletedState, - version, - entity, - persister, - isCascadeDeleteEnabled, - session + new EntityDeleteAction( + entityEntry.getId(), + deletedState, + version, + entity, + persister, + isCascadeDeleteEnabled, + session ) ); - - cascadeAfterDelete(session, persister, entity); - + + cascadeAfterDelete( session, persister, entity, transientEntities ); + // the entry will be removed after the flush, and will no longer // override the stale snapshot // This is now handled by removeEntity() in EntityDeleteAction @@ -210,15 +254,16 @@ EventSource session, EntityPersister persister, Object entity, - EntityEntry entityEntry) throws HibernateException { + EntityEntry entityEntry, + Set transientEntities) throws HibernateException { CacheMode cacheMode = session.getCacheMode(); session.setCacheMode(CacheMode.GET); session.getPersistenceContext().incrementCascadeLevel(); try { // cascade-delete to collections BEFORE the collection owner is deleted - new Cascade(CascadingAction.DELETE, Cascade.AFTER_INSERT_BEFORE_DELETE, session) - .cascade(persister, entity); + new Cascade( CascadingAction.DELETE, Cascade.AFTER_INSERT_BEFORE_DELETE, session ) + .cascade( persister, entity, transientEntities ); } finally { session.getPersistenceContext().decrementCascadeLevel(); @@ -229,15 +274,16 @@ protected void cascadeAfterDelete( EventSource session, EntityPersister persister, - Object entity) throws HibernateException { + Object entity, + Set transientEntities) throws HibernateException { CacheMode cacheMode = session.getCacheMode(); session.setCacheMode(CacheMode.GET); session.getPersistenceContext().incrementCascadeLevel(); try { // cascade-delete to many-to-one AFTER the parent was deleted - new Cascade(CascadingAction.DELETE, Cascade.BEFORE_INSERT_AFTER_DELETE, session) - .cascade(persister, entity); + new Cascade( CascadingAction.DELETE, Cascade.BEFORE_INSERT_AFTER_DELETE, session ) + .cascade( persister, entity, transientEntities ); } finally { session.getPersistenceContext().decrementCascadeLevel(); Modified: trunk/Hibernate3/src/org/hibernate/impl/IteratorImpl.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/impl/IteratorImpl.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/src/org/hibernate/impl/IteratorImpl.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -144,7 +144,8 @@ session.delete( ( (EntityType) types[0] ).getAssociatedEntityName(), currentResult, - false + false, + null ); } Modified: trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -756,8 +756,8 @@ /** * Delete a persistent object */ - public void delete(String entityName, Object object, boolean isCascadeDeleteEnabled) throws HibernateException { - fireDelete( new DeleteEvent(entityName, object, isCascadeDeleteEnabled, this) ); + public void delete(String entityName, Object object, boolean isCascadeDeleteEnabled, Set transientEntities) throws HibernateException { + fireDelete( new DeleteEvent( entityName, object, isCascadeDeleteEnabled, this ), transientEntities ); } private void fireDelete(DeleteEvent event) { @@ -769,7 +769,16 @@ } } + private void fireDelete(DeleteEvent event, Set transientEntities) { + errorIfClosed(); + checkTransactionSynchStatus(); + DeleteEventListener[] deleteEventListener = listeners.getDeleteEventListeners(); + for ( int i = 0; i < deleteEventListener.length; i++ ) { + deleteEventListener[i].onDelete( event, transientEntities ); + } + } + // load()/get() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public void load(Object object, Serializable id) throws HibernateException { Added: trunk/Hibernate3/test/org/hibernate/test/deletetransient/Address.java =================================================================== --- trunk/Hibernate3/test/org/hibernate/test/deletetransient/Address.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/test/org/hibernate/test/deletetransient/Address.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -0,0 +1,34 @@ +package org.hibernate.test.deletetransient; + +/** + * todo: describe Address + * + * @author Steve Ebersole + */ +public class Address { + private Long id; + private String info; + + public Address() { + } + + public Address(String info) { + this.info = info; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getInfo() { + return info; + } + + public void setInfo(String info) { + this.info = info; + } +} Added: trunk/Hibernate3/test/org/hibernate/test/deletetransient/DeleteTransientEntityTest.java =================================================================== --- trunk/Hibernate3/test/org/hibernate/test/deletetransient/DeleteTransientEntityTest.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/test/org/hibernate/test/deletetransient/DeleteTransientEntityTest.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -0,0 +1,101 @@ +package org.hibernate.test.deletetransient; + +import org.hibernate.test.TestCase; +import org.hibernate.Session; +import org.hibernate.Transaction; + +/** + * todo: describe DeleteTransientEntityTest + * + * @author Steve Ebersole + */ +public class DeleteTransientEntityTest extends TestCase { + public DeleteTransientEntityTest(String name) { + super( name ); + } + + protected String[] getMappings() { + return new String[] { "deletetransient/Person.hbm.xml" }; + } + + public void testTransientEntityDeletionNoCascades() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + s.delete( new Address() ); + t.commit(); + s.close(); + } + + public void testTransientEntityDeletionCascadingToTransientAssociation() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + Person p = new Person(); + p.getAddresses().add( new Address() ); + s.delete( p ); + t.commit(); + s.close(); + } + + public void testTransientEntityDeleteCascadingToCircularity() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + Person p1 = new Person(); + Person p2 = new Person(); + p1.getFriends().add( p2 ); + p2.getFriends().add( p1 ); + s.delete( p1 ); + t.commit(); + s.close(); + } + + public void testTransientEntityDeletionCascadingToDetachedAssociation() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + Address address = new Address(); + address.setInfo( "123 Main St." ); + s.save( address ); + t.commit(); + s.close(); + + s = openSession(); + t = s.beginTransaction(); + Person p = new Person(); + p.getAddresses().add( address ); + s.delete( p ); + t.commit(); + s.close(); + + s = openSession(); + t = s.beginTransaction(); + Long count = ( Long ) s.createQuery( "select count(*) from Address" ).list().get( 0 ); + assertEquals( "delete not cascaded properly across transient entity", 0, count.longValue() ); + t.commit(); + s.close(); + } + + public void testTransientEntityDeletionCascadingToPersistentAssociation() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + Address address = new Address(); + address.setInfo( "123 Main St." ); + s.save( address ); + t.commit(); + s.close(); + + s = openSession(); + t = s.beginTransaction(); + address = ( Address ) s.get( Address.class, address.getId() ); + Person p = new Person(); + p.getAddresses().add( address ); + s.delete( p ); + t.commit(); + s.close(); + + s = openSession(); + t = s.beginTransaction(); + Long count = ( Long ) s.createQuery( "select count(*) from Address" ).list().get( 0 ); + assertEquals( "delete not cascaded properly across transient entity", 0, count.longValue() ); + t.commit(); + s.close(); + } +} Added: trunk/Hibernate3/test/org/hibernate/test/deletetransient/Person.hbm.xml =================================================================== --- trunk/Hibernate3/test/org/hibernate/test/deletetransient/Person.hbm.xml 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/test/org/hibernate/test/deletetransient/Person.hbm.xml 2006-05-24 21:14:56 UTC (rev 9944) @@ -0,0 +1,34 @@ +<?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.deletetransient"> + + <class name="Person" table="T_PERSON"> + <id name="id" type="long"> + <generator class="increment"/> + </id> + <property name="name" type="string"/> + <set name="addresses" lazy="true" inverse="false" cascade="all"> + <key column="PERSON_ID"/> + <one-to-many class="Address"/> + </set> + <bag name="friends" lazy="true" inverse="false" cascade="all" table="T_FRIENDS"> + <key column="FRIEND_ID_1"/> + <many-to-many class="Person" column="FRIEND_ID_2"/> + </bag> + </class> + + <class name="Address" table="T_ADDRESS"> + <id name="id" type="long"> + <generator class="increment"/> + </id> + <property name="info" type="string"/> + </class> + +</hibernate-mapping> Added: trunk/Hibernate3/test/org/hibernate/test/deletetransient/Person.java =================================================================== --- trunk/Hibernate3/test/org/hibernate/test/deletetransient/Person.java 2006-05-24 13:34:55 UTC (rev 9943) +++ trunk/Hibernate3/test/org/hibernate/test/deletetransient/Person.java 2006-05-24 21:14:56 UTC (rev 9944) @@ -0,0 +1,57 @@ +package org.hibernate.test.deletetransient; + +import java.util.Set; +import java.util.HashSet; +import java.util.Collection; +import java.util.ArrayList; + +/** + * todo: describe Person + * + * @author Steve Ebersole + */ +public class Person { + private Long id; + private String name; + private Set addresses = new HashSet(); + private Collection friends = new ArrayList(); + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getAddresses() { + return addresses; + } + + public void setAddresses(Set addresses) { + this.addresses = addresses; + } + + public Collection getFriends() { + return friends; + } + + public void setFriends(Collection friends) { + this.friends = friends; + } +} |