org.hibernate.loader.JoinWalker Maven / Gradle / Ivy
//$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();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy