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 {
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;
if ( EntityReference.class.isInstance( currentFetch ) ) {
rhsAlias = resolveEntityTableAlias( (EntityReference) currentFetch );

View File

@ -25,7 +25,6 @@ package org.hibernate.loader.plan.internal;
import org.hibernate.LockMode;
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.CompositeFetch;
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.persister.walking.spi.AssociationAttributeDefinition;
import org.hibernate.persister.walking.spi.CompositionDefinition;
import org.hibernate.type.EntityType;
/**
* @author Steve Ebersole

View File

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

View File

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

View File

@ -42,7 +42,12 @@ public class CompositeElementGraph extends AbstractFetchOwner implements Fetchab
this.fetchOwnerDelegate = new CompositeFetchOwnerDelegate(
sessionFactory,
(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
*/
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;
@ -54,13 +54,18 @@ public class CompositeFetch extends AbstractSingularAttributeFetch {
*/
public CompositeFetch(
SessionFactoryImplementor sessionFactory,
FetchOwner owner,
final FetchOwner owner,
String ownerProperty) {
super( sessionFactory, owner, ownerProperty, FETCH_PLAN );
this.delegate = new CompositeFetchOwnerDelegate(
sessionFactory,
(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 org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.persister.walking.spi.WalkingException;
import org.hibernate.type.CompositeType;
import org.hibernate.type.Type;
@ -36,72 +35,109 @@ import org.hibernate.type.Type;
* owned sub-attribute fetch.
*
* @author Gail Badner
* @author Steve Ebersole
*/
public class CompositeFetchOwnerDelegate implements FetchOwnerDelegate {
public class CompositeFetchOwnerDelegate extends AbstractFetchOwnerDelegate implements FetchOwnerDelegate {
private final SessionFactoryImplementor sessionFactory;
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 compositeType - the composite type.
* @param columnNames - the column names used by sub-attribute fetches.
* @param propertyMappingDelegate - delegate for handling property mapping
*/
public CompositeFetchOwnerDelegate(
SessionFactoryImplementor sessionFactory,
CompositeType compositeType,
String[] columnNames) {
PropertyMappingDelegate propertyMappingDelegate) {
this.sessionFactory = sessionFactory;
this.compositeType = compositeType;
this.columnNames = columnNames;
this.propertyMappingDelegate = propertyMappingDelegate;
}
public static interface PropertyMappingDelegate {
public String[] toSqlSelectFragments(String alias);
}
@Override
public boolean isNullable(Fetch fetch) {
return compositeType.getPropertyNullability()[ determinePropertyIndex( fetch ) ];
}
protected FetchMetadata buildFetchMetadata(Fetch fetch) {
int subIndex = -1;
int selectFragmentRangeStart = 0;
int selectFragmentRangeEnd = -1;
@Override
public Type getType(Fetch fetch) {
return compositeType.getSubtypes()[ determinePropertyIndex( fetch ) ];
}
@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 );
for ( int i = 0; i < compositeType.getPropertyNames().length; i++ ) {
final Type type = compositeType.getSubtypes()[i];
final int typeColSpan = type.getColumnSpan( sessionFactory );
if ( compositeType.getPropertyNames()[ i ].equals( fetch.getOwnerPropertyName() ) ) {
// fount it!
subIndex = i;
selectFragmentRangeEnd = selectFragmentRangeStart + typeColSpan;
break;
}
begin += columnSpan;
selectFragmentRangeStart += typeColSpan;
}
return subColumnNames;
}
private int determinePropertyIndex(Fetch fetch) {
// 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 ) {
if ( subIndex < 0 ) {
throw new WalkingException(
String.format(
"Owner property [%s] not found in composite properties [%s]",
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(
sessionFactory,
(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;
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.OuterJoinLoadable;
import org.hibernate.type.AssociationType;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.persister.walking.spi.WalkingException;
import org.hibernate.type.CompositeType;
import org.hibernate.type.Type;
/**
@ -34,8 +38,9 @@ import org.hibernate.type.Type;
* an owned attribute fetch.
*
* @author Gail Badner
* @author Steve Ebersole
*/
public class EntityFetchOwnerDelegate implements FetchOwnerDelegate {
public class EntityFetchOwnerDelegate extends AbstractFetchOwnerDelegate implements FetchOwnerDelegate {
private final EntityPersister entityPersister;
/**
@ -48,35 +53,149 @@ public class EntityFetchOwnerDelegate implements FetchOwnerDelegate {
}
@Override
public boolean isNullable(Fetch fetch) {
return entityPersister.getPropertyNullability()[ determinePropertyIndex( fetch ) ];
}
@Override
public Type getType(Fetch fetch) {
return entityPersister.getPropertyTypes()[ determinePropertyIndex( fetch ) ];
}
@Override
public String[] getColumnNames(Fetch fetch) {
// TODO: cache this info
final OuterJoinLoadable outerJoinLoadable = (OuterJoinLoadable) entityPersister;
Type fetchType = getType( fetch );
if ( fetchType.isAssociationType() ) {
return JoinHelper.getLHSColumnNames(
(AssociationType) fetchType,
determinePropertyIndex( fetch ),
outerJoinLoadable,
outerJoinLoadable.getFactory()
);
protected FetchMetadata buildFetchMetadata(Fetch fetch) {
final Integer propertyIndex = entityPersister.getEntityMetamodel().getPropertyIndexOrNull(
fetch.getOwnerPropertyName()
);
if ( propertyIndex == null ) {
// possibly it is part of the identifier; but that's only possible if the identifier is composite
final Type idType = entityPersister.getIdentifierType();
if ( CompositeType.class.isInstance( idType ) ) {
final CompositeType cidType = (CompositeType) idType;
if ( entityPersister.hasIdentifierProperty() ) {
// encapsulated composite id case; this *should have* been handled as part of building the fetch...
throw new WalkingException(
"Expecting different FetchOwnerDelegate type for encapsulated composite id case"
);
}
else {
// non-encapsulated composite id case...
return new NonEncapsulatedIdentifierAttributeFetchMetadata(
entityPersister,
cidType,
fetch.getOwnerPropertyName()
);
}
}
}
else {
return outerJoinLoadable.getPropertyColumnNames( determinePropertyIndex( fetch ) );
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) {
// TODO: cache this info
return entityPersister.getEntityMetamodel().getPropertyIndex( fetch.getOwnerPropertyName() );
private class NonEncapsulatedIdentifierAttributeFetchMetadata implements FetchMetadata {
private final EntityPersister entityPersister;
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();
/**
* 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.

View File

@ -76,13 +76,15 @@ public interface FetchOwner {
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 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?

View File

@ -31,31 +31,39 @@ import org.hibernate.type.Type;
* @author Gail Badner
*/
public interface FetchOwnerDelegate {
public static interface FetchMetadata {
/**
* Is the fetch nullable?
*
* @return true, if the fetch is nullable; false, otherwise.
*/
public boolean isNullable();
/**
* Returns the type of the fetched attribute
*
* @return the type of the fetched attribute.
*/
public Type getType();
/**
* Generates the SQL select fragments for the specified fetch. A select fragment is the column and formula
* references.
*
* @param alias The table alias to apply to the fragments (used to qualify column references)
*
* @return the select fragments
*/
public String[] toSqlSelectFragments(String alias);
}
/**
* Is the specified fetch nullable?
* Locate the metadata for the specified Fetch. Allows easier caching of the resolved information.
*
* @param fetch - the owned fetch.
* @param fetch The fetch for which to locate metadata
*
* @return true, if the fetch is nullable; false, otherwise.
* @return The metadata; never {@code null}, rather an exception is thrown if the information for the fetch cannot
* be located.
*/
public boolean isNullable(Fetch fetch);
/**
* Returns the type of the specified fetch.
*
* @param fetch - the owned fetch.
*
* @return the type of the specified fetch.
*/
public Type getType(Fetch fetch);
/**
* Returns the column names used for loading the specified fetch.
*
* @param fetch - the owned fetch.
*
* @return the column names used for loading the specified fetch.
*/
public String[] getColumnNames(Fetch fetch);
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.loader.PropertyPath;
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.CollectionReference;
import org.hibernate.loader.plan.spi.CollectionReturn;
@ -573,16 +574,17 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder
@Override
public Type getType(Fetch fetch) {
if ( !fetch.getOwnerPropertyName().equals( entityReference.getEntityPersister().getIdentifierPropertyName() ) ) {
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 );
return getFetchOwnerDelegate().locateFetchMetadata( fetch ).getType();
}
@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
@ -620,39 +622,42 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder
final EntityReference entityReference) {
super( sessionFactory, entityReference );
this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath();
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;
}
this.delegate = new AbstractFetchOwnerDelegate() {
final boolean isCompositeType = entityReference.getEntityPersister().getIdentifierType().isComponentType();
@Override
public Type getType(Fetch fetch) {
protected FetchMetadata buildFetchMetadata(Fetch fetch) {
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() ) ) {
throw new IllegalArgumentException(
String.format(
"Fetch owner property name [%s] is not the same as the identifier prop" +
fetch.getOwnerPropertyName(),
fetch.getOwnerPropertyName(),
entityReference.getEntityPersister().getIdentifierPropertyName()
)
);
}
return entityReference.getEntityPersister().getIdentifierType();
}
@Override
public String[] getColumnNames(Fetch fetch) {
if ( !isCompositeType ) {
throw new IllegalStateException( "Non-composite ID cannot have fetches." );
}
return ( (Loadable) entityReference.getEntityPersister() ).getIdentifierColumnNames();
return new FetchMetadata() {
@Override
public boolean isNullable() {
return false;
}
@Override
public Type getType() {
return entityReference.getEntityPersister().getIdentifierType();
}
@Override
public String[] toSqlSelectFragments(String alias) {
// should not ever be called iiuc...
throw new WalkingException( "Should not be called" );
}
};
}
};
}
@ -681,14 +686,63 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder
private final PropertyPath propertyPath;
private final FetchOwnerDelegate fetchOwnerDelegate;
public NonEncapsulatedIdentifierAttributeCollector(SessionFactoryImplementor sessionfactory, EntityReference entityReference) {
public NonEncapsulatedIdentifierAttributeCollector(
final SessionFactoryImplementor sessionfactory,
final EntityReference entityReference) {
super( sessionfactory, entityReference );
this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath().append( "<id>" );
this.fetchOwnerDelegate = new CompositeFetchOwnerDelegate(
entityReference.getEntityPersister().getFactory(),
(CompositeType) entityReference.getEntityPersister().getIdentifierType(),
( (Loadable) entityReference.getEntityPersister() ).getIdentifierColumnNames()
);
this.fetchOwnerDelegate = new AbstractFetchOwnerDelegate() {
final boolean isCompositeType = entityReference.getEntityPersister().getIdentifierType().isComponentType();
final CompositeType idType = (CompositeType) entityReference.getEntityPersister().getIdentifierType();
@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
@ -710,6 +764,7 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder
protected FetchOwnerDelegate getFetchOwnerDelegate() {
return fetchOwnerDelegate;
}
}
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();
}
}