HHH-2277 - bidirectional <key-many-to-one> 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
This commit is contained in:
Steve Ebersole 2010-05-20 05:37:43 +00:00
parent a58e1ef197
commit 460cbf8d96
11 changed files with 501 additions and 188 deletions

View File

@ -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 ) {

View File

@ -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;

View File

@ -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;

View File

@ -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) {
}

View File

@ -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);
}
}

View File

@ -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 + ']';
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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() );
}
}

View File

@ -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<String,OuterJoinableAssociation> associationsByAlias
= new HashMap<String, OuterJoinableAssociation>();
private final HashMap<String,Integer> positionsByAlias = new HashMap<String, Integer>();
private final ArrayList<String> aliasesForAssociationsWithCompositesIds
= new ArrayList<String>();
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<Integer> keyManyToOneTargetIndices = new ArrayList<Integer>();
// 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<Integer> 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;
}
}
}

View File

@ -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;
}
}