JoinWalkerpublic class JoinWalker extends Object Walks the metamodel, searching for joins, and collecting
together information needed by OuterJoinLoader. |
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 |
Methods Summary |
---|
private void | addAssociationToJoinTree(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 void | addAssociationToJoinTreeIfNecessary(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 int | countCollectionPersisters(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 int | countEntityPersisters(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.String | generateRootAlias(java.lang.String description)
return StringHelper.generateAlias(description, 0);
| protected java.lang.String | generateTableAlias(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.Dialect | getDialect()
return factory.getDialect();
| protected java.util.Map | getEnabledFilters()
return enabledFilters;
| protected org.hibernate.engine.SessionFactoryImplementor | getFactory()
return factory;
| protected int | getJoinType(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 int | getJoinType(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.String | getSQLString()
return sql;
| public java.lang.String[] | getSuffixes()
return suffixes;
| protected void | initPersisters(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 boolean | isDuplicateAssociation(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 boolean | isDuplicateAssociation(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 boolean | isJoinable(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 boolean | isJoinedFetchEnabled(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 boolean | isJoinedFetchEnabledInMapping(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 boolean | isTooDeep(int currentDepth)
Integer maxFetchDepth = getFactory().getSettings().getMaximumFetchDepth();
return maxFetchDepth!=null && currentDepth >= maxFetchDepth.intValue();
| protected boolean | isTooManyCollections()
return false;
| protected static java.lang.String | mergeOrderings(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.JoinFragment | mergeOuterJoins(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.String | orderBy(java.util.List associations, java.lang.String orderBy)
return mergeOrderings( orderBy( associations ), orderBy );
| protected static final java.lang.String | orderBy(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.String | selectString(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 void | setAliases(java.lang.String[] aliases)
this.aliases = aliases;
| public void | setCollectionOwners(int[] collectionOwners)
this.collectionOwners = collectionOwners;
| public void | setCollectionPersisters(org.hibernate.persister.collection.CollectionPersister[] collectionPersisters)
this.collectionPersisters = collectionPersisters;
| public void | setCollectionSuffixes(java.lang.String[] collectionSuffixes)
this.collectionSuffixes = collectionSuffixes;
| public void | setLockModeArray(org.hibernate.LockMode[] lockModeArray)
this.lockModeArray = lockModeArray;
| public void | setOwnerAssociationTypes(org.hibernate.type.EntityType[] ownerAssociationType)
this.ownerAssociationTypes = ownerAssociationType;
| public void | setOwners(int[] owners)
this.owners = owners;
| public void | setPersisters(org.hibernate.persister.entity.Loadable[] persisters)
this.persisters = persisters;
| public void | setSql(java.lang.String sql)
this.sql = sql;
| public void | setSuffixes(java.lang.String[] suffixes)
this.suffixes = suffixes;
| private static java.lang.String | subPath(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 void | walkCollectionTree(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 void | walkCollectionTree(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 void | walkComponentTree(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 void | walkCompositeElementTree(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 void | walkEntityAssociationTree(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 void | walkEntityTree(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 void | walkEntityTree(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.StringBuffer | whereString(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;
}
|
|