org.hibernate.loader.JoinWalker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate Show documentation
Show all versions of hibernate Show documentation
Relational Persistence for Java
//$Id: JoinWalker.java 9889 2006-05-05 01:24:12Z [email protected] $
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.Map;
import java.util.Set;
import org.hibernate.FetchMode;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.CascadeStyle;
import org.hibernate.engine.JoinHelper;
import org.hibernate.engine.SessionFactoryImplementor;
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.type.AbstractComponentType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.Type;
import org.hibernate.util.ArrayHelper;
import org.hibernate.util.StringHelper;
/**
* Walks the metamodel, searching for joins, and collecting
* together information needed by 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 Map enabledFilters;
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 LockMode[] lockModeArray;
protected String sql;
public String[] getCollectionSuffixes() {
return collectionSuffixes;
}
public void setCollectionSuffixes(String[] collectionSuffixes) {
this.collectionSuffixes = collectionSuffixes;
}
public LockMode[] getLockModeArray() {
return lockModeArray;
}
public void setLockModeArray(LockMode[] lockModeArray) {
this.lockModeArray = 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();
}
protected Map getEnabledFilters() {
return enabledFilters;
}
protected JoinWalker(SessionFactoryImplementor factory, Map enabledFilters) {
this.factory = factory;
this.enabledFilters = enabledFilters;
}
/**
* 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 String path,
int currentDepth,
final int joinType)
throws MappingException {
if (joinType>=0) {
addAssociationToJoinTree(
type,
aliasedLhsColumns,
alias,
path,
currentDepth,
joinType
);
}
}
/**
* 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 String path,
final int currentDepth,
final int joinType)
throws MappingException {
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
);
}
}
}
/**
* For an entity class, return a list of associations to be fetched by outerjoin
*/
protected final void walkEntityTree(OuterJoinLoadable persister, String alias)
throws MappingException {
walkEntityTree(persister, alias, "", 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);
//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 String 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 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
);
}
}
}
/**
* Walk the tree for a particular entity association
*/
private final void walkEntityAssociationTree(
final AssociationType associationType,
final OuterJoinLoadable persister,
final int propertyNumber,
final String alias,
final String 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);
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
);
}
/**
* For an entity class, add to a list of associations to be fetched
* by outerjoin
*/
private final void walkEntityTree(
final OuterJoinLoadable persister,
final String alias,
final String path,
final int currentDepth)
throws MappingException {
int n = persister.countSubclassProperties();
for ( int i=0; i= 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 String 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;
}
public boolean equals(Object other) {
AssociationKey that = (AssociationKey) other;
return that.table.equals(table) && Arrays.equals(columns, that.columns);
}
public int hashCode() {
return table.hashCode(); //TODO: inefficient
}
}
/**
* 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;
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 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()==JoinFragment.LEFT_OUTER_JOIN && oj.getJoinable().isCollection() ) {
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() == 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();
}
/**
* 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= 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 (
i0
) {
buf.append(", ");
}
}
return buf.toString();
}
}
}