FileDocCategorySizeDatePackage
JoinWalker.javaAPI DocHibernate 3.2.527789Thu May 04 20:24:12 BST 2006org.hibernate.loader

JoinWalker

public class JoinWalker extends Object
Walks the metamodel, searching for joins, and collecting together information needed by OuterJoinLoader.
see
OuterJoinLoader
author
Gavin King, Jon Lipsky

Fields Summary
private final org.hibernate.engine.SessionFactoryImplementor
factory
protected final List
associations
private final Set
visitedAssociationKeys
private final Map
enabledFilters
protected String[]
suffixes
protected String[]
collectionSuffixes
protected org.hibernate.persister.entity.Loadable[]
persisters
protected int[]
owners
protected org.hibernate.type.EntityType[]
ownerAssociationTypes
protected org.hibernate.persister.collection.CollectionPersister[]
collectionPersisters
protected int[]
collectionOwners
protected String[]
aliases
protected org.hibernate.LockMode[]
lockModeArray
protected String
sql
Constructors Summary
protected JoinWalker(org.hibernate.engine.SessionFactoryImplementor factory, Map enabledFilters)

		this.factory = factory;
		this.enabledFilters = enabledFilters;
	
Methods Summary
private voidaddAssociationToJoinTree(org.hibernate.type.AssociationType type, java.lang.String[] aliasedLhsColumns, java.lang.String alias, java.lang.String path, int currentDepth, int joinType)
Add on association (one-to-one, many-to-one, or a collection) to a list of associations to be fetched by outerjoin


		Joinable joinable = type.getAssociatedJoinable( getFactory() );

		String subalias = generateTableAlias(
				associations.size()+1, //before adding to collection!
				path, 
				joinable
			);

		OuterJoinableAssociation assoc = new OuterJoinableAssociation(
				type, 
				alias, 
				aliasedLhsColumns, 
				subalias, 
				joinType, 
				getFactory(), 
				enabledFilters
			);
		assoc.validateJoin(path);
		associations.add(assoc);

		int nextDepth = currentDepth+1;
		if ( !joinable.isCollection() ) {
			if (joinable instanceof OuterJoinLoadable) {
				walkEntityTree(
					(OuterJoinLoadable) joinable, 
					subalias,
					path, 
					nextDepth
				);
			}
		}
		else {
			if (joinable instanceof QueryableCollection) {
				walkCollectionTree(
					(QueryableCollection) joinable, 
					subalias, 
					path, 
					nextDepth
				);
			}
		}

	
private voidaddAssociationToJoinTreeIfNecessary(org.hibernate.type.AssociationType type, java.lang.String[] aliasedLhsColumns, java.lang.String alias, java.lang.String path, int currentDepth, int joinType)
Add on association (one-to-one, many-to-one, or a collection) to a list of associations to be fetched by outerjoin (if necessary)

		
		if (joinType>=0) {	
			addAssociationToJoinTree(
					type, 
					aliasedLhsColumns, 
					alias, 
					path,
					currentDepth,
					joinType
				);
		}

	
protected static final intcountCollectionPersisters(java.util.List associations)
Count the number of instances of Joinable which are actually also instances of PersistentCollection which are being fetched by outer join

		int result = 0;
		Iterator iter = associations.iterator();
		while ( iter.hasNext() ) {
			OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next();
			if ( oj.getJoinType()==JoinFragment.LEFT_OUTER_JOIN && oj.getJoinable().isCollection() ) {
				result++;
			}
		}
		return result;
	
protected static final intcountEntityPersisters(java.util.List associations)
Count the number of instances of Joinable which are actually also instances of Loadable, or are one-to-many associations

		int result = 0;
		Iterator iter = associations.iterator();
		while ( iter.hasNext() ) {
			OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next();
			if ( oj.getJoinable().consumesEntityAlias() ) {
				result++;
			}
		}
		return result;
	
protected java.lang.StringgenerateRootAlias(java.lang.String description)

		return StringHelper.generateAlias(description, 0);
	
protected java.lang.StringgenerateTableAlias(int n, java.lang.String path, org.hibernate.persister.entity.Joinable joinable)

		return StringHelper.generateAlias( joinable.getName(), n );
	
public java.lang.String[]getAliases()

		return aliases;
	
public int[]getCollectionOwners()

		return collectionOwners;
	
public org.hibernate.persister.collection.CollectionPersister[]getCollectionPersisters()

		return collectionPersisters;
	
public java.lang.String[]getCollectionSuffixes()

	
	   
		return collectionSuffixes;
	
protected org.hibernate.dialect.DialectgetDialect()

		return factory.getDialect();
	
protected java.util.MapgetEnabledFilters()

		return enabledFilters;
	
protected org.hibernate.engine.SessionFactoryImplementorgetFactory()

		return factory;
	
protected intgetJoinType(org.hibernate.type.AssociationType type, org.hibernate.FetchMode config, java.lang.String path, java.lang.String lhsTable, java.lang.String[] lhsColumns, boolean nullable, int currentDepth, org.hibernate.engine.CascadeStyle cascadeStyle)
Get the join type (inner, outer, etc) or -1 if the association should not be joined. Override on subclasses.

		
		if  ( !isJoinedFetchEnabled(type, config, cascadeStyle) ) return -1;
		
		if ( isTooDeep(currentDepth) || ( type.isCollectionType() && isTooManyCollections() ) ) return -1;
		
		final boolean dupe = isDuplicateAssociation(lhsTable,  lhsColumns, type);
		if (dupe) return -1;
		
		return getJoinType(nullable, currentDepth);
		
	
protected intgetJoinType(boolean nullable, int currentDepth)
Use an inner join if it is a non-null association and this is the "first" join in a series

		//TODO: this is too conservative; if all preceding joins were 
		//      also inner joins, we could use an inner join here
		return !nullable && currentDepth==0 ? 
					JoinFragment.INNER_JOIN : 
					JoinFragment.LEFT_OUTER_JOIN;
	
public org.hibernate.LockMode[]getLockModeArray()

		return lockModeArray;
	
public org.hibernate.type.EntityType[]getOwnerAssociationTypes()

		return ownerAssociationTypes;
	
public int[]getOwners()

		return owners;
	
public org.hibernate.persister.entity.Loadable[]getPersisters()

		return persisters;
	
public java.lang.StringgetSQLString()

		return sql;
	
public java.lang.String[]getSuffixes()

		return suffixes;
	
protected voidinitPersisters(java.util.List associations, org.hibernate.LockMode lockMode)

		
		final int joins = countEntityPersisters(associations);
		final int collections = countCollectionPersisters(associations);

		collectionOwners = collections==0 ? null : new int[collections];
		collectionPersisters = collections==0 ? null : new CollectionPersister[collections];
		collectionSuffixes = BasicLoader.generateSuffixes( joins + 1, collections );

		persisters = new Loadable[joins];
		aliases = new String[joins];
		owners = new int[joins];
		ownerAssociationTypes = new EntityType[joins];
		lockModeArray = ArrayHelper.fillArray(lockMode, joins);
		
		int i=0;
		int j=0;
		Iterator iter = associations.iterator();
		while ( iter.hasNext() ) {
			final OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next();
			if ( !oj.isCollection() ) {
				
				persisters[i] = (Loadable) oj.getJoinable();
				aliases[i] = oj.getRHSAlias();
				owners[i] = oj.getOwner(associations);
				ownerAssociationTypes[i] = (EntityType) oj.getJoinableType();
				i++;
				
			}
			else {
				
				QueryableCollection collPersister = (QueryableCollection) oj.getJoinable();
				if ( oj.getJoinType()==JoinFragment.LEFT_OUTER_JOIN ) {
					//it must be a collection fetch
					collectionPersisters[j] = collPersister;
					collectionOwners[j] = oj.getOwner(associations);
					j++;
				}
	
				if ( collPersister.isOneToMany() ) {
					persisters[i] = (Loadable) collPersister.getElementPersister();
					aliases[i] = oj.getRHSAlias();
					i++;
				}
			}
		}
	
		if ( ArrayHelper.isAllNegative(owners) ) owners = null;
		if ( collectionOwners!=null && ArrayHelper.isAllNegative(collectionOwners) ) {
			collectionOwners = null;
		}
	
protected booleanisDuplicateAssociation(java.lang.String foreignKeyTable, java.lang.String[] foreignKeyColumns)
Used to detect circularities in the joined graph, note that this method is side-effecty

		AssociationKey associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable);
		return !visitedAssociationKeys.add( associationKey );
	
