alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Hibernate example source code file (JoinWalker.java)

This example Hibernate source code file (JoinWalker.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Hibernate tags/keywords

associationkey, associationtype, jointype, mappingexception, mappingexception, outerjoinableassociation, outerjoinableassociation, outerjoinloadable, propertypath, propertypath, queryablecollection, string, string, stringbuffer, util

The Hibernate JoinWalker.java source code

/*
 * 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.hibernate.FetchMode;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.internal.JoinHelper;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.mapping.Join;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.sql.ConditionFragment;
import org.hibernate.sql.DisjunctionFragment;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.JoinFragment;
import org.hibernate.sql.JoinType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.Type;

/**
 * Walks the metamodel, searching for joins, and collecting
 * together information needed by <tt>OuterJoinLoader.
 * 
 * @see OuterJoinLoader
 * @author Gavin King, Jon Lipsky
 */
public class JoinWalker {
	
	private final SessionFactoryImplementor factory;
	protected final List associations = new ArrayList();
	private final Set visitedAssociationKeys = new HashSet();
	private final LoadQueryInfluencers loadQueryInfluencers;

	protected String[] suffixes;
	protected String[] collectionSuffixes;
	protected Loadable[] persisters;
	protected int[] owners;
	protected EntityType[] ownerAssociationTypes;
	protected CollectionPersister[] collectionPersisters;
	protected int[] collectionOwners;
	protected String[] aliases;
	protected LockOptions lockOptions;
	protected LockMode[] lockModeArray;
	protected String sql;

	protected JoinWalker(
			SessionFactoryImplementor factory,
			LoadQueryInfluencers loadQueryInfluencers) {
		this.factory = factory;
		this.loadQueryInfluencers = loadQueryInfluencers;

	}


	public String[] getCollectionSuffixes() {
		return collectionSuffixes;
	}

	public void setCollectionSuffixes(String[] collectionSuffixes) {
		this.collectionSuffixes = collectionSuffixes;
	}

	public LockOptions getLockModeOptions() {
		return lockOptions;
	}

	public LockMode[] getLockModeArray() {
		return lockModeArray;
	}

	public String[] getSuffixes() {
		return suffixes;
	}

	public void setSuffixes(String[] suffixes) {
		this.suffixes = suffixes;
	}

	public String[] getAliases() {
		return aliases;
	}

	public void setAliases(String[] aliases) {
		this.aliases = aliases;
	}

	public int[] getCollectionOwners() {
		return collectionOwners;
	}

	public void setCollectionOwners(int[] collectionOwners) {
		this.collectionOwners = collectionOwners;
	}

	public CollectionPersister[] getCollectionPersisters() {
		return collectionPersisters;
	}

	public void setCollectionPersisters(CollectionPersister[] collectionPersisters) {
		this.collectionPersisters = collectionPersisters;
	}

	public EntityType[] getOwnerAssociationTypes() {
		return ownerAssociationTypes;
	}

	public void setOwnerAssociationTypes(EntityType[] ownerAssociationType) {
		this.ownerAssociationTypes = ownerAssociationType;
	}

	public int[] getOwners() {
		return owners;
	}

	public void setOwners(int[] owners) {
		this.owners = owners;
	}

	public Loadable[] getPersisters() {
		return persisters;
	}

	public void setPersisters(Loadable[] persisters) {
		this.persisters = persisters;
	}

	public String getSQLString() {
		return sql;
	}

	public void setSql(String sql) {
		this.sql = sql;
	}

	protected SessionFactoryImplementor getFactory() {
		return factory;
	}

	protected Dialect getDialect() {
		return factory.getDialect();
	}

	public LoadQueryInfluencers getLoadQueryInfluencers() {
		return loadQueryInfluencers;
	}

	/**
	 * Add on association (one-to-one, many-to-one, or a collection) to a list 
	 * of associations to be fetched by outerjoin (if necessary)
	 */
	private void addAssociationToJoinTreeIfNecessary(
			final AssociationType type,
			final String[] aliasedLhsColumns,
			final String alias,
			final PropertyPath path,
			int currentDepth,
			final JoinType joinType) throws MappingException {
		if ( joinType != JoinType.NONE ) {
			addAssociationToJoinTree(
					type, 
					aliasedLhsColumns, 
					alias, 
					path,
					currentDepth,
					joinType
			);
		}
	}

	protected boolean hasRestriction(PropertyPath path)	{
		return false;
	}

	protected String getWithClause(PropertyPath path)	{
		return "";
	}
	
	/**
	 * Add on association (one-to-one, many-to-one, or a collection) to a list 
	 * of associations to be fetched by outerjoin 
	 */
	private void addAssociationToJoinTree(
			final AssociationType type,
			final String[] aliasedLhsColumns,
			final String alias,
			final PropertyPath path,
			final int currentDepth,
			final JoinType joinType) throws MappingException {

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

		// important to generate alias based on size of association collection
		// *before* adding this join to that collection
		String subalias = generateTableAlias( associations.size() + 1, path, joinable );

		// NOTE : it should be fine to continue to pass only filters below
		// (instead of LoadQueryInfluencers) since "from that point on" we
		// only need to worry about restrictions (and not say adding more
		// joins)
		OuterJoinableAssociation assoc = new OuterJoinableAssociation(
				path,
				type, 
				alias, 
				aliasedLhsColumns, 
				subalias, 
				joinType, 
				getWithClause(path),
				hasRestriction( path ),
				getFactory(),
				loadQueryInfluencers.getEnabledFilters()
		);
		assoc.validateJoin( path.getFullPath() );
		associations.add( assoc );

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

	}

	/**
	 * 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(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.
	 * @throws org.hibernate.MappingException ???
	 */
	protected final void walkEntityTree(
			OuterJoinLoadable persister,
			String alias) throws MappingException {
		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, new PropertyPath(), 0 );
		//TODO: when this is the entry point, we should use an INNER_JOIN for fetching the many-to-many elements!
	}

	/**
	 * For a collection role, return a list of associations to be fetched by outerjoin
	 */
	private void walkCollectionTree(
			final QueryableCollection persister,
			final String alias,
			final PropertyPath path,
			final int currentDepth)	throws MappingException {

		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 JoinType 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(
						(CompositeType) type,
						persister.getElementColumnNames(),
						persister,
						alias,
						path,
						currentDepth
					);
			}
		}

	}
	
	/**
	 * Process a particular association owned by the entity
	 *
	 * @param associationType The type representing the association to be
	 * processed.
	 * @param persister The owner of the association to be processed.
	 * @param propertyNumber The property number for the association
	 * (relative to the persister).
	 * @param alias The entity alias
	 * @param path The path to the association
	 * @param nullable is the association nullable (which I think is supposed
	 * to indicate inner/outer join semantics).
	 * @param currentDepth The current join depth
	 * @throws org.hibernate.MappingException ???
	 */
	private void walkEntityAssociationTree(
			final AssociationType associationType,
			final OuterJoinLoadable persister,
			final int propertyNumber,
			final String alias,
			final PropertyPath path,
			final boolean nullable,
			final int currentDepth) throws MappingException {
		String[] aliasedLhsColumns = JoinHelper.getAliasedLHSColumnNames(
				associationType, alias, propertyNumber, persister, getFactory()
		);
		String[] lhsColumns = JoinHelper.getLHSColumnNames(
				associationType, propertyNumber, persister, getFactory()
		);
		String lhsTable = JoinHelper.getLHSTableName(associationType, propertyNumber, persister);

		PropertyPath subPath = path.append( persister.getSubclassPropertyName(propertyNumber) );
		JoinType joinType = getJoinType(
				persister,
				subPath,
				propertyNumber,
				associationType,
				persister.getFetchMode( propertyNumber ),
				persister.getCascadeStyle( propertyNumber ),
				lhsTable,
				lhsColumns,
				nullable,
				currentDepth
		);
		addAssociationToJoinTreeIfNecessary(
				associationType,
				aliasedLhsColumns,
				alias,
				subPath,
				currentDepth,
				joinType
		);
	}

	/**
	 * Determine the appropriate type of join (if any) to use to fetch the
	 * given association.
	 *
	 * @param persister The owner of the association.
	 * @param path The path to the association
	 * @param propertyNumber The property number representing the association.
	 * @param associationType The association type.
	 * @param metadataFetchMode The metadata-defined fetch mode.
	 * @param metadataCascadeStyle The metadata-defined cascade style.
	 * @param lhsTable The owner table
	 * @param lhsColumns The owner join columns
	 * @param nullable Is the association nullable.
	 * @param currentDepth Current join depth
	 * @return type of join to use ({@link org.hibernate.sql.JoinType#INNER_JOIN},
	 * {@link org.hibernate.sql.JoinType#LEFT_OUTER_JOIN}, or -1 to indicate no joining.
	 * @throws MappingException ??
	 */
	protected JoinType getJoinType(
			OuterJoinLoadable persister,
			final PropertyPath path,
			int propertyNumber,
			AssociationType associationType,
			FetchMode metadataFetchMode,
			CascadeStyle metadataCascadeStyle,
			String lhsTable,
			String[] lhsColumns,
			final boolean nullable,
			final int currentDepth) throws MappingException {
		return getJoinType(
				associationType,
				metadataFetchMode,
				path,
				lhsTable,
				lhsColumns,
				nullable,
				currentDepth,
				metadataCascadeStyle
		);
	}

	/**
	 * Determine the appropriate associationType of join (if any) to use to fetch the
	 * given association.
	 *
	 * @param associationType The association associationType.
	 * @param config The metadata-defined fetch mode.
	 * @param path The path to the association
	 * @param lhsTable The owner table
	 * @param lhsColumns The owner join columns
	 * @param nullable Is the association nullable.
	 * @param currentDepth Current join depth
	 * @param cascadeStyle The metadata-defined cascade style.
	 * @return type of join to use ({@link org.hibernate.sql.JoinType#INNER_JOIN},
	 * {@link org.hibernate.sql.JoinType#LEFT_OUTER_JOIN}, or -1 to indicate no joining.
	 * @throws MappingException ??
	 */
	protected JoinType getJoinType(
			AssociationType associationType,
			FetchMode config,
			PropertyPath path,
			String lhsTable,
			String[] lhsColumns,
			boolean nullable,
			int currentDepth,
			CascadeStyle cascadeStyle) throws MappingException {
		if  ( !isJoinedFetchEnabled( associationType, config, cascadeStyle ) ) {
			return JoinType.NONE;
		}
		if ( isTooDeep(currentDepth) || ( associationType.isCollectionType() && isTooManyCollections() ) ) {
			return JoinType.NONE;
		}
		if ( isDuplicateAssociation( lhsTable, lhsColumns, associationType ) ) {
			return JoinType.NONE;
		}
		return getJoinType( nullable, currentDepth );
	}

	/**
	 * 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(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 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 PropertyPath path,
			final int currentDepth) throws MappingException {
		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(
						( CompositeType ) type,
						i,
						0,
						persister,
						alias,
						path.append( persister.getSubclassPropertyName(i) ),
						currentDepth
				);
			}
		}
	}

	/**
	 * For a component, add to a list of associations to be fetched by outerjoin
	 *
	 *
	 * @param componentType The component type to be walked.
	 * @param propertyNumber The property number for the component property (relative to
	 * persister).
	 * @param begin todo unknowm
	 * @param persister The owner of the component property
	 * @param alias The root alias
	 * @param path The property access path
	 * @param currentDepth The current join depth
	 * @throws org.hibernate.MappingException ???
	 */
	private void walkComponentTree(
			final CompositeType componentType,
			final int propertyNumber,
			int begin,
			final OuterJoinLoadable persister,
			final String alias,
			final PropertyPath path,
			final int currentDepth) throws MappingException {
		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);

				final PropertyPath subPath = path.append( propertyNames[i] );
				final boolean[] propertyNullability = componentType.getPropertyNullability();
				final JoinType joinType = getJoinType(
						persister,
						subPath,
						propertyNumber,
						associationType,
						componentType.getFetchMode(i),
						componentType.getCascadeStyle(i),
						lhsTable,
						lhsColumns,
						propertyNullability==null || propertyNullability[i],
						currentDepth
				);
				addAssociationToJoinTreeIfNecessary(			
						associationType,
						aliasedLhsColumns,
						alias,
						subPath,
						currentDepth,
						joinType
				);

			}
			else if ( types[i].isComponentType() ) {
				final PropertyPath subPath = path.append( propertyNames[i] );
				walkComponentTree(
						( CompositeType ) types[i],
						propertyNumber,
						begin,
						persister,
						alias,
						subPath,
						currentDepth
				);
			}
			begin += types[i].getColumnSpan( getFactory() );
		}

	}

	/**
	 * For a composite element, add to a list of associations to be fetched by outerjoin
	 */
	private void walkCompositeElementTree(
			final CompositeType compositeType,
			final String[] cols,
			final QueryableCollection persister,
			final String alias,
			final PropertyPath path,
			final int currentDepth) throws MappingException {

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

				final PropertyPath subPath = path.append( propertyNames[i] );
				final boolean[] propertyNullability = compositeType.getPropertyNullability();
				final JoinType 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() ) {
				final PropertyPath subPath = path.append( propertyNames[i] );
				walkCompositeElementTree(
						(CompositeType) types[i],
						lhsColumns,
						persister,
						alias,
						subPath,
						currentDepth
					);
			}
			begin+=length;
		}

	}

	/**
	 * Use an inner join if it is a non-null association and this
	 * is the "first" join in a series
	 */
	protected JoinType getJoinType(boolean nullable, int currentDepth) {
		//TODO: this is too conservative; if all preceding joins were 
		//      also inner joins, we could use an inner join here
		//
		// IMPL NOTE : currentDepth might be less-than zero if this is the
		// 		root of a many-to-many collection initializer 
		return !nullable && currentDepth <= 0
				? JoinType.INNER_JOIN
				: JoinType.LEFT_OUTER_JOIN;
	}

	protected boolean isTooDeep(int currentDepth) {
		Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth();
		return maxFetchDepth!=null && currentDepth >= maxFetchDepth.intValue();
	}
	
	protected boolean isTooManyCollections() {
		return false;
	}
	
	/**
	 * Does the mapping, and Hibernate default semantics, specify that
	 * this association should be fetched by outer joining
	 */
	protected boolean isJoinedFetchEnabledInMapping(FetchMode config, AssociationType type) 
	throws MappingException {
		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;
			}
		}
	}

	/**
	 * Override on subclasses to enable or suppress joining 
	 * of certain association types
	 */
	protected boolean isJoinedFetchEnabled(AssociationType type, FetchMode config, CascadeStyle cascadeStyle) {
		return type.isEntityType() && isJoinedFetchEnabledInMapping(config, type) ;
	}
	
	protected String generateTableAlias(final int n, final PropertyPath path, final Joinable joinable) {
		return StringHelper.generateAlias( joinable.getName(), n );
	}

	protected String generateRootAlias(final String description) {
		return StringHelper.generateAlias(description, 0);
	}

	/**
	 * Used to detect circularities in the joined graph, note that 
	 * this method is side-effecty
	 */
	protected boolean isDuplicateAssociation(final String foreignKeyTable, final String[] foreignKeyColumns) {
		AssociationKey associationKey = new AssociationKey(foreignKeyColumns, foreignKeyTable);
		return !visitedAssociationKeys.add( associationKey );
	}
	
	/**
	 * 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) {
		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);
	}
	
	/**
	 * Uniquely identifier a foreign key, so that we don't
	 * join it more than once, and create circularities
	 */
	private static final class AssociationKey {
		private String[] columns;
		private String table;
		private AssociationKey(String[] columns, String table) {
			this.columns = columns;
			this.table = table;
		}
		@Override
        public boolean equals(Object other) {
			AssociationKey that = (AssociationKey) other;
			return that.table.equals(table) && Arrays.equals(columns, that.columns);
		}
		@Override
        public int hashCode() {
			return table.hashCode(); //TODO: inefficient
		}
	}
	
	/**
	 * Should we join this association?
	 */
	protected boolean isJoinable(
			final JoinType joinType,
			final Set visitedAssociationKeys,
			final String lhsTable,
			final String[] lhsColumnNames,
			final AssociationType type,
			final int depth) {

		if ( joinType == JoinType.NONE ) {
			return false;
		}
		
		if ( joinType == JoinType.INNER_JOIN ) {
			return true;
		}

		Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth();
		final boolean tooDeep = maxFetchDepth!=null && depth >= maxFetchDepth.intValue();
		
		return !tooDeep && !isDuplicateAssociation(lhsTable, lhsColumnNames, type);
	}
	
	protected String orderBy(final List associations, final String orderBy) {
		return mergeOrderings( orderBy( associations ), orderBy );
	}

	protected static String mergeOrderings(String ordering1, String ordering2) {
		if ( ordering1.length() == 0 ) {
			return ordering2;
		}
		else if ( ordering2.length() == 0 ) {
			return ordering1;
		}
		else {
			return ordering1 + ", " + ordering2;
		}
	}
	
	/**
	 * Generate a sequence of <tt>LEFT OUTER JOIN clauses for the given associations.
	 */
	protected final JoinFragment mergeOuterJoins(List associations)
	throws MappingException {
		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;
	}

	/**
	 * Count the number of instances of Joinable which are actually
	 * also instances of Loadable, or are one-to-many associations
	 */
	protected static final int countEntityPersisters(List associations)
	throws MappingException {
		int result = 0;
		Iterator iter = associations.iterator();
		while ( iter.hasNext() ) {
			OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next();
			if ( oj.getJoinable().consumesEntityAlias() ) {
				result++;
			}
		}
		return result;
	}
	
	/**
	 * Count the number of instances of Joinable which are actually
	 * also instances of PersistentCollection which are being fetched
	 * by outer join
	 */
	protected static final int countCollectionPersisters(List associations)
	throws MappingException {
		int result = 0;
		Iterator iter = associations.iterator();
		while ( iter.hasNext() ) {
			OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next();
			if ( oj.getJoinType()==JoinType.LEFT_OUTER_JOIN &&
					oj.getJoinable().isCollection() &&
					! oj.hasRestriction() ) {
				result++;
			}
		}
		return result;
	}
	
	/**
	 * Get the order by string required for collection fetching
	 */
	protected static final String orderBy(List associations)
	throws MappingException {
		StringBuffer buf = new StringBuffer();
		Iterator iter = associations.iterator();
		OuterJoinableAssociation last = null;
		while ( iter.hasNext() ) {
			OuterJoinableAssociation oj = (OuterJoinableAssociation) iter.next();
			if ( oj.getJoinType() == JoinType.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();
	}
	
	/**
	 * Render the where condition for a (batch) load by identifier / collection key
	 */
	protected StringBuffer whereString(String alias, String[] columnNames, int batchSize) {
		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;
		}
	}


	protected void initPersisters(final List associations, final LockMode lockMode) throws MappingException {
		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);

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

		this.lockOptions = lockOptions;

		persisters = new Loadable[joins];
		aliases = new String[joins];
		owners = new int[joins];
		ownerAssociationTypes = new EntityType[joins];
		lockModeArray = ArrayHelper.fillArray( lockOptions.getLockMode(), 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();
				callback.associationProcessed( oj, i );
				i++;
				
			}
			else {
				
				QueryableCollection collPersister = (QueryableCollection) oj.getJoinable();
				if ( oj.getJoinType()==JoinType.LEFT_OUTER_JOIN && ! oj.hasRestriction() ) {
					//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();
					callback.associationProcessed( oj, i );
					i++;
				}
			}
		}

		if ( ArrayHelper.isAllNegative(owners) ) owners = null;
		if ( collectionOwners!=null && ArrayHelper.isAllNegative(collectionOwners) ) {
			collectionOwners = null;
		}
	}

	/**
	 * Generate a select list of columns containing all properties of the entity classes
	 */
	protected final String selectString(List associations)
	throws MappingException {

		if ( associations.size()==0 ) {
			return "";
		}
		else {
			StringBuffer buf = new StringBuffer( associations.size() * 100 );
			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()==JoinType.LEFT_OUTER_JOIN
				);
				if (selectFragment.trim().length() > 0) {
					buf.append(", ").append(selectFragment);
				}
				if ( joinable.consumesEntityAlias() ) entityAliasCount++;
				if ( joinable.consumesCollectionAlias() && join.getJoinType()==JoinType.LEFT_OUTER_JOIN ) collectionAliasCount++;
			}
			return buf.toString();
		}
	}

}

Other Hibernate examples (source code examples)

Here is a short list of links related to this Hibernate JoinWalker.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.