HHH-7841 - Introduce LoadPlan
This commit is contained in:
parent
d874bc4737
commit
af5e8c3869
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue