diff --git a/core/src/main/java/org/hibernate/engine/StatefulPersistenceContext.java b/core/src/main/java/org/hibernate/engine/StatefulPersistenceContext.java index d2af22e426..a6ef4f95b4 100644 --- a/core/src/main/java/org/hibernate/engine/StatefulPersistenceContext.java +++ b/core/src/main/java/org/hibernate/engine/StatefulPersistenceContext.java @@ -1003,74 +1003,124 @@ public class StatefulPersistenceContext implements PersistenceContext { .append("]") .toString(); } - + /** - * Search the persistence context for an owner for the child object, - * given a collection role. If mergeMap is non-null, also - * check the detached graph being merged for a parent. + * Search this persistence context for an associated entity instance which is considered the "owner" of + * the given childEntity, and return that owner's id value. This is performed in the scenario of a + * uni-directional, non-inverse one-to-many collection (which means that the collection elements do not maintain + * a direct reference to the owner). + *
+ * As such, the processing here is basically to loop over every entity currently associated with this persistence + * context and for those of the correct entity (sub) type to extract its collection role property value and see + * if the child is contained within that collection. If so, we have found the owner; if not, we go on. + * + * Also need to account for mergeMap which acts as a local copy cache managed for the duration of a merge + * operation. It represents a map of the detached entity instances pointing to the corresponding managed instance. + * + * @param entityName The entity name for the entity type which would own the child + * @param propertyName The name of the property on the owning entity type which would name this child association. + * @param childEntity The child entity instance for which to locate the owner instance id. + * @param mergeMap A map of non-persistent instances from an on-going merge operation (possibly null). + * + * @return The id of the entityName instance which is said to own the child; null if an appropriate owner not + * located. */ - public Serializable getOwnerId(String entity, String property, Object childEntity, Map mergeMap) { - - EntityPersister persister = session.getFactory() - .getEntityPersister(entity); - final CollectionPersister collectionPersister = session.getFactory() - .getCollectionPersister(entity + '.' + property); - + public Serializable getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap) { + final String collectionRole = entityName + '.' + propertyName; + final EntityPersister persister = session.getFactory().getEntityPersister( entityName ); + final CollectionPersister collectionPersister = session.getFactory().getCollectionPersister( collectionRole ); + + // iterate all the entities currently associated with the persistence context. Iterator entities = entityEntries.entrySet().iterator(); while ( entities.hasNext() ) { - Map.Entry me = (Map.Entry) entities.next(); - EntityEntry ee = (EntityEntry) me.getValue(); - if ( persister.isSubclassEntityName( ee.getEntityName() ) ) { - Object instance = me.getKey(); + final Map.Entry me = ( Map.Entry ) entities.next(); + final EntityEntry entityEntry = ( EntityEntry ) me.getValue(); + // does this entity entry pertain to the entity persister in which we are interested (owner)? + if ( persister.isSubclassEntityName( entityEntry.getEntityName() ) ) { + final Object entityEntryInstance = me.getKey(); //check if the managed object is the parent - boolean found = isFoundInParent( - property, - childEntity, - persister, + boolean found = isFoundInParent( + propertyName, + childEntity, + persister, collectionPersister, - instance - ); + entityEntryInstance + ); - if (!found && mergeMap!=null) { + if ( !found && mergeMap != null ) { //check if the detached object being merged is the parent - Object unmergedInstance = mergeMap.get(instance); - Object unmergedChild = mergeMap.get(childEntity); - if ( unmergedInstance!=null && unmergedChild!=null ) { - found = isFoundInParent( - property, - unmergedChild, - persister, + Object unmergedInstance = mergeMap.get( entityEntryInstance ); + Object unmergedChild = mergeMap.get( childEntity ); + if ( unmergedInstance != null && unmergedChild != null ) { + found = isFoundInParent( + propertyName, + unmergedChild, + persister, collectionPersister, - unmergedInstance - ); + unmergedInstance + ); } } - + if ( found ) { - return ee.getId(); + return entityEntry.getId(); } - + } } + + // if we get here, it is possible that we have a proxy 'in the way' of the merge map resolution... + // NOTE: decided to put this here rather than in the above loop as I was nervous about the performance + // of the loop-in-loop especially considering this is far more likely the 'edge case' + if ( mergeMap != null ) { + Iterator mergeMapItr = mergeMap.entrySet().iterator(); + while ( mergeMapItr.hasNext() ) { + final Map.Entry mergeMapEntry = ( Map.Entry ) mergeMapItr.next(); + if ( mergeMapEntry.getKey() instanceof HibernateProxy ) { + final HibernateProxy proxy = ( HibernateProxy ) mergeMapEntry.getKey(); + if ( persister.isSubclassEntityName( proxy.getHibernateLazyInitializer().getEntityName() ) ) { + boolean found = isFoundInParent( + propertyName, + childEntity, + persister, + collectionPersister, + mergeMap.get( proxy ) + ); + if ( !found ) { + found = isFoundInParent( + propertyName, + mergeMap.get( childEntity ), + persister, + collectionPersister, + mergeMap.get( proxy ) + ); + } + if ( found ) { + return proxy.getHibernateLazyInitializer().getIdentifier(); + } + } + } + } + } + return null; } private boolean isFoundInParent( - String property, - Object childEntity, - EntityPersister persister, + String property, + Object childEntity, + EntityPersister persister, CollectionPersister collectionPersister, - Object potentialParent - ) { - Object collection = persister.getPropertyValue( - potentialParent, - property, - session.getEntityMode() - ); - return collection!=null && Hibernate.isInitialized(collection) && - collectionPersister.getCollectionType() - .contains(collection, childEntity, session); + Object potentialParent) { + Object collection = persister.getPropertyValue( + potentialParent, + property, + session.getEntityMode() + ); + return collection != null + && Hibernate.isInitialized( collection ) + && collectionPersister.getCollectionType().contains( collection, childEntity, session ); } /** diff --git a/core/src/main/java/org/hibernate/property/BackrefPropertyAccessor.java b/core/src/main/java/org/hibernate/property/BackrefPropertyAccessor.java index 9d0e61f8a7..21029df25b 100755 --- a/core/src/main/java/org/hibernate/property/BackrefPropertyAccessor.java +++ b/core/src/main/java/org/hibernate/property/BackrefPropertyAccessor.java @@ -1,67 +1,112 @@ -//$Id: BackrefPropertyAccessor.java 7516 2005-07-16 22:20:48Z oneovthafew $ +/* + * Copyright (c) 2007, Red Hat Middleware, LLC. All rights reserved. + * + * 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, v. 2.1. This program is distributed in the + * hope that it will be useful, but WITHOUT A 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, v.2.1 along with this + * distribution; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Red Hat Author(s): Gavin King, Steve Ebersole + */ package org.hibernate.property; +import java.io.Serializable; import java.lang.reflect.Method; import java.util.Map; -import java.io.Serializable; -import org.hibernate.HibernateException; -import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.SessionImplementor; /** - * Represents a "back-reference" to the id of a collection owner. + * Represents a "back-reference" to the id of a collection owner. A "back-reference" is pertinent in mapping scenarios + * where we have a uni-directional one-to-many association in which only the many side is mapped. In this case it is + * the collection itself which is responsible for the FK value. + * + * In this scenario, the one side has no inherent knowledge of its "owner". So we introduce a synthetic property into + * the one side to represent the association; a so-called back-reference. * * @author Gavin King + * @author Steve Ebersole */ public class BackrefPropertyAccessor implements PropertyAccessor { private final String propertyName; private final String entityName; + // cache these since they are stateless + private final BackrefSetter setter; // this one could even be static... + private final BackrefGetter getter; + /** * A placeholder for a property value, indicating that * we don't know the value of the back reference */ public static final Serializable UNKNOWN = new Serializable() { - public String toString() { return "