protected booleanisDuplicateAssociation(java.lang.String lhsTable, java.lang.String[] lhsColumnNames, org.hibernate.type.AssociationType type)
Used to detect circularities in the joined graph, note that this method is side-effecty

		final String foreignKeyTable;
		final String[] foreignKeyColumns;
		if ( type.getForeignKeyDirection()==ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT ) {
			foreignKeyTable = lhsTable;
			foreignKeyColumns = lhsColumnNames;
		}
		else {
			foreignKeyTable = type.getAssociatedJoinable( getFactory() ).getTableName();
			foreignKeyColumns = JoinHelper.getRHSColumnNames( type, getFactory() );
		}
		return isDuplicateAssociation(foreignKeyTable, foreignKeyColumns);
	
protected booleanisJoinable(int joinType, java.util.Set visitedAssociationKeys, java.lang.String lhsTable, java.lang.String[] lhsColumnNames, org.hibernate.type.AssociationType type, int depth)
Should we join this association?

		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();
		
		return !tooDeep && !isDuplicateAssociation(lhsTable, lhsColumnNames, type);
	
protected booleanisJoinedFetchEnabled(org.hibernate.type.AssociationType type, org.hibernate.FetchMode config, org.hibernate.engine.CascadeStyle cascadeStyle)
Override on subclasses to enable or suppress joining of certain association types

		return type.isEntityType() && isJoinedFetchEnabledInMapping(config, type) ;
	
protected booleanisJoinedFetchEnabledInMapping(org.hibernate.FetchMode config, org.hibernate.type.AssociationType type)
Does the mapping, and Hibernate default semantics, specify that this association should be fetched by outer joining

		if ( !type.isEntityType() && !type.isCollectionType() ) {
			return false;
		}
		else {
			if (config==FetchMode.JOIN) return true;
			if (config==FetchMode.SELECT) return false;
			if ( type.isEntityType() ) {
				//TODO: look at the owning property and check that it 
				//      isn't lazy (by instrumentation)
				EntityType entityType =(EntityType) type;
				EntityPersister persister = getFactory().getEntityPersister( entityType.getAssociatedEntityName() );
				return !persister.hasProxy();
			}
			else {
				return false;
			}
		}
	
protected booleanisTooDeep(int currentDepth)

		Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth();
		return maxFetchDepth!=null && currentDepth >= maxFetchDepth.intValue();
	
protected booleanisTooManyCollections()

		return false;
	
protected static java.lang.StringmergeOrderings(java.lang.String ordering1, java.lang.String ordering2)

		if ( ordering1.length() == 0 ) {
			return ordering2;
		}
		else if ( ordering2.length() == 0 ) {
			return ordering1;
		}
		else {
			return ordering1 + ", " + ordering2;
		}
	
protected final org.hibernate.sql.JoinFragmentmergeOuterJoins(java.util.List associations)
Generate a sequence of LEFT OUTER JOIN clauses for the given associations.

		JoinFragment outerjoin = getDialect().createOuterJoinFragment();
		Iterator iter = associations.iterator();
		OuterJoinableAssociation last = null;
		while ( iter.hasNext() ) {
			OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next();
			if ( last != null && last.isManyToManyWith( oj ) ) {
				oj.addManyToManyJoin( outerjoin, ( QueryableCollection ) last.getJoinable() );
			}
			else {
				oj.addJoins(outerjoin);
			}
			last = oj;
		}
		last = null;
		return outerjoin;
	
protected java.lang.StringorderBy(java.util.List associations, java.lang.String orderBy)

		return mergeOrderings( orderBy( associations ), orderBy );
	
protected static final java.lang.StringorderBy(java.util.List associations)
Get the order by string required for collection fetching

		StringBuffer buf = new StringBuffer();
		Iterator iter = associations.iterator();
		OuterJoinableAssociation last = null;
		while ( iter.hasNext() ) {
			OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next();
			if ( oj.getJoinType() == JoinFragment.LEFT_OUTER_JOIN ) { // why does this matter?
				if ( oj.getJoinable().isCollection() ) {
					final QueryableCollection queryableCollection = (QueryableCollection) oj.getJoinable();
					if ( queryableCollection.hasOrdering() ) {
						final String orderByString = queryableCollection.getSQLOrderByString( oj.getRHSAlias() );
						buf.append( orderByString ).append(", ");
					}
				}
				else {
					// it might still need to apply a collection ordering based on a
					// many-to-many defined order-by...
					if ( last != null && last.getJoinable().isCollection() ) {
						final QueryableCollection queryableCollection = (QueryableCollection) last.getJoinable();
						if ( queryableCollection.isManyToMany() && last.isManyToManyWith( oj ) ) {
							if ( queryableCollection.hasManyToManyOrdering() ) {
								final String orderByString = queryableCollection.getManyToManyOrderByString( oj.getRHSAlias() );
								buf.append( orderByString ).append(", ");
							}
						}
					}
				}
			}
			last = oj;
		}
		if ( buf.length()>0 ) buf.setLength( buf.length()-2 );
		return buf.toString();
	
