HHH-7841 - Introduce LoadPlan

This commit is contained in:
Steve Ebersole 2013-05-31 01:14:18 -05:00
parent d874bc4737
commit af5e8c3869
16 changed files with 665 additions and 151 deletions

View File

@ -225,7 +225,8 @@ public class LoadQueryAliasResolutionContextImpl implements LoadQueryAliasResolu
else { else {
throw new NotYetImplementedException( "Cannot determine LHS alias for FetchOwner." ); throw new NotYetImplementedException( "Cannot determine LHS alias for FetchOwner." );
} }
final String[] aliasedLhsColumnNames = StringHelper.qualify( lhsAlias, currentFetch.getColumnNames() );
final String[] aliasedLhsColumnNames = currentFetch.toSqlSelectFragments( lhsAlias );
final String rhsAlias; final String rhsAlias;
if ( EntityReference.class.isInstance( currentFetch ) ) { if ( EntityReference.class.isInstance( currentFetch ) ) {
rhsAlias = resolveEntityTableAlias( (EntityReference) currentFetch ); rhsAlias = resolveEntityTableAlias( (EntityReference) currentFetch );

View File

@ -25,7 +25,6 @@ package org.hibernate.loader.plan.internal;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStrategy;
import org.hibernate.loader.plan.spi.AbstractFetchOwner;
import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CollectionFetch;
import org.hibernate.loader.plan.spi.CompositeFetch; import org.hibernate.loader.plan.spi.CompositeFetch;
import org.hibernate.loader.plan.spi.EntityFetch; import org.hibernate.loader.plan.spi.EntityFetch;
@ -33,7 +32,6 @@ import org.hibernate.loader.plan.spi.FetchOwner;
import org.hibernate.loader.plan.spi.build.LoadPlanBuildingContext; import org.hibernate.loader.plan.spi.build.LoadPlanBuildingContext;
import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition;
import org.hibernate.persister.walking.spi.CompositionDefinition; import org.hibernate.persister.walking.spi.CompositionDefinition;
import org.hibernate.type.EntityType;
/** /**
* @author Steve Ebersole * @author Steve Ebersole

View File

@ -108,17 +108,17 @@ public abstract class AbstractFetchOwner extends AbstractPlanNode implements Fet
@Override @Override
public boolean isNullable(Fetch fetch) { public boolean isNullable(Fetch fetch) {
return getFetchOwnerDelegate().isNullable( fetch ); return getFetchOwnerDelegate().locateFetchMetadata( fetch ).isNullable();
} }
@Override @Override
public Type getType(Fetch fetch) { public Type getType(Fetch fetch) {
return getFetchOwnerDelegate().getType( fetch ); return getFetchOwnerDelegate().locateFetchMetadata( fetch ).getType();
} }
@Override @Override
public String[] getColumnNames(Fetch fetch) { public String[] toSqlSelectFragments(Fetch fetch, String alias) {
return getFetchOwnerDelegate().getColumnNames( fetch ); return getFetchOwnerDelegate().locateFetchMetadata( fetch ).toSqlSelectFragments( alias );
} }
@Override @Override

View File

@ -0,0 +1,51 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, 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.plan.spi;
import java.util.HashMap;
import java.util.Map;
/**
* Base implementation of FetchOwnerDelegate providing caching of FetchMetadata.
*
* @author Steve Ebersole
*/
public abstract class AbstractFetchOwnerDelegate implements FetchOwnerDelegate {
private Map<String,FetchMetadata> fetchMetadataMap;
@Override
public FetchMetadata locateFetchMetadata(Fetch fetch) {
FetchMetadata metadata = fetchMetadataMap == null ? null : fetchMetadataMap.get( fetch.getOwnerPropertyName() );
if ( metadata == null ) {
if ( fetchMetadataMap == null ) {
fetchMetadataMap = new HashMap<String, FetchMetadata>();
}
metadata = buildFetchMetadata( fetch );
fetchMetadataMap.put( fetch.getOwnerPropertyName(), metadata );
}
return metadata;
}
protected abstract FetchMetadata buildFetchMetadata(Fetch fetch);
}

View File

@ -92,8 +92,8 @@ public abstract class AbstractSingularAttributeFetch extends AbstractFetchOwner
} }
@Override @Override
public String[] getColumnNames() { public String[] toSqlSelectFragments(String alias) {
return owner.getColumnNames( this ); return owner.toSqlSelectFragments( this, alias );
} }
@Override @Override

