From 460cbf8d9605ae5e135896e9b77a07ce1ce68cfe Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 20 May 2010 05:37:43 +0000 Subject: [PATCH] HHH-2277 - bidirectional both lazy=false fetch=join lead to infinite loop git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@19563 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../java/org/hibernate/impl/SessionImpl.java | 8 +- .../loader/AbstractEntityJoinWalker.java | 36 ++-- .../java/org/hibernate/loader/JoinWalker.java | 165 +++++++++--------- .../java/org/hibernate/loader/Loader.java | 110 ++++++++++-- .../loader/OuterJoinableAssociation.java | 47 +++-- .../org/hibernate/loader/PropertyPath.java | 90 ++++++++++ .../collection/BasicCollectionJoinWalker.java | 21 +-- .../collection/OneToManyJoinWalker.java | 12 +- .../loader/criteria/CriteriaJoinWalker.java | 25 ++- .../loader/entity/EntityJoinWalker.java | 155 +++++++++++++++- .../hibernate/loader/entity/EntityLoader.java | 20 ++- 11 files changed, 501 insertions(+), 188 deletions(-) create mode 100644 core/src/main/java/org/hibernate/loader/PropertyPath.java diff --git a/core/src/main/java/org/hibernate/impl/SessionImpl.java b/core/src/main/java/org/hibernate/impl/SessionImpl.java index 990080cbb7..7b1b2327a0 100644 --- a/core/src/main/java/org/hibernate/impl/SessionImpl.java +++ b/core/src/main/java/org/hibernate/impl/SessionImpl.java @@ -1022,9 +1022,11 @@ public final class SessionImpl extends AbstractSessionImpl public Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) throws HibernateException { // todo : remove - LoadEventListener.LoadType type = nullable ? - LoadEventListener.INTERNAL_LOAD_NULLABLE : - eager ? LoadEventListener.INTERNAL_LOAD_EAGER : LoadEventListener.INTERNAL_LOAD_LAZY; + LoadEventListener.LoadType type = nullable + ? LoadEventListener.INTERNAL_LOAD_NULLABLE + : eager + ? LoadEventListener.INTERNAL_LOAD_EAGER + : LoadEventListener.INTERNAL_LOAD_LAZY; LoadEvent event = new LoadEvent(id, entityName, true, this); fireLoad(event, type); if ( !nullable ) { diff --git a/core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java b/core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java index 9a4ce64804..432fd0e3bc 100755 --- a/core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java +++ b/core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.Iterator; import org.hibernate.FetchMode; -import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.LockOptions; import org.hibernate.engine.CascadeStyle; @@ -42,7 +41,6 @@ import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.sql.JoinFragment; import org.hibernate.sql.Select; import org.hibernate.type.AssociationType; -import org.hibernate.util.CollectionHelper; /** * Abstract walker for walkers which begin at an entity (criteria @@ -76,23 +74,20 @@ public abstract class AbstractEntityJoinWalker extends JoinWalker { final String whereString, final String orderByString, final LockOptions lockOptions) throws MappingException { + initAll( whereString, orderByString, lockOptions, AssociationInitCallback.NO_CALLBACK ); + } + + protected final void initAll( + final String whereString, + final String orderByString, + final LockOptions lockOptions, + final AssociationInitCallback callback) throws MappingException { walkEntityTree( persister, getAlias() ); List allAssociations = new ArrayList(); - allAssociations.addAll(associations); - allAssociations.add( - new OuterJoinableAssociation( - persister.getEntityType(), - null, - null, - alias, - JoinFragment.LEFT_OUTER_JOIN, - null, - getFactory(), - CollectionHelper.EMPTY_MAP - ) - ); - initPersisters(allAssociations, lockOptions); - initStatementString( whereString, orderByString, lockOptions); + allAssociations.addAll( associations ); + allAssociations.add( OuterJoinableAssociation.createRoot( persister.getEntityType(), alias, getFactory() ) ); + initPersisters( allAssociations, lockOptions, callback ); + initStatementString( whereString, orderByString, lockOptions ); } protected final void initProjection( @@ -162,17 +157,18 @@ public abstract class AbstractEntityJoinWalker extends JoinWalker { return isJoinedFetchEnabledInMapping( config, type ); } - protected final boolean isJoinFetchEnabledByProfile(OuterJoinLoadable persister, String path, int propertyNumber) { + protected final boolean isJoinFetchEnabledByProfile(OuterJoinLoadable persister, PropertyPath path, int propertyNumber) { if ( !getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { // perf optimization return false; } // ugh, this stuff has to be made easier... + final String fullPath = path.getFullPath(); String rootPropertyName = persister.getSubclassPropertyName( propertyNumber ); - int pos = path.lastIndexOf( rootPropertyName ); + int pos = fullPath.lastIndexOf( rootPropertyName ); String relativePropertyPath = pos >= 0 - ? path.substring( pos ) + ? fullPath.substring( pos ) : rootPropertyName; String fetchRole = persister.getEntityName() + "." + relativePropertyPath; diff --git a/core/src/main/java/org/hibernate/loader/JoinWalker.java b/core/src/main/java/org/hibernate/loader/JoinWalker.java index a383cdc71b..4b7ae5478f 100755 --- a/core/src/main/java/org/hibernate/loader/JoinWalker.java +++ b/core/src/main/java/org/hibernate/loader/JoinWalker.java @@ -40,8 +40,6 @@ import org.hibernate.engine.CascadeStyle; import org.hibernate.engine.JoinHelper; import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.LoadQueryInfluencers; -import org.hibernate.engine.profile.FetchProfile; -import org.hibernate.engine.profile.Fetch; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.EntityPersister; @@ -93,7 +91,8 @@ public class JoinWalker { this.loadQueryInfluencers = loadQueryInfluencers; } - + + public String[] getCollectionSuffixes() { return collectionSuffixes; } @@ -194,7 +193,7 @@ public class JoinWalker { final AssociationType type, final String[] aliasedLhsColumns, final String alias, - final String path, + final PropertyPath path, int currentDepth, final int joinType) throws MappingException { if ( joinType >= 0 ) { @@ -209,7 +208,7 @@ public class JoinWalker { } } - protected String getWithClause(String path) { + protected String getWithClause(PropertyPath path) { return ""; } @@ -221,7 +220,7 @@ public class JoinWalker { final AssociationType type, final String[] aliasedLhsColumns, final String alias, - String path, + final PropertyPath path, final int currentDepth, final int joinType) throws MappingException { @@ -236,6 +235,7 @@ public class JoinWalker { // only need to worry about restrictions (and not say adding more // joins) OuterJoinableAssociation assoc = new OuterJoinableAssociation( + path, type, alias, aliasedLhsColumns, @@ -245,7 +245,7 @@ public class JoinWalker { getFactory(), loadQueryInfluencers.getEnabledFilters() ); - assoc.validateJoin( path ); + assoc.validateJoin( path.getFullPath() ); associations.add( assoc ); int nextDepth = currentDepth + 1; @@ -277,7 +277,7 @@ public class JoinWalker { * Walk the association tree for an entity, adding associations which should * be join fetched to the {@link #associations} inst var. This form is the * entry point into the walking for a given entity, starting the recursive - * calls into {@link #walkEntityTree(OuterJoinLoadable, String, String, int)}. + * calls into {@link #walkEntityTree(org.hibernate.persister.entity.OuterJoinLoadable, String, PropertyPath ,int)}. * * @param persister The persister representing the entity to be walked. * @param alias The (root) alias to use for this entity/persister. @@ -286,15 +286,14 @@ public class JoinWalker { protected final void walkEntityTree( OuterJoinLoadable persister, String alias) throws MappingException { - walkEntityTree( persister, alias, "", 0 ); + walkEntityTree( persister, alias, new PropertyPath(), 0 ); } /** * For a collection role, return a list of associations to be fetched by outerjoin */ - protected final void walkCollectionTree(QueryableCollection persister, String alias) - throws MappingException { - walkCollectionTree(persister, alias, "", 0); + protected final void walkCollectionTree(QueryableCollection persister, String alias) throws MappingException { + walkCollectionTree( persister, alias, new PropertyPath(), 0 ); //TODO: when this is the entry point, we should use an INNER_JOIN for fetching the many-to-many elements! } @@ -302,11 +301,10 @@ public class JoinWalker { * For a collection role, return a list of associations to be fetched by outerjoin */ private void walkCollectionTree( - final QueryableCollection persister, - final String alias, - final String path, - final int currentDepth) - throws MappingException { + final QueryableCollection persister, + final String alias, + final PropertyPath path, + final int currentDepth) throws MappingException { if ( persister.isOneToMany() ) { walkEntityTree( @@ -338,7 +336,7 @@ public class JoinWalker { !useInnerJoin, currentDepth - 1, null //operations which cascade as far as the collection also cascade to collection elements - ); + ); addAssociationToJoinTreeIfNecessary( associationType, aliasedLhsColumns, @@ -382,7 +380,7 @@ public class JoinWalker { final OuterJoinLoadable persister, final int propertyNumber, final String alias, - final String path, + final PropertyPath path, final boolean nullable, final int currentDepth) throws MappingException { String[] aliasedLhsColumns = JoinHelper.getAliasedLHSColumnNames( @@ -393,10 +391,10 @@ public class JoinWalker { ); String lhsTable = JoinHelper.getLHSTableName(associationType, propertyNumber, persister); - String subpath = subPath( path, persister.getSubclassPropertyName(propertyNumber) ); + PropertyPath subPath = path.append( persister.getSubclassPropertyName(propertyNumber) ); int joinType = getJoinType( persister, - subpath, + subPath, propertyNumber, associationType, persister.getFetchMode( propertyNumber ), @@ -410,7 +408,7 @@ public class JoinWalker { associationType, aliasedLhsColumns, alias, - subpath, + subPath, currentDepth, joinType ); @@ -436,7 +434,7 @@ public class JoinWalker { */ protected int getJoinType( OuterJoinLoadable persister, - final String path, + final PropertyPath path, int propertyNumber, AssociationType associationType, FetchMode metadataFetchMode, @@ -476,7 +474,7 @@ public class JoinWalker { protected int getJoinType( AssociationType associationType, FetchMode config, - String path, + PropertyPath path, String lhsTable, String[] lhsColumns, boolean nullable, @@ -498,18 +496,18 @@ public class JoinWalker { * Walk the association tree for an entity, adding associations which should * be join fetched to the {@link #associations} inst var. This form is the * entry point into the walking for a given entity, starting the recursive - * calls into {@link #walkEntityTree(OuterJoinLoadable, String, String, int)}. + * calls into {@link #walkEntityTree(org.hibernate.persister.entity.OuterJoinLoadable, String, PropertyPath ,int)}. * * @param persister The persister representing the entity to be walked. * @param alias The (root) alias to use for this entity/persister. - * @param path todo this seems to be rooted at the *root* persister + * @param path The property path to the entity being walked * @param currentDepth The current join depth * @throws org.hibernate.MappingException ??? */ private void walkEntityTree( final OuterJoinLoadable persister, final String alias, - final String path, + final PropertyPath path, final int currentDepth) throws MappingException { int n = persister.countSubclassProperties(); for ( int i = 0; i < n; i++ ) { @@ -527,13 +525,13 @@ public class JoinWalker { } else if ( type.isComponentType() ) { walkComponentTree( - ( AbstractComponentType ) type, - i, - 0, - persister, - alias, - subPath( path, persister.getSubclassPropertyName(i) ), - currentDepth + ( AbstractComponentType ) type, + i, + 0, + persister, + alias, + path.append( persister.getSubclassPropertyName(i) ), + currentDepth ); } } @@ -559,7 +557,7 @@ public class JoinWalker { int begin, final OuterJoinLoadable persister, final String alias, - final String path, + final PropertyPath path, final int currentDepth) throws MappingException { Type[] types = componentType.getSubtypes(); String[] propertyNames = componentType.getPropertyNames(); @@ -574,11 +572,11 @@ public class JoinWalker { ); String lhsTable = JoinHelper.getLHSTableName(associationType, propertyNumber, persister); - String subpath = subPath( path, propertyNames[i] ); + final PropertyPath subPath = path.append( propertyNames[i] ); final boolean[] propertyNullability = componentType.getPropertyNullability(); final int joinType = getJoinType( persister, - subpath, + subPath, propertyNumber, associationType, componentType.getFetchMode(i), @@ -592,21 +590,21 @@ public class JoinWalker { associationType, aliasedLhsColumns, alias, - subpath, + subPath, currentDepth, joinType ); } else if ( types[i].isComponentType() ) { - String subpath = subPath( path, propertyNames[i] ); + final PropertyPath subPath = path.append( propertyNames[i] ); walkComponentTree( ( AbstractComponentType ) types[i], propertyNumber, begin, persister, alias, - subpath, + subPath, currentDepth ); } @@ -623,7 +621,7 @@ public class JoinWalker { final String[] cols, final QueryableCollection persister, final String alias, - final String path, + final PropertyPath path, final int currentDepth) throws MappingException { Type[] types = compositeType.getSubtypes(); @@ -640,12 +638,12 @@ public class JoinWalker { // (or even a property-ref) in a composite-element: String[] aliasedLhsColumns = StringHelper.qualify(alias, lhsColumns); - String subpath = subPath( path, propertyNames[i] ); + final PropertyPath subPath = path.append( propertyNames[i] ); final boolean[] propertyNullability = compositeType.getPropertyNullability(); final int joinType = getJoinType( associationType, compositeType.getFetchMode(i), - subpath, + subPath, persister.getTableName(), lhsColumns, propertyNullability==null || propertyNullability[i], @@ -656,19 +654,19 @@ public class JoinWalker { associationType, aliasedLhsColumns, alias, - subpath, + subPath, currentDepth, joinType ); } else if ( types[i].isComponentType() ) { - String subpath = subPath( path, propertyNames[i] ); + final PropertyPath subPath = path.append( propertyNames[i] ); walkCompositeElementTree( (AbstractComponentType) types[i], lhsColumns, persister, alias, - subpath, + subPath, currentDepth ); } @@ -677,18 +675,6 @@ public class JoinWalker { } - /** - * Extend the path by the given property name - */ - private static String subPath(String path, String property) { - if ( path==null || path.length()==0) { - return property; - } - else { - return StringHelper.qualify(path, property); - } - } - /** * Use an inner join if it is a non-null association and this * is the "first" join in a series @@ -746,10 +732,7 @@ public class JoinWalker { return type.isEntityType() && isJoinedFetchEnabledInMapping(config, type) ; } - protected String generateTableAlias( - final int n, - final String path, - final Joinable joinable) { + protected String generateTableAlias(final int n, final PropertyPath path, final Joinable joinable) { return StringHelper.generateAlias( joinable.getName(), n ); } @@ -761,10 +744,7 @@ public class JoinWalker { * Used to detect circularities in the joined graph, note that * this method is side-effecty */ - protected boolean isDuplicateAssociation( - final String foreignKeyTable, - final String[] foreignKeyColumns - ) { + protected boolean isDuplicateAssociation(final String foreignKeyTable, final String[] foreignKeyColumns) { AssociationKey associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable); return !visitedAssociationKeys.add( associationKey ); } @@ -773,11 +753,7 @@ public class JoinWalker { * Used to detect circularities in the joined graph, note that * this method is side-effecty */ - protected boolean isDuplicateAssociation( - final String lhsTable, - final String[] lhsColumnNames, - final AssociationType type - ) { + protected boolean isDuplicateAssociation(final String lhsTable, final String[] lhsColumnNames, final AssociationType type) { final String foreignKeyTable; final String[] foreignKeyColumns; if ( type.getForeignKeyDirection()==ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT ) { @@ -815,20 +791,23 @@ public class JoinWalker { * Should we join this association? */ protected boolean isJoinable( - final int joinType, - final Set visitedAssociationKeys, - final String lhsTable, - final String[] lhsColumnNames, - final AssociationType type, - final int depth - ) { - if (joinType<0) return false; - - if (joinType==JoinFragment.INNER_JOIN) return true; + final int joinType, + final Set visitedAssociationKeys, + final String lhsTable, + final String[] lhsColumnNames, + final AssociationType type, + final int depth) { + + if ( joinType < 0 ) { + return false; + } + if ( joinType == JoinFragment.INNER_JOIN ) { + return true; + } + Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth(); - final boolean tooDeep = maxFetchDepth!=null && - depth >= maxFetchDepth.intValue(); + final boolean tooDeep = maxFetchDepth!=null && depth >= maxFetchDepth.intValue(); return !tooDeep && !isDuplicateAssociation(lhsTable, lhsColumnNames, type); } @@ -985,8 +964,22 @@ public class JoinWalker { initPersisters( associations, new LockOptions(lockMode)); } + protected static interface AssociationInitCallback { + public static final AssociationInitCallback NO_CALLBACK = new AssociationInitCallback() { + public void associationProcessed(OuterJoinableAssociation oja, int position) { + } + }; + + public void associationProcessed(OuterJoinableAssociation oja, int position); + } protected void initPersisters(final List associations, final LockOptions lockOptions) throws MappingException { - + initPersisters( associations, lockOptions, AssociationInitCallback.NO_CALLBACK ); + } + + protected void initPersisters( + final List associations, + final LockOptions lockOptions, + final AssociationInitCallback callback) throws MappingException { final int joins = countEntityPersisters(associations); final int collections = countCollectionPersisters(associations); @@ -1013,6 +1006,7 @@ public class JoinWalker { aliases[i] = oj.getRHSAlias(); owners[i] = oj.getOwner(associations); ownerAssociationTypes[i] = (EntityType) oj.getJoinableType(); + callback.associationProcessed( oj, i ); i++; } @@ -1029,11 +1023,12 @@ public class JoinWalker { if ( collPersister.isOneToMany() ) { persisters[i] = (Loadable) collPersister.getElementPersister(); aliases[i] = oj.getRHSAlias(); + callback.associationProcessed( oj, i ); i++; } } } - + if ( ArrayHelper.isAllNegative(owners) ) owners = null; if ( collectionOwners!=null && ArrayHelper.isAllNegative(collectionOwners) ) { collectionOwners = null; diff --git a/core/src/main/java/org/hibernate/loader/Loader.java b/core/src/main/java/org/hibernate/loader/Loader.java index 005b684209..4c59315400 100644 --- a/core/src/main/java/org/hibernate/loader/Loader.java +++ b/core/src/main/java/org/hibernate/loader/Loader.java @@ -173,6 +173,10 @@ public abstract class Loader { return null; } + protected int[][] getCompositeKeyManyToOneTargetIndices() { + return null; + } + /** * What lock options does this load entities with? * @@ -599,19 +603,7 @@ public abstract class Loader { final Loadable[] persisters = getEntityPersisters(); final int entitySpan = persisters.length; - - for ( int i = 0; i < entitySpan; i++ ) { - keys[i] = getKeyFromResultSet( - i, - persisters[i], - i == entitySpan - 1 ? - queryParameters.getOptionalId() : - null, - resultSet, - session - ); - //TODO: the i==entitySpan-1 bit depends upon subclass implementation (very bad) - } + extractKeysFromResultSet( persisters, queryParameters, resultSet, session, keys, lockModesArray, hydratedObjects ); registerNonExists( keys, persisters, session ); @@ -648,6 +640,98 @@ public abstract class Loader { } + protected void extractKeysFromResultSet( + Loadable[] persisters, + QueryParameters queryParameters, + ResultSet resultSet, + SessionImplementor session, + EntityKey[] keys, + LockMode[] lockModes, + List hydratedObjects) throws SQLException { + final int entitySpan = persisters.length; + + final int numberOfPersistersToProcess; + final Serializable optionalId = queryParameters.getOptionalId(); + if ( isSingleRowLoader() && optionalId != null ) { + keys[ entitySpan - 1 ] = new EntityKey( optionalId, persisters[ entitySpan - 1 ], session.getEntityMode() ); + // skip the last persister below... + numberOfPersistersToProcess = entitySpan - 1; + } + else { + numberOfPersistersToProcess = entitySpan; + } + + final Object[] hydratedKeyState = new Object[numberOfPersistersToProcess]; + + for ( int i = 0; i < numberOfPersistersToProcess; i++ ) { + final Type idType = persisters[i].getIdentifierType(); + hydratedKeyState[i] = idType.hydrate( resultSet, getEntityAliases()[i].getSuffixedKeyAliases(), session, null ); + } + + for ( int i = 0; i < numberOfPersistersToProcess; i++ ) { + final Type idType = persisters[i].getIdentifierType(); + if ( idType.isComponentType() && getCompositeKeyManyToOneTargetIndices() != null ) { + // we may need to force resolve any key-many-to-one(s) + int[] keyManyToOneTargetIndices = getCompositeKeyManyToOneTargetIndices()[i]; + // todo : better solution is to order the index processing based on target indices + // that would account for multiple levels whereas this scheme does not + if ( keyManyToOneTargetIndices != null ) { + for ( int targetIndex : keyManyToOneTargetIndices ) { + if ( targetIndex < numberOfPersistersToProcess ) { + final Type targetIdType = persisters[targetIndex].getIdentifierType(); + final Serializable targetId = (Serializable) targetIdType.resolve( + hydratedKeyState[targetIndex], + session, + null + ); + // todo : need a way to signal that this key is resolved and its data resolved + keys[targetIndex] = new EntityKey( targetId, persisters[targetIndex], session.getEntityMode() ); + } + + // this part copied from #getRow, this section could be refactored out + Object object = session.getEntityUsingInterceptor( keys[targetIndex] ); + if ( object != null ) { + //its already loaded so don't need to hydrate it + instanceAlreadyLoaded( + resultSet, + targetIndex, + persisters[targetIndex], + keys[targetIndex], + object, + lockModes[targetIndex], + session + ); + } + else { + object = instanceNotYetLoaded( + resultSet, + targetIndex, + persisters[targetIndex], + getEntityAliases()[targetIndex].getRowIdAlias(), + keys[targetIndex], + lockModes[targetIndex], + getOptionalObjectKey( queryParameters, session ), + queryParameters.getOptionalObject(), + hydratedObjects, + session + ); + } + } + } + } + final Serializable resolvedId = (Serializable) idType.resolve( hydratedKeyState[i], session, null ); + keys[i] = resolvedId == null ? null : new EntityKey( resolvedId, persisters[i], session.getEntityMode() ); + } + } + + private Serializable determineResultId(SessionImplementor session, Serializable optionalId, Type idType, Serializable resolvedId) { + final boolean idIsResultId = optionalId != null + && resolvedId != null + && idType.isEqual( optionalId, resolvedId, session.getEntityMode(), factory ); + final Serializable resultId = idIsResultId ? optionalId : resolvedId; + return resultId; + } + protected void applyPostLoadLocks(Object[] row, LockMode[] lockModesArray, SessionImplementor session) { } diff --git a/core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java b/core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java index 5e22410cbb..2392577e09 100644 --- a/core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java +++ b/core/src/main/java/org/hibernate/loader/OuterJoinableAssociation.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2010, Red Hat Inc. 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. + * distributed under license by Red Hat Inc. * * 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 @@ -20,7 +20,6 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.loader; @@ -35,6 +34,7 @@ import org.hibernate.persister.entity.Joinable; import org.hibernate.sql.JoinFragment; import org.hibernate.type.AssociationType; import org.hibernate.type.EntityType; +import org.hibernate.util.CollectionHelper; /** * Part of the Hibernate SQL rendering internals. This class represents @@ -43,6 +43,7 @@ import org.hibernate.type.EntityType; * @author Gavin King */ public final class OuterJoinableAssociation { + private final PropertyPath propertyPath; private final AssociationType joinableType; private final Joinable joinable; private final String lhsAlias; // belong to other persister @@ -53,7 +54,25 @@ public final class OuterJoinableAssociation { private final String on; private final Map enabledFilters; + public static OuterJoinableAssociation createRoot( + AssociationType joinableType, + String alias, + SessionFactoryImplementor factory) { + return new OuterJoinableAssociation( + new PropertyPath(), + joinableType, + null, + null, + alias, + JoinFragment.LEFT_OUTER_JOIN, + null, + factory, + CollectionHelper.EMPTY_MAP + ); + } + public OuterJoinableAssociation( + PropertyPath propertyPath, AssociationType joinableType, String lhsAlias, String[] lhsColumns, @@ -62,6 +81,7 @@ public final class OuterJoinableAssociation { String withClause, SessionFactoryImplementor factory, Map enabledFilters) throws MappingException { + this.propertyPath = propertyPath; this.joinableType = joinableType; this.lhsAlias = lhsAlias; this.lhsColumns = lhsColumns; @@ -74,14 +94,26 @@ public final class OuterJoinableAssociation { this.enabledFilters = enabledFilters; // needed later for many-to-many/filter application } + public PropertyPath getPropertyPath() { + return propertyPath; + } + public int getJoinType() { return joinType; } + public String getLhsAlias() { + return lhsAlias; + } + public String getRHSAlias() { return rhsAlias; } + public String getRhsAlias() { + return rhsAlias; + } + private boolean isOneToOne() { if ( joinableType.isEntityType() ) { EntityType etype = (EntityType) joinableType; @@ -90,7 +122,6 @@ public final class OuterJoinableAssociation { else { return false; } - } public AssociationType getJoinableType() { @@ -150,12 +181,8 @@ public final class OuterJoinableAssociation { } public void validateJoin(String path) throws MappingException { - if ( - rhsColumns==null || - lhsColumns==null || - lhsColumns.length!=rhsColumns.length || - lhsColumns.length==0 - ) { + if ( rhsColumns==null || lhsColumns==null + || lhsColumns.length!=rhsColumns.length || lhsColumns.length==0 ) { throw new MappingException("invalid join columns for association: " + path); } } diff --git a/core/src/main/java/org/hibernate/loader/PropertyPath.java b/core/src/main/java/org/hibernate/loader/PropertyPath.java new file mode 100644 index 0000000000..0b0dfedad8 --- /dev/null +++ b/core/src/main/java/org/hibernate/loader/PropertyPath.java @@ -0,0 +1,90 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2010, Red Hat Inc. 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 Inc. + * + * 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.loader; + +import org.hibernate.util.StringHelper; + +/** +* TODO : javadoc +* +* @author Steve Ebersole +*/ +public class PropertyPath { + private final PropertyPath parent; + private final String property; + private final String fullPath; + + public PropertyPath(PropertyPath parent, String property) { + this.parent = parent; + this.property = property; + + final String prefix; + if ( parent != null ) { + final String resolvedParent = parent.getFullPath(); + if ( StringHelper.isEmpty( resolvedParent ) ) { + prefix = ""; + } + else { + prefix = resolvedParent + '.'; + } + } + else { + prefix = ""; + } + this.fullPath = prefix + property; + } + + public PropertyPath(String property) { + this( null, property ); + } + + public PropertyPath() { + this( "" ); + } + + public PropertyPath append(String property) { + return new PropertyPath( this, property ); + } + + public PropertyPath getParent() { + return parent; + } + + public String getProperty() { + return property; + } + + public String getFullPath() { + return fullPath; + } + + public boolean isRoot() { + return parent == null && StringHelper.isEmpty( property ); + } + + @Override + public String toString() { + return getClass().getSimpleName() + '[' + fullPath + ']'; + } +} diff --git a/core/src/main/java/org/hibernate/loader/collection/BasicCollectionJoinWalker.java b/core/src/main/java/org/hibernate/loader/collection/BasicCollectionJoinWalker.java index 336fbc168e..c3a4d9246b 100755 --- a/core/src/main/java/org/hibernate/loader/collection/BasicCollectionJoinWalker.java +++ b/core/src/main/java/org/hibernate/loader/collection/BasicCollectionJoinWalker.java @@ -27,8 +27,6 @@ package org.hibernate.loader.collection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Set; import org.hibernate.FetchMode; import org.hibernate.LockMode; @@ -38,12 +36,12 @@ import org.hibernate.engine.LoadQueryInfluencers; import org.hibernate.engine.CascadeStyle; import org.hibernate.loader.BasicLoader; import org.hibernate.loader.OuterJoinableAssociation; +import org.hibernate.loader.PropertyPath; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.sql.JoinFragment; import org.hibernate.sql.Select; import org.hibernate.type.AssociationType; -import org.hibernate.util.CollectionHelper; import org.hibernate.util.StringHelper; /** @@ -73,18 +71,7 @@ public class BasicCollectionJoinWalker extends CollectionJoinWalker { List allAssociations = new ArrayList(); allAssociations.addAll(associations); - allAssociations.add( - new OuterJoinableAssociation( - collectionPersister.getCollectionType(), - null, - null, - alias, - JoinFragment.LEFT_OUTER_JOIN, - null, - getFactory(), - CollectionHelper.EMPTY_MAP - ) - ); + allAssociations.add( OuterJoinableAssociation.createRoot( collectionPersister.getCollectionType(), alias, getFactory() ) ); initPersisters(allAssociations, LockMode.NONE); initStatementString(alias, batchSize, subquery); } @@ -155,7 +142,7 @@ public class BasicCollectionJoinWalker extends CollectionJoinWalker { protected int getJoinType( OuterJoinLoadable persister, - String path, + PropertyPath path, int propertyNumber, AssociationType associationType, FetchMode metadataFetchMode, @@ -177,7 +164,7 @@ public class BasicCollectionJoinWalker extends CollectionJoinWalker { currentDepth ); //we can use an inner join for the many-to-many - if ( joinType==JoinFragment.LEFT_OUTER_JOIN && "".equals(path) ) { + if ( joinType==JoinFragment.LEFT_OUTER_JOIN && path.isRoot() ) { joinType=JoinFragment.INNER_JOIN; } return joinType; diff --git a/core/src/main/java/org/hibernate/loader/collection/OneToManyJoinWalker.java b/core/src/main/java/org/hibernate/loader/collection/OneToManyJoinWalker.java index 325ed07885..d9df8467e8 100755 --- a/core/src/main/java/org/hibernate/loader/collection/OneToManyJoinWalker.java +++ b/core/src/main/java/org/hibernate/loader/collection/OneToManyJoinWalker.java @@ -79,17 +79,7 @@ public class OneToManyJoinWalker extends CollectionJoinWalker { List allAssociations = new ArrayList(); allAssociations.addAll(associations); - allAssociations.add( new OuterJoinableAssociation( - oneToManyPersister.getCollectionType(), - null, - null, - alias, - JoinFragment.LEFT_OUTER_JOIN, - null, - getFactory(), - CollectionHelper.EMPTY_MAP - ) ); - + allAssociations.add( OuterJoinableAssociation.createRoot( oneToManyPersister.getCollectionType(), alias, getFactory() ) ); initPersisters(allAssociations, LockMode.NONE); initStatementString(elementPersister, alias, batchSize, subquery); } diff --git a/core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java b/core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java index aee55d180a..633527652e 100755 --- a/core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java +++ b/core/src/main/java/org/hibernate/loader/criteria/CriteriaJoinWalker.java @@ -30,7 +30,6 @@ import java.util.Set; import org.hibernate.Criteria; import org.hibernate.FetchMode; -import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.LockOptions; import org.hibernate.engine.CascadeStyle; @@ -38,12 +37,12 @@ import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.LoadQueryInfluencers; import org.hibernate.impl.CriteriaImpl; import org.hibernate.loader.AbstractEntityJoinWalker; +import org.hibernate.loader.PropertyPath; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.persister.entity.Queryable; import org.hibernate.type.AssociationType; import org.hibernate.type.Type; -import org.hibernate.type.TypeFactory; import org.hibernate.util.ArrayHelper; /** @@ -121,7 +120,7 @@ public class CriteriaJoinWalker extends AbstractEntityJoinWalker { protected int getJoinType( OuterJoinLoadable persister, - final String path, + final PropertyPath path, int propertyNumber, AssociationType associationType, FetchMode metadataFetchMode, @@ -130,15 +129,15 @@ public class CriteriaJoinWalker extends AbstractEntityJoinWalker { String[] lhsColumns, final boolean nullable, final int currentDepth) throws MappingException { - if ( translator.isJoin( path ) ) { - return translator.getJoinType( path ); + if ( translator.isJoin( path.getFullPath() ) ) { + return translator.getJoinType( path.getFullPath() ); } else { if ( translator.hasProjection() ) { return -1; } else { - FetchMode fetchMode = translator.getRootCriteria().getFetchMode( path ); + FetchMode fetchMode = translator.getRootCriteria().getFetchMode( path.getFullPath() ); if ( isDefaultFetchMode( fetchMode ) ) { if ( isJoinFetchEnabledByProfile( persister, path, propertyNumber ) ) { return getJoinType( nullable, currentDepth ); @@ -174,14 +173,14 @@ public class CriteriaJoinWalker extends AbstractEntityJoinWalker { protected int getJoinType( AssociationType associationType, FetchMode config, - String path, + PropertyPath path, String lhsTable, String[] lhsColumns, boolean nullable, int currentDepth, CascadeStyle cascadeStyle) throws MappingException { - return ( translator.isJoin( path ) ? - translator.getJoinType( path ) : + return ( translator.isJoin( path.getFullPath() ) ? + translator.getJoinType( path.getFullPath() ) : super.getJoinType( associationType, config, @@ -208,9 +207,9 @@ public class CriteriaJoinWalker extends AbstractEntityJoinWalker { ( (Queryable) getPersister() ).filterFragment( getAlias(), getLoadQueryInfluencers().getEnabledFilters() ); } - protected String generateTableAlias(int n, String path, Joinable joinable) { + protected String generateTableAlias(int n, PropertyPath path, Joinable joinable) { if ( joinable.consumesEntityAlias() ) { - final Criteria subcriteria = translator.getCriteria(path); + final Criteria subcriteria = translator.getCriteria( path.getFullPath() ); String sqlAlias = subcriteria==null ? null : translator.getSQLAlias(subcriteria); if (sqlAlias!=null) { userAliasList.add( subcriteria.getAlias() ); //alias may be null @@ -235,8 +234,8 @@ public class CriteriaJoinWalker extends AbstractEntityJoinWalker { return "criteria query"; } - protected String getWithClause(String path) { - return translator.getWithClause(path); + protected String getWithClause(PropertyPath path) { + return translator.getWithClause( path.getFullPath() ); } } diff --git a/core/src/main/java/org/hibernate/loader/entity/EntityJoinWalker.java b/core/src/main/java/org/hibernate/loader/entity/EntityJoinWalker.java index 8c373252f0..25276fd775 100755 --- a/core/src/main/java/org/hibernate/loader/entity/EntityJoinWalker.java +++ b/core/src/main/java/org/hibernate/loader/entity/EntityJoinWalker.java @@ -24,18 +24,27 @@ */ package org.hibernate.loader.entity; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import org.hibernate.FetchMode; import org.hibernate.LockMode; -import org.hibernate.MappingException; import org.hibernate.LockOptions; +import org.hibernate.MappingException; import org.hibernate.engine.CascadeStyle; -import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.LoadQueryInfluencers; +import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.loader.AbstractEntityJoinWalker; +import org.hibernate.loader.OuterJoinableAssociation; +import org.hibernate.loader.PropertyPath; +import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.type.AssociationType; +import org.hibernate.type.ComponentType; +import org.hibernate.type.EntityType; +import org.hibernate.type.Type; /** * A walker for loaders that fetch entities @@ -46,13 +55,14 @@ import org.hibernate.type.AssociationType; public class EntityJoinWalker extends AbstractEntityJoinWalker { private final LockOptions lockOptions = new LockOptions(); + private final int[][] compositeKeyManyToOneTargetIndices; public EntityJoinWalker( OuterJoinLoadable persister, String[] uniqueKey, int batchSize, LockMode lockMode, - SessionFactoryImplementor factory, + final SessionFactoryImplementor factory, LoadQueryInfluencers loadQueryInfluencers) throws MappingException { super( persister, factory, loadQueryInfluencers ); @@ -62,7 +72,9 @@ public class EntityJoinWalker extends AbstractEntityJoinWalker { //include the discriminator and class-level where, but not filters .append( persister.filterFragment( getAlias(), Collections.EMPTY_MAP ) ); - initAll( whereCondition.toString(), "", lockOptions); + AssociationInitCallbackImpl callback = new AssociationInitCallbackImpl( factory ); + initAll( whereCondition.toString(), "", lockOptions, callback ); + this.compositeKeyManyToOneTargetIndices = callback.resolve(); } public EntityJoinWalker( @@ -79,12 +91,14 @@ public class EntityJoinWalker extends AbstractEntityJoinWalker { //include the discriminator and class-level where, but not filters .append( persister.filterFragment( getAlias(), Collections.EMPTY_MAP ) ); - initAll( whereCondition.toString(), "", lockOptions); + AssociationInitCallbackImpl callback = new AssociationInitCallbackImpl( factory ); + initAll( whereCondition.toString(), "", lockOptions, callback ); + this.compositeKeyManyToOneTargetIndices = callback.resolve(); } protected int getJoinType( OuterJoinLoadable persister, - String path, + PropertyPath path, int propertyNumber, AssociationType associationType, FetchMode metadataFetchMode, @@ -116,5 +130,132 @@ public class EntityJoinWalker extends AbstractEntityJoinWalker { public String getComment() { return "load " + getPersister().getEntityName(); } - + + public int[][] getCompositeKeyManyToOneTargetIndices() { + return compositeKeyManyToOneTargetIndices; + } + + private static class AssociationInitCallbackImpl implements AssociationInitCallback { + private final SessionFactoryImplementor factory; + private final HashMap associationsByAlias + = new HashMap(); + private final HashMap positionsByAlias = new HashMap(); + private final ArrayList aliasesForAssociationsWithCompositesIds + = new ArrayList(); + + public AssociationInitCallbackImpl(SessionFactoryImplementor factory) { + this.factory = factory; + } + + public void associationProcessed(OuterJoinableAssociation oja, int position) { + associationsByAlias.put( oja.getRhsAlias(), oja ); + positionsByAlias.put( oja.getRhsAlias(), position ); + EntityPersister entityPersister = null; + if ( oja.getJoinableType().isCollectionType() ) { + entityPersister = ( ( QueryableCollection) oja.getJoinable() ).getElementPersister(); + } + else if ( oja.getJoinableType().isEntityType() ) { + entityPersister = ( EntityPersister ) oja.getJoinable(); + } + if ( entityPersister != null + && entityPersister.getIdentifierType().isComponentType() + && ! entityPersister.getEntityMetamodel().getIdentifierProperty().isEmbedded() + && hasAssociation( (ComponentType) entityPersister.getIdentifierType() ) ) { + aliasesForAssociationsWithCompositesIds.add( oja.getRhsAlias() ); + } + } + + private boolean hasAssociation(ComponentType componentType) { + int i = 0; + for ( Type subType : componentType.getSubtypes() ) { + if ( subType.isEntityType() ) { + return true; + } + else if ( subType.isComponentType() && hasAssociation( ( (ComponentType) subType ) ) ) { + return true; + } + i++; + } + return false; + } + + public int[][] resolve() { + int[][] compositeKeyManyToOneTargetIndices = null; + for ( final String aliasWithCompositeId : aliasesForAssociationsWithCompositesIds ) { + final OuterJoinableAssociation joinWithCompositeId = associationsByAlias.get( aliasWithCompositeId ); + final ArrayList keyManyToOneTargetIndices = new ArrayList(); + // for each association with a composite id containing key-many-to-one(s), find the bidirectional side of + // each key-many-to-one (if exists) to see if it is eager as well. If so, we need to track the indices + EntityPersister entityPersister = null; + if ( joinWithCompositeId.getJoinableType().isCollectionType() ) { + entityPersister = ( ( QueryableCollection) joinWithCompositeId.getJoinable() ).getElementPersister(); + } + else if ( joinWithCompositeId.getJoinableType().isEntityType() ) { + entityPersister = ( EntityPersister ) joinWithCompositeId.getJoinable(); + } + + findKeyManyToOneTargetIndices( + keyManyToOneTargetIndices, + joinWithCompositeId, + (ComponentType) entityPersister.getIdentifierType() + ); + + if ( ! keyManyToOneTargetIndices.isEmpty() ) { + if ( compositeKeyManyToOneTargetIndices == null ) { + compositeKeyManyToOneTargetIndices = new int[ associationsByAlias.size() ][]; + } + int position = positionsByAlias.get( aliasWithCompositeId ); + compositeKeyManyToOneTargetIndices[position] = new int[ keyManyToOneTargetIndices.size() ]; + int i = 0; + for ( int index : keyManyToOneTargetIndices ) { + compositeKeyManyToOneTargetIndices[position][i] = index; + i++; + } + } + } + return compositeKeyManyToOneTargetIndices; + } + + private void findKeyManyToOneTargetIndices( + ArrayList keyManyToOneTargetIndices, + OuterJoinableAssociation joinWithCompositeId, + ComponentType componentType) { + for ( Type subType : componentType.getSubtypes() ) { + if ( subType.isEntityType() ) { + Integer index = locateKeyManyToOneTargetIndex( joinWithCompositeId, (EntityType) subType ); + if ( index != null ) { + keyManyToOneTargetIndices.add( index ); + } + } + else if ( subType.isComponentType() ) { + findKeyManyToOneTargetIndices( + keyManyToOneTargetIndices, + joinWithCompositeId, + (ComponentType) subType + ); + } + } + } + + private Integer locateKeyManyToOneTargetIndex(OuterJoinableAssociation joinWithCompositeId, EntityType keyManyToOneType) { + // the lhs (if one) is a likely candidate + if ( joinWithCompositeId.getLhsAlias() != null ) { + final OuterJoinableAssociation lhs = associationsByAlias.get( joinWithCompositeId.getLhsAlias() ); + if ( keyManyToOneType.getAssociatedEntityName( factory ).equals( lhs.getJoinableType().getAssociatedEntityName( factory ) ) ) { + return positionsByAlias.get( lhs.getRhsAlias() ); + } + } + // otherwise, seek out OuterJoinableAssociation which are RHS of given OuterJoinableAssociation + // (joinWithCompositeId) + for ( OuterJoinableAssociation oja : associationsByAlias.values() ) { + if ( oja.getLhsAlias() != null && oja.getLhsAlias().equals( joinWithCompositeId.getRhsAlias() ) ) { + if ( keyManyToOneType.equals( oja.getJoinableType() ) ) { + return positionsByAlias.get( oja.getLhsAlias() ); + } + } + } + return null; + } + } + } \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/loader/entity/EntityLoader.java b/core/src/main/java/org/hibernate/loader/entity/EntityLoader.java index 03cc98a795..fee30dcf13 100644 --- a/core/src/main/java/org/hibernate/loader/entity/EntityLoader.java +++ b/core/src/main/java/org/hibernate/loader/entity/EntityLoader.java @@ -24,14 +24,12 @@ */ package org.hibernate.loader.entity; -import org.hibernate.HibernateException; import org.hibernate.LockMode; -import org.hibernate.MappingException; import org.hibernate.LockOptions; +import org.hibernate.MappingException; +import org.hibernate.engine.LoadQueryInfluencers; import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.SessionImplementor; -import org.hibernate.engine.LoadQueryInfluencers; -import org.hibernate.loader.JoinWalker; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.type.Type; @@ -46,6 +44,7 @@ import org.hibernate.type.Type; public class EntityLoader extends AbstractEntityLoader { private final boolean batchLoader; + private final int[][] compositeKeyManyToOneTargetIndices; public EntityLoader( OuterJoinLoadable persister, @@ -107,7 +106,7 @@ public class EntityLoader extends AbstractEntityLoader { LoadQueryInfluencers loadQueryInfluencers) throws MappingException { super( persister, uniqueKeyType, factory, loadQueryInfluencers ); - JoinWalker walker = new EntityJoinWalker( + EntityJoinWalker walker = new EntityJoinWalker( persister, uniqueKey, batchSize, @@ -116,7 +115,7 @@ public class EntityLoader extends AbstractEntityLoader { loadQueryInfluencers ); initFromWalker( walker ); - + this.compositeKeyManyToOneTargetIndices = walker.getCompositeKeyManyToOneTargetIndices(); postInstantiate(); batchLoader = batchSize > 1; @@ -134,7 +133,7 @@ public class EntityLoader extends AbstractEntityLoader { LoadQueryInfluencers loadQueryInfluencers) throws MappingException { super( persister, uniqueKeyType, factory, loadQueryInfluencers ); - JoinWalker walker = new EntityJoinWalker( + EntityJoinWalker walker = new EntityJoinWalker( persister, uniqueKey, batchSize, @@ -143,7 +142,7 @@ public class EntityLoader extends AbstractEntityLoader { loadQueryInfluencers ); initFromWalker( walker ); - + this.compositeKeyManyToOneTargetIndices = walker.getCompositeKeyManyToOneTargetIndices(); postInstantiate(); batchLoader = batchSize > 1; @@ -163,5 +162,8 @@ public class EntityLoader extends AbstractEntityLoader { protected boolean isSingleRowLoader() { return !batchLoader; } - + + public int[][] getCompositeKeyManyToOneTargetIndices() { + return compositeKeyManyToOneTargetIndices; + } } \ No newline at end of file