protected final java.lang.StringselectString(java.util.List associations)
Generate a select list of columns containing all properties of the entity classes


		if ( associations.size()==0 ) {
			return "";
		}
		else {
			StringBuffer buf = new StringBuffer( associations.size() * 100 )
				.append(", ");
			int entityAliasCount=0;
			int collectionAliasCount=0;
			for ( int i=0; i<associations.size(); i++ ) {
				OuterJoinableAssociation join = (OuterJoinableAssociation) associations.get(i);
				OuterJoinableAssociation next = (i == associations.size() - 1)
				        ? null
				        : ( OuterJoinableAssociation ) associations.get( i + 1 );
				final Joinable joinable = join.getJoinable();
				final String entitySuffix = ( suffixes == null || entityAliasCount >= suffixes.length )
				        ? null
				        : suffixes[entityAliasCount];
				final String collectionSuffix = ( collectionSuffixes == null || collectionAliasCount >= collectionSuffixes.length )
				        ? null
				        : collectionSuffixes[collectionAliasCount];
				final String selectFragment = joinable.selectFragment(
						next == null ? null : next.getJoinable(),
						next == null ? null : next.getRHSAlias(),
						join.getRHSAlias(),
						entitySuffix,
				        collectionSuffix,
						join.getJoinType()==JoinFragment.LEFT_OUTER_JOIN
				);
				buf.append(selectFragment);
				if ( joinable.consumesEntityAlias() ) entityAliasCount++;
				if ( joinable.consumesCollectionAlias() && join.getJoinType()==JoinFragment.LEFT_OUTER_JOIN ) collectionAliasCount++;
				if (
					i<associations.size()-1 &&
					selectFragment.trim().length()>0
				) {
					buf.append(", ");
				}
			}
			return buf.toString();
		}
	
public voidsetAliases(java.lang.String[] aliases)

		this.aliases = aliases;
	
public voidsetCollectionOwners(int[] collectionOwners)

		this.collectionOwners = collectionOwners;
	
public voidsetCollectionPersisters(org.hibernate.persister.collection.CollectionPersister[] collectionPersisters)

		this.collectionPersisters = collectionPersisters;
	
public voidsetCollectionSuffixes(java.lang.String[] collectionSuffixes)

		this.collectionSuffixes = collectionSuffixes;
	
public voidsetLockModeArray(org.hibernate.LockMode[] lockModeArray)

		this.lockModeArray = lockModeArray;
	
public voidsetOwnerAssociationTypes(org.hibernate.type.EntityType[] ownerAssociationType)

		this.ownerAssociationTypes = ownerAssociationType;
	
public voidsetOwners(int[] owners)

		this.owners = owners;
	
public voidsetPersisters(org.hibernate.persister.entity.Loadable[] persisters)

		this.persisters = persisters;
	
public voidsetSql(java.lang.String sql)

		this.sql = sql;
	
public voidsetSuffixes(java.lang.String[] suffixes)

		this.suffixes = suffixes;
	
private static java.lang.StringsubPath(java.lang.String path, java.lang.String property)
Extend the path by the given property name

		if ( path==null || path.length()==0) {
			return property;
		}
		else {
			return StringHelper.qualify(path, property);
		}
	
protected final voidwalkCollectionTree(org.hibernate.persister.collection.QueryableCollection persister, java.lang.String alias)
For a collection role, return a list of associations to be fetched by outerjoin

		walkCollectionTree(persister, alias, "", 0);
		//TODO: when this is the entry point, we should use an INNER_JOIN for fetching the many-to-many elements!
	