View File

@ -28,11 +28,8 @@ import java.sql.SQLException;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.internal.JoinHelper;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.loader.spi.ResultSetProcessingContext; import org.hibernate.loader.spi.ResultSetProcessingContext;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.type.CollectionType;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
@ -82,8 +79,8 @@ public class CollectionFetch extends AbstractCollectionReference implements Fetc
} }
@Override @Override
public String[] getColumnNames() { public String[] toSqlSelectFragments(String alias) {
return getOwner().getColumnNames( this ); return getOwner().toSqlSelectFragments( this, alias );
} }
@Override @Override

View File

@ -42,7 +42,12 @@ public class CompositeElementGraph extends AbstractFetchOwner implements Fetchab
this.fetchOwnerDelegate = new CompositeFetchOwnerDelegate( this.fetchOwnerDelegate = new CompositeFetchOwnerDelegate(
sessionFactory, sessionFactory,
(CompositeType) collectionPersister.getElementType(), (CompositeType) collectionPersister.getElementType(),
( (QueryableCollection) collectionPersister ).getElementColumnNames() new CompositeFetchOwnerDelegate.PropertyMappingDelegate() {
@Override
public String[] toSqlSelectFragments(String alias) {
return ( (QueryableCollection) collectionPersister ).getElementColumnNames( alias );
}
}
); );
} }

View File

@ -41,7 +41,7 @@ import org.hibernate.type.CompositeType;
* @author Gail Badner * @author Gail Badner
*/ */
public class CompositeFetch extends AbstractSingularAttributeFetch { public class CompositeFetch extends AbstractSingularAttributeFetch {
public static final FetchStrategy FETCH_PLAN = new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.JOIN ); private static final FetchStrategy FETCH_PLAN = new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.JOIN );
private final FetchOwnerDelegate delegate; private final FetchOwnerDelegate delegate;
@ -54,13 +54,18 @@ public class CompositeFetch extends AbstractSingularAttributeFetch {
*/ */
public CompositeFetch( public CompositeFetch(
SessionFactoryImplementor sessionFactory, SessionFactoryImplementor sessionFactory,
FetchOwner owner, final FetchOwner owner,
String ownerProperty) { String ownerProperty) {
super( sessionFactory, owner, ownerProperty, FETCH_PLAN ); super( sessionFactory, owner, ownerProperty, FETCH_PLAN );
this.delegate = new CompositeFetchOwnerDelegate( this.delegate = new CompositeFetchOwnerDelegate(
sessionFactory, sessionFactory,
(CompositeType) getOwner().getType( this ), (CompositeType) getOwner().getType( this ),
getOwner().getColumnNames( this ) new CompositeFetchOwnerDelegate.PropertyMappingDelegate() {
@Override
public String[] toSqlSelectFragments(String alias) {
return owner.toSqlSelectFragments( CompositeFetch.this, alias );
}
}
); );
} }

View File

@ -26,7 +26,6 @@ package org.hibernate.loader.plan.spi;
import java.util.Arrays; import java.util.Arrays;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.persister.walking.spi.WalkingException; import org.hibernate.persister.walking.spi.WalkingException;
import org.hibernate.type.CompositeType; import org.hibernate.type.CompositeType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -36,72 +35,109 @@ import org.hibernate.type.Type;
* owned sub-attribute fetch. * owned sub-attribute fetch.
* *
* @author Gail Badner * @author Gail Badner
* @author Steve Ebersole
*/ */
public class CompositeFetchOwnerDelegate implements FetchOwnerDelegate { public class CompositeFetchOwnerDelegate extends AbstractFetchOwnerDelegate implements FetchOwnerDelegate {
private final SessionFactoryImplementor sessionFactory; private final SessionFactoryImplementor sessionFactory;
private final CompositeType compositeType; private final CompositeType compositeType;
private final String[] columnNames; private final PropertyMappingDelegate propertyMappingDelegate;
/** /**
* Constructs a {@link CompositeFetchOwnerDelegate}. * Constructs a CompositeFetchOwnerDelegate
*
* @param sessionFactory - the session factory. * @param sessionFactory - the session factory.
* @param compositeType - the composite type. * @param compositeType - the composite type.
* @param columnNames - the column names used by sub-attribute fetches. * @param propertyMappingDelegate - delegate for handling property mapping
*/ */
public CompositeFetchOwnerDelegate( public CompositeFetchOwnerDelegate(
SessionFactoryImplementor sessionFactory, SessionFactoryImplementor sessionFactory,
CompositeType compositeType, CompositeType compositeType,
String[] columnNames) { PropertyMappingDelegate propertyMappingDelegate) {
this.sessionFactory = sessionFactory; this.sessionFactory = sessionFactory;
this.compositeType = compositeType; this.compositeType = compositeType;
this.columnNames = columnNames; this.propertyMappingDelegate = propertyMappingDelegate;
}
public static interface PropertyMappingDelegate {
public String[] toSqlSelectFragments(String alias);
} }
@Override @Override
public boolean isNullable(Fetch fetch) { protected FetchMetadata buildFetchMetadata(Fetch fetch) {
return compositeType.getPropertyNullability()[ determinePropertyIndex( fetch ) ]; int subIndex = -1;
} int selectFragmentRangeStart = 0;
int selectFragmentRangeEnd = -1;
@Override for ( int i = 0; i < compositeType.getPropertyNames().length; i++ ) {
public Type getType(Fetch fetch) { final Type type = compositeType.getSubtypes()[i];
return compositeType.getSubtypes()[ determinePropertyIndex( fetch ) ]; final int typeColSpan = type.getColumnSpan( sessionFactory );
}
@Override
public String[] getColumnNames(Fetch fetch) {
// TODO: probably want to cache this
int begin = 0;
String[] subColumnNames = null;
for ( int i = 0; i < compositeType.getSubtypes().length; i++ ) {
final int columnSpan = compositeType.getSubtypes()[i].getColumnSpan( sessionFactory );
subColumnNames = ArrayHelper.slice( columnNames, begin, columnSpan );
if ( compositeType.getPropertyNames()[ i ].equals( fetch.getOwnerPropertyName() ) ) { if ( compositeType.getPropertyNames()[ i ].equals( fetch.getOwnerPropertyName() ) ) {
// fount it!
subIndex = i;
selectFragmentRangeEnd = selectFragmentRangeStart + typeColSpan;
break; break;
} }
begin += columnSpan; selectFragmentRangeStart += typeColSpan;
}
return subColumnNames;
} }
private int determinePropertyIndex(Fetch fetch) { if ( subIndex < 0 ) {
// TODO: probably want to cache this
final String[] subAttributeNames = compositeType.getPropertyNames();
int subAttributeIndex = -1;
for ( int i = 0; i < subAttributeNames.length ; i++ ) {
if ( subAttributeNames[ i ].equals( fetch.getOwnerPropertyName() ) ) {
subAttributeIndex = i;
break;
}
}
if ( subAttributeIndex == -1 ) {
throw new WalkingException( throw new WalkingException(
String.format( String.format(
"Owner property [%s] not found in composite properties [%s]", "Owner property [%s] not found in composite properties [%s]",
fetch.getOwnerPropertyName(), fetch.getOwnerPropertyName(),
Arrays.asList( subAttributeNames ) Arrays.asList( compositeType.getPropertyNames() )
) )
); );
} }
return subAttributeIndex;
return new FetchMetadataImpl(
compositeType,
subIndex,
propertyMappingDelegate,
selectFragmentRangeStart,
selectFragmentRangeEnd
);
// todo : we really need a PropertyMapping delegate which can encapsulate both the PropertyMapping and the path
}
private static class FetchMetadataImpl implements FetchMetadata {
private final CompositeType compositeType;
private final int index;
private final PropertyMappingDelegate propertyMappingDelegate;
private final int selectFragmentRangeStart;
private final int selectFragmentRangeEnd;
public FetchMetadataImpl(
CompositeType compositeType,
int index,
PropertyMappingDelegate propertyMappingDelegate,
int selectFragmentRangeStart,
int selectFragmentRangeEnd) {
this.compositeType = compositeType;
this.index = index;
this.propertyMappingDelegate = propertyMappingDelegate;
this.selectFragmentRangeStart = selectFragmentRangeStart;
this.selectFragmentRangeEnd = selectFragmentRangeEnd;
}
@Override
public boolean isNullable() {
return compositeType.getPropertyNullability()[ index ];
}
@Override
public Type getType() {
return compositeType.getSubtypes()[ index ];
}
@Override
public String[] toSqlSelectFragments(String alias) {
return Arrays.copyOfRange(
propertyMappingDelegate.toSqlSelectFragments( alias ),
selectFragmentRangeStart,
selectFragmentRangeEnd
);
}
} }
} }

View File

@ -41,7 +41,12 @@ public class CompositeIndexGraph extends AbstractFetchOwner implements Fetchable
this.fetchOwnerDelegate = new CompositeFetchOwnerDelegate( this.fetchOwnerDelegate = new CompositeFetchOwnerDelegate(
sessionFactory, sessionFactory,
(CompositeType) collectionPersister.getIndexType(), (CompositeType) collectionPersister.getIndexType(),
( (QueryableCollection) collectionPersister ).getIndexColumnNames() new CompositeFetchOwnerDelegate.PropertyMappingDelegate() {
@Override
public String[] toSqlSelectFragments(String alias) {
return ( (QueryableCollection) collectionPersister ).getIndexColumnNames( alias );
}
}
); );
} }