private voidwalkCollectionTree(org.hibernate.persister.collection.QueryableCollection persister, java.lang.String alias, java.lang.String path, int currentDepth)
For a collection role, return a list of associations to be fetched by outerjoin


		if ( persister.isOneToMany() ) {
			walkEntityTree(
					(OuterJoinLoadable) persister.getElementPersister(),
					alias,
					path,
					currentDepth
				);
		}
		else {
			Type type = persister.getElementType();
			if ( type.isAssociationType() ) {
				// a many-to-many;
				// decrement currentDepth here to allow join across the association table
				// without exceeding MAX_FETCH_DEPTH (i.e. the "currentDepth - 1" bit)
				AssociationType associationType = (AssociationType) type;
				String[] aliasedLhsColumns = persister.getElementColumnNames(alias);
				String[] lhsColumns = persister.getElementColumnNames();
				// if the current depth is 0, the root thing being loaded is the
				// many-to-many collection itself.  Here, it is alright to use
				// an inner join...
				boolean useInnerJoin = currentDepth == 0;
				final int joinType = getJoinType(
						associationType,
						persister.getFetchMode(),
						path,
						persister.getTableName(),
						lhsColumns,
						!useInnerJoin,
						currentDepth - 1, 
						null //operations which cascade as far as the collection also cascade to collection elements
					);
				addAssociationToJoinTreeIfNecessary(
						associationType,
						aliasedLhsColumns,
						alias,
						path,
						currentDepth - 1,
						joinType
					);
			}
			else if ( type.isComponentType() ) {
				walkCompositeElementTree(
						(AbstractComponentType) type,
						persister.getElementColumnNames(),
						persister,
						alias,
						path,
						currentDepth
					);
			}
		}

	
private voidwalkComponentTree(org.hibernate.type.AbstractComponentType componentType, int propertyNumber, int begin, org.hibernate.persister.entity.OuterJoinLoadable persister, java.lang.String alias, java.lang.String path, int currentDepth)
For a component, add to a list of associations to be fetched by outerjoin


		Type[] types = componentType.getSubtypes();
		String[] propertyNames = componentType.getPropertyNames();
		for ( int i=0; i <types.length; i++ ) {

			if ( types[i].isAssociationType() ) {
				AssociationType associationType = (AssociationType) types[i];

				String[] aliasedLhsColumns = JoinHelper.getAliasedLHSColumnNames(
					associationType, alias, propertyNumber, begin, persister, getFactory()
				);

				String[] lhsColumns = JoinHelper.getLHSColumnNames(
					associationType, propertyNumber, begin, persister, getFactory()
				);
				String lhsTable = JoinHelper.getLHSTableName(associationType, propertyNumber, persister);

				String subpath = subPath( path, propertyNames[i] );
				final boolean[] propertyNullability = componentType.getPropertyNullability();
				final int joinType = getJoinType(
						associationType,
						componentType.getFetchMode(i),
						subpath,
						lhsTable,
						lhsColumns,
						propertyNullability==null || propertyNullability[i],
						currentDepth, 
						componentType.getCascadeStyle(i)
					);
				addAssociationToJoinTreeIfNecessary(			
						associationType,
						aliasedLhsColumns,
						alias,
						subpath,
						currentDepth,
						joinType
					);

			}
			else if ( types[i].isComponentType() ) {
				String subpath = subPath( path, propertyNames[i] );
				walkComponentTree(
						(AbstractComponentType) types[i],
						propertyNumber,
						begin,
						persister,
						alias,
						subpath,
						currentDepth
					);
			}
			
			begin+=types[i].getColumnSpan( getFactory() );
		}

	
private voidwalkCompositeElementTree(org.hibernate.type.AbstractComponentType compositeType, java.lang.String[] cols, org.hibernate.persister.collection.QueryableCollection persister, java.lang.String alias, java.lang.String path, int currentDepth)
For a composite element, add to a list of associations to be fetched by outerjoin


		Type[] types = compositeType.getSubtypes();
		String[] propertyNames = compositeType.getPropertyNames();
		int begin = 0;
		for ( int i=0; i <types.length; i++ ) {
			int length = types[i].getColumnSpan( getFactory() );
			String[] lhsColumns = ArrayHelper.slice(cols, begin, length);

			if ( types[i].isAssociationType() ) {
				AssociationType associationType = (AssociationType) types[i];

				// simple, because we can't have a one-to-one or a collection 
				// (or even a property-ref) in a composite-element:
				String[] aliasedLhsColumns = StringHelper.qualify(alias, lhsColumns);

				String subpath = subPath( path, propertyNames[i] );
				final boolean[] propertyNullability = compositeType.getPropertyNullability();
				final int joinType = getJoinType(
						associationType,
						compositeType.getFetchMode(i),
						subpath,
						persister.getTableName(),
						lhsColumns,
						propertyNullability==null || propertyNullability[i],
						currentDepth, 
						compositeType.getCascadeStyle(i)
					);
				addAssociationToJoinTreeIfNecessary(
						associationType,
						aliasedLhsColumns,
						alias,
						subpath,
						currentDepth,
						joinType
					);
			}
			else if ( types[i].isComponentType() ) {
				String subpath = subPath( path, propertyNames[i] );
				walkCompositeElementTree(
						(AbstractComponentType) types[i],
						lhsColumns,
						persister,
						alias,
						subpath,
						currentDepth
					);
			}
			begin+=length;
		}

	
private final voidwalkEntityAssociationTree(org.hibernate.type.AssociationType associationType, org.hibernate.persister.entity.OuterJoinLoadable persister, int propertyNumber, java.lang.String alias, java.lang.String path, boolean nullable, int currentDepth)
Walk the tree for a particular entity association


		String[] aliasedLhsColumns = JoinHelper.getAliasedLHSColumnNames(
				associationType, alias, propertyNumber, persister, getFactory()
			);

		String[] lhsColumns = JoinHelper.getLHSColumnNames(
				associationType, propertyNumber, persister, getFactory()
			);
		String lhsTable = JoinHelper.getLHSTableName(associationType, propertyNumber, persister);

		String subpath = subPath( path, persister.getSubclassPropertyName(propertyNumber) );
		int joinType = getJoinType(
				associationType,
				persister.getFetchMode(propertyNumber),
				subpath,
				lhsTable,
				lhsColumns,
				nullable,
				currentDepth, 
				persister.getCascadeStyle(propertyNumber)
			);
		addAssociationToJoinTreeIfNecessary(
				associationType,
				aliasedLhsColumns,
				alias,
				subpath,
				currentDepth,
				joinType
			);

	
protected final voidwalkEntityTree(org.hibernate.persister.entity.OuterJoinLoadable persister, java.lang.String alias)
For an entity class, return a list of associations to be fetched by outerjoin

		walkEntityTree(persister, alias, "", 0);
	
private final voidwalkEntityTree(org.hibernate.persister.entity.OuterJoinLoadable persister, java.lang.String alias, java.lang.String path, int currentDepth)
For an entity class, add to a list of associations to be fetched by outerjoin


		int n = persister.countSubclassProperties();
		for ( int i=0; i<n; i++ ) {
			Type type = persister.getSubclassPropertyType(i);
			if ( type.isAssociationType() ) {
				walkEntityAssociationTree(
					(AssociationType) type,
					persister,
					i,
					alias,
					path,
					persister.isSubclassPropertyNullable(i),
					currentDepth
				);
			}
			else if ( type.isComponentType() ) {
				walkComponentTree(
					(AbstractComponentType) type,
					i,
					0,
					persister,
					alias,
					subPath( path, persister.getSubclassPropertyName(i) ),
					currentDepth
				);
			}
		}
	
protected java.lang.StringBufferwhereString(java.lang.String alias, java.lang.String[] columnNames, int batchSize)
Render the where condition for a (batch) load by identifier / collection key

		if ( columnNames.length==1 ) {
			// if not a composite key, use "foo in (?, ?, ?)" for batching
			// if no batch, and not a composite key, use "foo = ?"
			InFragment in = new InFragment().setColumn( alias, columnNames[0] );
			for ( int i=0; i<batchSize; i++ ) in.addValue("?");
			return new StringBuffer( in.toFragmentString() );
		}
		else {
			//a composite key
			ConditionFragment byId = new ConditionFragment()
					.setTableAlias(alias)
					.setCondition( columnNames, "?" );
	
			StringBuffer whereString = new StringBuffer();
			if ( batchSize==1 ) {
				// if no batch, use "foo = ? and bar = ?"
				whereString.append( byId.toFragmentString() );
			}
			else {
				// if a composite key, use "( (foo = ? and bar = ?) or (foo = ? and bar = ?) )" for batching
				whereString.append('("); //TODO: unnecessary for databases with ANSI-style joins
				DisjunctionFragment df = new DisjunctionFragment();
				for ( int i=0; i<batchSize; i++ ) {
					df.addCondition(byId);
				}
				whereString.append( df.toFragmentString() );
				whereString.append(')"); //TODO: unnecessary for databases with ANSI-style joins
			}
			return whereString;
		}