View File

@ -23,10 +23,14 @@
*/ */
package org.hibernate.loader.plan.spi; package org.hibernate.loader.plan.spi;
import org.hibernate.engine.internal.JoinHelper; import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.persister.entity.Queryable;
import org.hibernate.type.AssociationType; import org.hibernate.persister.walking.spi.WalkingException;
import org.hibernate.type.CompositeType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
/** /**
@ -34,8 +38,9 @@ import org.hibernate.type.Type;
* an owned attribute fetch. * an owned attribute fetch.
* *
* @author Gail Badner * @author Gail Badner
* @author Steve Ebersole
*/ */
public class EntityFetchOwnerDelegate implements FetchOwnerDelegate { public class EntityFetchOwnerDelegate extends AbstractFetchOwnerDelegate implements FetchOwnerDelegate {
private final EntityPersister entityPersister; private final EntityPersister entityPersister;
/** /**
@ -48,35 +53,149 @@ public class EntityFetchOwnerDelegate implements FetchOwnerDelegate {
} }
@Override @Override
public boolean isNullable(Fetch fetch) { protected FetchMetadata buildFetchMetadata(Fetch fetch) {
return entityPersister.getPropertyNullability()[ determinePropertyIndex( fetch ) ]; final Integer propertyIndex = entityPersister.getEntityMetamodel().getPropertyIndexOrNull(
} fetch.getOwnerPropertyName()
);
@Override if ( propertyIndex == null ) {
public Type getType(Fetch fetch) { // possibly it is part of the identifier; but that's only possible if the identifier is composite
return entityPersister.getPropertyTypes()[ determinePropertyIndex( fetch ) ]; final Type idType = entityPersister.getIdentifierType();
} if ( CompositeType.class.isInstance( idType ) ) {
final CompositeType cidType = (CompositeType) idType;
@Override if ( entityPersister.hasIdentifierProperty() ) {
public String[] getColumnNames(Fetch fetch) { // encapsulated composite id case; this *should have* been handled as part of building the fetch...
// TODO: cache this info throw new WalkingException(
final OuterJoinLoadable outerJoinLoadable = (OuterJoinLoadable) entityPersister; "Expecting different FetchOwnerDelegate type for encapsulated composite id case"
Type fetchType = getType( fetch );
if ( fetchType.isAssociationType() ) {
return JoinHelper.getLHSColumnNames(
(AssociationType) fetchType,
determinePropertyIndex( fetch ),
outerJoinLoadable,
outerJoinLoadable.getFactory()
); );
} }
else { else {
return outerJoinLoadable.getPropertyColumnNames( determinePropertyIndex( fetch ) ); // non-encapsulated composite id case...
return new NonEncapsulatedIdentifierAttributeFetchMetadata(
entityPersister,
cidType,
fetch.getOwnerPropertyName()
);
}
}
}
else {
return new NonIdentifierAttributeFetchMetadata(
entityPersister,
fetch.getOwnerPropertyName(),
propertyIndex.intValue()
);
}
throw new WalkingException(
"Could not locate metadata about given fetch [" + fetch + "] in its owning persister"
);
}
private class NonIdentifierAttributeFetchMetadata implements FetchMetadata {
private final EntityPersister entityPersister;
private final String attributeName;
private final int propertyIndex;
private Type attributeType;
public NonIdentifierAttributeFetchMetadata(
EntityPersister entityPersister,
String attributeName,
int propertyIndex) {
this.entityPersister = entityPersister;
this.attributeName = attributeName;
this.propertyIndex = propertyIndex;
}
@Override
public boolean isNullable() {
return entityPersister.getPropertyNullability()[ propertyIndex ];
}
@Override
public Type getType() {
if ( attributeType == null ) {
attributeType = entityPersister.getPropertyTypes()[ propertyIndex ];
}
return attributeType;
}
@Override
public String[] toSqlSelectFragments(String alias) {
// final Type type = getType();
// final OuterJoinLoadable outerJoinLoadable = (OuterJoinLoadable) entityPersister;
final Queryable queryable = (Queryable) entityPersister;
return queryable.toColumns( alias, attributeName );
// if ( type.isAssociationType() ) {
// return JoinHelper.getLHSColumnNames(
// (AssociationType) type,
// propertyIndex,
// outerJoinLoadable,
// outerJoinLoadable.getFactory()
// );
// }
// else {
// return outerJoinLoadable.getPropertyColumnNames( propertyIndex );
// }
} }
} }
private int determinePropertyIndex(Fetch fetch) { private class NonEncapsulatedIdentifierAttributeFetchMetadata implements FetchMetadata {
// TODO: cache this info private final EntityPersister entityPersister;
return entityPersister.getEntityMetamodel().getPropertyIndex( fetch.getOwnerPropertyName() ); private final String attributeName;
// virtually final fields
private Type type;
private int selectFragmentRangeStart;
private int selectFragmentRangeEnd;
public NonEncapsulatedIdentifierAttributeFetchMetadata(
EntityPersister entityPersister,
CompositeType cidType,
String attributeName) {
this.entityPersister = entityPersister;
this.attributeName = attributeName;
this.selectFragmentRangeStart = 0;
Type subType;
boolean foundIt = false;
for ( int i = 0; i < cidType.getPropertyNames().length; i++ ) {
subType = cidType.getSubtypes()[i];
if ( cidType.getPropertyNames()[i].equals( attributeName ) ) {
// found it!
foundIt = true;
this.type = subType;
break;
}
selectFragmentRangeStart += subType.getColumnSpan( entityPersister.getFactory() );
}
if ( !foundIt ) {
throw new WalkingException( "Could not find " );
}
selectFragmentRangeEnd = selectFragmentRangeStart + type.getColumnSpan( entityPersister.getFactory() );
}
@Override
public boolean isNullable() {
return false;
}
@Override
public Type getType() {
return type;
}
@Override
public String[] toSqlSelectFragments(String alias) {
return Arrays.copyOfRange(
( (Queryable) entityPersister ).toColumns( alias, attributeName ),
selectFragmentRangeStart,
selectFragmentRangeEnd
);
}
} }
} }

View File

@ -60,11 +60,11 @@ public interface Fetch extends CopyableFetch {
public boolean isNullable(); public boolean isNullable();
/** /**
* Gets the column names used for this fetch. * Generates the SQL select fragments for this fetch. A select fragment is the column and formula references.
* *
* @return the column names used for this fetch. * @return the select fragments
*/ */
public String[] getColumnNames(); public String[] toSqlSelectFragments(String alias);
/** /**
* Gets the fetch strategy for this fetch. * Gets the fetch strategy for this fetch.

View File

@ -76,13 +76,15 @@ public interface FetchOwner {
public boolean isNullable(Fetch fetch); public boolean isNullable(Fetch fetch);
/** /**
* Returns the column names used for loading the specified fetch. * Generates the SQL select fragments for the specified fetch. A select fragment is the column and formula
* references.
* *
* @param fetch - the owned fetch. * @param fetch - the owned fetch.
* @param alias The table alias to apply to the fragments (used to qualify column references)
* *
* @return the column names used for loading the specified fetch. * @return the select fragments
*/ */
public String[] getColumnNames(Fetch fetch); public String[] toSqlSelectFragments(Fetch fetch, String alias);
/** /**
* Is the asserted plan valid from this owner to a fetch? * Is the asserted plan valid from this owner to a fetch?

View File

@ -31,31 +31,39 @@ import org.hibernate.type.Type;
* @author Gail Badner * @author Gail Badner
*/ */
public interface FetchOwnerDelegate { public interface FetchOwnerDelegate {
public static interface FetchMetadata {
/** /**
* Is the specified fetch nullable? * Is the fetch nullable?
*
* @param fetch - the owned fetch.
* *
* @return true, if the fetch is nullable; false, otherwise. * @return true, if the fetch is nullable; false, otherwise.
*/ */
public boolean isNullable(Fetch fetch); public boolean isNullable();
/** /**
* Returns the type of the specified fetch. * Returns the type of the fetched attribute
* *
* @param fetch - the owned fetch. * @return the type of the fetched attribute.
*
* @return the type of the specified fetch.
*/ */
public Type getType(Fetch fetch); public Type getType();
/** /**
* Returns the column names used for loading the specified fetch. * Generates the SQL select fragments for the specified fetch. A select fragment is the column and formula
* references.
* *
* @param fetch - the owned fetch. * @param alias The table alias to apply to the fragments (used to qualify column references)
* *
* @return the column names used for loading the specified fetch. * @return the select fragments
*/ */
public String[] getColumnNames(Fetch fetch); public String[] toSqlSelectFragments(String alias);
}
/**
* Locate the metadata for the specified Fetch. Allows easier caching of the resolved information.
*
* @param fetch The fetch for which to locate metadata
*
* @return The metadata; never {@code null}, rather an exception is thrown if the information for the fetch cannot
* be located.
*/
public FetchMetadata locateFetchMetadata(Fetch fetch);
} }

View File

@ -43,6 +43,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.loader.PropertyPath; import org.hibernate.loader.PropertyPath;
import org.hibernate.loader.plan.spi.AbstractFetchOwner; import org.hibernate.loader.plan.spi.AbstractFetchOwner;
import org.hibernate.loader.plan.spi.AbstractFetchOwnerDelegate;
import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CollectionFetch;
import org.hibernate.loader.plan.spi.CollectionReference; import org.hibernate.loader.plan.spi.CollectionReference;
import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.CollectionReturn;
@ -573,16 +574,17 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder
@Override @Override
public Type getType(Fetch fetch) { public Type getType(Fetch fetch) {
if ( !fetch.getOwnerPropertyName().equals( entityReference.getEntityPersister().getIdentifierPropertyName() ) ) { return getFetchOwnerDelegate().locateFetchMetadata( fetch ).getType();
throw new IllegalArgumentException(
String.format(
"Fetch owner property name [%s] is not the same as the identifier property name [%s].",
fetch.getOwnerPropertyName(),
entityReference.getEntityPersister().getIdentifierPropertyName()
)
);
} }
return super.getType( fetch );
@Override
public boolean isNullable(Fetch fetch) {
return getFetchOwnerDelegate().locateFetchMetadata( fetch ).isNullable();
}
@Override
public String[] toSqlSelectFragments(Fetch fetch, String alias) {
return getFetchOwnerDelegate().locateFetchMetadata( fetch ).toSqlSelectFragments( alias );
} }
@Override @Override
@ -620,21 +622,15 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder
final EntityReference entityReference) { final EntityReference entityReference) {
super( sessionFactory, entityReference ); super( sessionFactory, entityReference );
this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath(); this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath();
this.delegate = new AbstractFetchOwnerDelegate() {
final boolean isCompositeType = entityReference.getEntityPersister().getIdentifierType().isComponentType(); final boolean isCompositeType = entityReference.getEntityPersister().getIdentifierType().isComponentType();
this.delegate = new FetchOwnerDelegate() {
@Override
public boolean isNullable(Fetch fetch) {
if ( !isCompositeType ) {
throw new IllegalStateException( "Non-composite ID cannot have fetches." );
}
return true;
}
@Override @Override
public Type getType(Fetch fetch) { protected FetchMetadata buildFetchMetadata(Fetch fetch) {
if ( !isCompositeType ) { if ( !isCompositeType ) {
throw new IllegalStateException( "Non-composite ID cannot have fetches." ); throw new WalkingException( "Non-composite identifier cannot be a fetch owner" );
} }
if ( !fetch.getOwnerPropertyName().equals( entityReference.getEntityPersister().getIdentifierPropertyName() ) ) { if ( !fetch.getOwnerPropertyName().equals( entityReference.getEntityPersister().getIdentifierPropertyName() ) ) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
String.format( String.format(
@ -644,15 +640,24 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder
) )
); );
} }
return new FetchMetadata() {
@Override
public boolean isNullable() {
return false;
}
@Override
public Type getType() {
return entityReference.getEntityPersister().getIdentifierType(); return entityReference.getEntityPersister().getIdentifierType();
} }
@Override @Override
public String[] getColumnNames(Fetch fetch) { public String[] toSqlSelectFragments(String alias) {
if ( !isCompositeType ) { // should not ever be called iiuc...
throw new IllegalStateException( "Non-composite ID cannot have fetches." ); throw new WalkingException( "Should not be called" );
} }
return ( (Loadable) entityReference.getEntityPersister() ).getIdentifierColumnNames(); };
} }
}; };
} }
@ -681,16 +686,65 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder
private final PropertyPath propertyPath; private final PropertyPath propertyPath;
private final FetchOwnerDelegate fetchOwnerDelegate; private final FetchOwnerDelegate fetchOwnerDelegate;
public NonEncapsulatedIdentifierAttributeCollector(SessionFactoryImplementor sessionfactory, EntityReference entityReference) { public NonEncapsulatedIdentifierAttributeCollector(
final SessionFactoryImplementor sessionfactory,
final EntityReference entityReference) {
super( sessionfactory, entityReference ); super( sessionfactory, entityReference );
this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath().append( "<id>" ); this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath().append( "<id>" );
this.fetchOwnerDelegate = new CompositeFetchOwnerDelegate( this.fetchOwnerDelegate = new AbstractFetchOwnerDelegate() {
entityReference.getEntityPersister().getFactory(), final boolean isCompositeType = entityReference.getEntityPersister().getIdentifierType().isComponentType();
(CompositeType) entityReference.getEntityPersister().getIdentifierType(), final CompositeType idType = (CompositeType) entityReference.getEntityPersister().getIdentifierType();
( (Loadable) entityReference.getEntityPersister() ).getIdentifierColumnNames()
@Override
protected FetchMetadata buildFetchMetadata(Fetch fetch) {
if ( !isCompositeType ) {
throw new WalkingException( "Non-composite identifier cannot be a fetch owner" );
}
final int subPropertyIndex = locateSubPropertyIndex( idType, fetch.getOwnerPropertyName() );
return new FetchMetadata() {
final Type subType = idType.getSubtypes()[ subPropertyIndex ];
@Override
public boolean isNullable() {
return false;
}
@Override
public Type getType() {
return subType;
}
@Override
public String[] toSqlSelectFragments(String alias) {
// should not ever be called iiuc...
throw new WalkingException( "Should not be called" );
}
};
}
private int locateSubPropertyIndex(CompositeType idType, String ownerPropertyName) {
for ( int i = 0; i < idType.getPropertyNames().length; i++ ) {
if ( ownerPropertyName.equals( idType.getPropertyNames()[i] ) ) {
return i;
}
}
// does not bode well if we get here...
throw new IllegalStateException(
String.format(
"Unable to locate fetched attribute [%s] as part of composite identifier [%s]",
ownerPropertyName,
getPropertyPath().getFullPath()
)
); );
} }
};
}
@Override @Override
protected IdentifierDescription buildIdentifierDescription() { protected IdentifierDescription buildIdentifierDescription() {
return new IdentifierDescriptionImpl( return new IdentifierDescriptionImpl(
@ -710,6 +764,7 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder
protected FetchOwnerDelegate getFetchOwnerDelegate() { protected FetchOwnerDelegate getFetchOwnerDelegate() {
return fetchOwnerDelegate; return fetchOwnerDelegate;
} }
} }
private static class IdentifierDescriptionImpl implements IdentifierDescription { private static class IdentifierDescriptionImpl implements IdentifierDescription {

View File

@ -0,0 +1,232 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, 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.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.jdbc.Work;
import org.hibernate.loader.internal.EntityLoadQueryBuilderImpl;
import org.hibernate.loader.internal.LoadQueryAliasResolutionContextImpl;
import org.hibernate.loader.internal.ResultSetProcessorImpl;
import org.hibernate.loader.plan.internal.SingleRootReturnLoadPlanBuilderStrategy;
import org.hibernate.loader.plan.spi.LoadPlan;
import org.hibernate.loader.plan.spi.build.LoadPlanBuilder;
import org.hibernate.loader.spi.LoadQueryAliasResolutionContext;
import org.hibernate.loader.spi.NamedParameterContext;
import org.hibernate.loader.spi.NoOpLoadPlanAdvisor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.Type;
import org.junit.Test;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.test.onetoone.formula.Address;
import org.hibernate.test.onetoone.formula.Person;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author Steve Ebersole
*/
public class NonEncapsulatedCompositeIdResultSetProcessorTest extends BaseCoreFunctionalTestCase {
@Override
protected String[] getMappings() {
return new String[] { "onetoone/formula/Person.hbm.xml" };
}
@Test
public void testCompositeIdWithKeyManyToOne() throws Exception {
final String personId = "John Doe";
Person p = new Person();
p.setName( personId );
final Address a = new Address();
a.setPerson( p );
p.setAddress( a );
a.setType( "HOME" );
a.setStreet( "Main St" );
a.setState( "Sweet Home Alabama" );
a.setZip( "3181" );
Session s = openSession();
Transaction t = s.beginTransaction();
s.persist( p );
t.commit();
s.close();
final EntityPersister personPersister = sessionFactory().getEntityPersister( Person.class.getName() );
final EntityPersister addressPersister = sessionFactory().getEntityPersister( Address.class.getName() );
{
final List results = getResults(
addressPersister,
new Callback() {
@Override
public void bind(PreparedStatement ps) throws SQLException {
ps.setString( 1, personId );
ps.setString( 2, "HOME" );
}
@Override
public QueryParameters getQueryParameters() {
QueryParameters qp = new QueryParameters();
qp.setPositionalParameterTypes( new Type[] { addressPersister.getIdentifierType() } );
qp.setPositionalParameterValues( new Object[] { a } );
qp.setOptionalObject( a );
qp.setOptionalEntityName( addressPersister.getEntityName() );
qp.setOptionalId( a );
qp.setLockOptions( LockOptions.NONE );
return qp;
}
}
);
assertEquals( 1, results.size() );
Object result = results.get( 0 );
assertNotNull( result );
}
// test loading the Person (the entity with normal id def, but mixed composite fk to Address)
{
final List results = getResults(
personPersister,
new Callback() {
@Override
public void bind(PreparedStatement ps) throws SQLException {
ps.setString( 1, personId );
}
@Override
public QueryParameters getQueryParameters() {
QueryParameters qp = new QueryParameters();
qp.setPositionalParameterTypes( new Type[] { personPersister.getIdentifierType() } );
qp.setPositionalParameterValues( new Object[] { personId } );
qp.setOptionalObject( null );
qp.setOptionalEntityName( personPersister.getEntityName() );
qp.setOptionalId( personId );
qp.setLockOptions( LockOptions.NONE );
return qp;
}
}
);
assertEquals( 1, results.size() );
Object result = results.get( 0 );
assertNotNull( result );
}
// CardField cardFieldWork = ExtraAssertions.assertTyping( CardField.class, result );
// assertEquals( cardFieldGotten, cardFieldWork );
// clean up test data
s = openSession();
s.beginTransaction();
s.createQuery( "delete Address" ).executeUpdate();
s.createQuery( "delete Person" ).executeUpdate();
s.getTransaction().commit();
s.close();
}
private List getResults(final EntityPersister entityPersister, final Callback callback) {
final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy(
sessionFactory(),
LoadQueryInfluencers.NONE
);
final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister );
final LoadQueryAliasResolutionContext aliasResolutionContext =
new LoadQueryAliasResolutionContextImpl(
sessionFactory(),
0,
Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } )
);
final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl(
LoadQueryInfluencers.NONE,
plan
);
final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext );
final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan );
final List results = new ArrayList();
final Session workSession = openSession();
workSession.beginTransaction();
workSession.doWork(
new Work() {
@Override
public void execute(Connection connection) throws SQLException {
System.out.println( "SQL : " + sql );
PreparedStatement ps = connection.prepareStatement( sql );
callback.bind( ps );
ResultSet resultSet = ps.executeQuery();
//callback.beforeExtractResults( workSession );
results.addAll(
resultSetProcessor.extractResults(
NoOpLoadPlanAdvisor.INSTANCE,
resultSet,
(SessionImplementor) workSession,
callback.getQueryParameters(),
new NamedParameterContext() {
@Override
public int[] getNamedParameterLocations(String name) {
return new int[0];
}
},
aliasResolutionContext,
true,
false,
null,
null
)
);
resultSet.close();
ps.close();
}
}
);
workSession.getTransaction().commit();
workSession.close();
return results;
}
private interface Callback {
void bind(PreparedStatement ps) throws SQLException;
QueryParameters getQueryParameters();
}
}