From 4620ff4b4fe670bbf121b709e9da6b418c24ee43 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 13 May 2013 11:54:13 -0700 Subject: [PATCH] HHH-7841 - Redesign Loader --- .../AbstractJoinableAssociationImpl.java | 4 +- .../CollectionJoinableAssociationImpl.java | 1 + .../EntityJoinableAssociationImpl.java | 2 + .../internal/EntityLoadQueryBuilderImpl.java | 4 + .../plan/internal/LoadPlanBuildingHelper.java | 4 +- .../loader/plan/spi/CompositeFetch.java | 7 +- .../loader/plan/spi/EntityFetch.java | 6 +- .../loader/plan/spi/EntityReturn.java | 15 +- .../AbstractLoadPlanBuilderStrategy.java | 175 +++++-- .../entity/AbstractEntityPersister.java | 122 +---- .../EntityIdentifierDefinitionHelper.java | 313 +++++++++++++ .../{Helper.java => FetchStrategyHelper.java} | 2 +- .../spi/MetadataDrivenModelGraphVisitor.java | 37 +- .../AbstractCompositionDefinition.java | 4 +- .../CompositeBasedAssociationAttribute.java | 10 +- .../EntityBasedAssociationAttribute.java | 10 +- ...atedCompositeIdResultSetProcessorTest.java | 434 ++++++++++++++++++ .../graph/internal/advisor/AdviceHelper.java | 7 + 18 files changed, 964 insertions(+), 193 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/persister/walking/internal/EntityIdentifierDefinitionHelper.java rename hibernate-core/src/main/java/org/hibernate/persister/walking/internal/{Helper.java => FetchStrategyHelper.java} (99%) create mode 100644 hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeIdResultSetProcessorTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractJoinableAssociationImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractJoinableAssociationImpl.java index 74c9c31ad0..11bf50aaf5 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractJoinableAssociationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractJoinableAssociationImpl.java @@ -56,12 +56,10 @@ public abstract class AbstractJoinableAssociationImpl implements JoinableAssocia EntityReference currentEntityReference, CollectionReference currentCollectionReference, String withClause, + boolean isNullable, boolean hasRestriction, Map enabledFilters) throws MappingException { this.propertyPath = currentFetch.getPropertyPath(); - final OuterJoinLoadable ownerPersister = (OuterJoinLoadable) currentFetch.getOwner().retrieveFetchSourcePersister(); - final int propertyNumber = ownerPersister.getEntityMetamodel().getPropertyIndex( currentFetch.getOwnerPropertyName() ); - final boolean isNullable = ownerPersister.isSubclassPropertyNullable( propertyNumber ); if ( currentFetch.getFetchStrategy().getStyle() == FetchStyle.JOIN ) { joinType = isNullable ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/CollectionJoinableAssociationImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/CollectionJoinableAssociationImpl.java index 254c1a9c33..df49038b35 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/CollectionJoinableAssociationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/CollectionJoinableAssociationImpl.java @@ -56,6 +56,7 @@ public class CollectionJoinableAssociationImpl extends AbstractJoinableAssociati currentEntityReference, collectionFetch, withClause, + true, hasRestriction, enabledFilters ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityJoinableAssociationImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityJoinableAssociationImpl.java index 34d22b56ba..d52323bc09 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityJoinableAssociationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityJoinableAssociationImpl.java @@ -48,6 +48,7 @@ public class EntityJoinableAssociationImpl extends AbstractJoinableAssociationIm EntityFetch entityFetch, CollectionReference currentCollectionReference, String withClause, + boolean isNullable, boolean hasRestriction, Map enabledFilters) throws MappingException { super( @@ -55,6 +56,7 @@ public class EntityJoinableAssociationImpl extends AbstractJoinableAssociationIm entityFetch, currentCollectionReference, withClause, + isNullable, hasRestriction, enabledFilters ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryBuilderImpl.java index aef90267f1..769f419583 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryBuilderImpl.java @@ -149,10 +149,14 @@ public class EntityLoadQueryBuilderImpl implements LoadQueryBuilder { @Override public void startingEntityFetch(EntityFetch entityFetch) { + final OuterJoinLoadable ownerPersister = (OuterJoinLoadable) entityFetch.getOwner().retrieveFetchSourcePersister(); + final int propertyNumber = ownerPersister.getEntityMetamodel().getPropertyIndex( entityFetch.getOwnerPropertyName() ); + final boolean isNullable = ownerPersister.isSubclassPropertyNullable( propertyNumber ); EntityJoinableAssociationImpl assoc = new EntityJoinableAssociationImpl( entityFetch, getCurrentCollectionReference(), "", // getWithClause( entityFetch.getPropertyPath() ) + isNullable, false, // hasRestriction( entityFetch.getPropertyPath() ) loadQueryInfluencers.getEnabledFilters() ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanBuildingHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanBuildingHelper.java index 8a645e1e2c..ef71c92038 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanBuildingHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanBuildingHelper.java @@ -33,6 +33,7 @@ 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 @@ -63,6 +64,7 @@ public class LoadPlanBuildingHelper { LockMode.NONE, // todo : for now fetchOwner, attributeDefinition.getName(), + (EntityType) attributeDefinition.getType(), fetchStrategy ); } @@ -73,7 +75,7 @@ public class LoadPlanBuildingHelper { LoadPlanBuildingContext loadPlanBuildingContext) { return new CompositeFetch( loadPlanBuildingContext.getSessionFactory(), - (AbstractFetchOwner) fetchOwner, + fetchOwner, attributeDefinition.getName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java index 68e0c19291..b002c7b567 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java @@ -73,7 +73,12 @@ public class CompositeFetch extends AbstractSingularAttributeFetch { AssociationAttributeDefinition attributeDefinition, FetchStrategy fetchStrategy, LoadPlanBuildingContext loadPlanBuildingContext) { - return null; //To change body of implemented methods use File | Settings | File Templates. + return LoadPlanBuildingHelper.buildStandardEntityFetch( + this, + attributeDefinition, + fetchStrategy, + loadPlanBuildingContext + ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetch.java index 444e80cdc1..72d2b4744b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetch.java @@ -55,10 +55,14 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit LockMode lockMode, FetchOwner owner, String ownerProperty, + EntityType entityType, FetchStrategy fetchStrategy) { super( sessionFactory, lockMode, owner, ownerProperty, fetchStrategy ); - this.associationType = (EntityType) owner.retrieveFetchSourcePersister().getPropertyType( ownerProperty ); + this.associationType = entityType; + // (EntityType) owner.retrieveFetchSourcePersister().getPropertyType( ownerProperty ); + //this.associationType = + // (EntityType) owner.retrieveFetchSourcePersister().getPropertyType( getPropertyPath().getFullPath() ); this.persister = sessionFactory.getEntityPersister( associationType.getAssociatedEntityName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReturn.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReturn.java index 2d3bb88b52..cb15874c8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReturn.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReturn.java @@ -134,7 +134,7 @@ public class EntityReturn extends AbstractFetchOwner implements Return, EntityRe @Override public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - EntityKey entityKey = context.getDictatedRootEntityKey(); + EntityKey entityKey = getEntityKeyFromContext( context ); if ( entityKey != null ) { context.getIdentifierResolutionContext( this ).registerEntityKey( entityKey ); return; @@ -147,6 +147,19 @@ public class EntityReturn extends AbstractFetchOwner implements Return, EntityRe } } + private EntityKey getEntityKeyFromContext(ResultSetProcessingContext context) { + if ( context.getDictatedRootEntityKey() != null ) { + return context.getDictatedRootEntityKey(); + } + else if ( context.getQueryParameters().getOptionalId() != null ) { + return context.getSession().generateEntityKey( + context.getQueryParameters().getOptionalId(), + getEntityPersister() + ); + } + return null; + } + @Override public void resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { final IdentifierResolutionContext identifierResolutionContext = context.getIdentifierResolutionContext( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/AbstractLoadPlanBuilderStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/AbstractLoadPlanBuilderStrategy.java index e6968905d1..89a5272ae5 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/AbstractLoadPlanBuilderStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/AbstractLoadPlanBuilderStrategy.java @@ -37,6 +37,7 @@ import org.jboss.logging.MDC; import org.hibernate.HibernateException; import org.hibernate.LockMode; +import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.EntityKey; @@ -44,6 +45,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.PropertyPath; import org.hibernate.loader.plan.internal.LoadPlanBuildingHelper; +import org.hibernate.loader.plan.spi.AbstractSingularAttributeFetch; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CollectionReference; import org.hibernate.loader.plan.spi.CollectionReturn; @@ -83,6 +85,8 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder private ArrayDeque fetchOwnerStack = new ArrayDeque(); private ArrayDeque collectionReferenceStack = new ArrayDeque(); + //private AbstractIdentifierAttributeCollector currentIdentifierAttributeCollector = null; + protected AbstractLoadPlanBuilderStrategy(SessionFactoryImplementor sessionFactory) { this.sessionFactory = sessionFactory; } @@ -117,6 +121,7 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder MDC.remove( MDC_KEY ); fetchOwnerStack.clear(); collectionReferenceStack.clear(); +// currentIdentifierAttributeCollector = null; } @Override @@ -413,10 +418,21 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder ); } - if ( FetchOwner.class.isInstance( associationFetch ) ) { + // If we are collecting fetches for the identifier then + // currentIdentifierAttributeCollector will be non-null. + // In that case, we do not want to continue walking the association, so + // don't push associationFetch to the stack. + //final boolean continueWalk = currentIdentifierAttributeCollector == null; + //if ( continueWalk && FetchOwner.class.isInstance( associationFetch) ) { + if ( FetchOwner.class.isInstance( associationFetch) ) { pushToStack( (FetchOwner) associationFetch ); } + //if ( ! continueWalk ) { + // popFromStack(); + //} + + //return continueWalk; return true; } @@ -431,7 +447,21 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder } private void pushToStack(FetchOwner fetchOwner) { - log.trace( "Pushing fetch owner to stack : " + fetchOwner ); +// if ( fetchOwner instanceof AbstractIdentifierAttributeCollector ) { +// if ( currentIdentifierAttributeCollector != null ) { +// throw new WalkingException( +// String.format( +// "An AbstractIdentifierAttributeCollector is already being processed: %s", +// currentIdentifierAttributeCollector +// ) +// ); +// } +// currentIdentifierAttributeCollector = (AbstractIdentifierAttributeCollector) fetchOwner; +// log.trace( "Pushing AbstractIdentifierAttributeCollector fetch owner to stack : " + fetchOwner ); +// } +// else { + log.trace( "Pushing fetch owner to stack : " + fetchOwner ); +// } mdcStack().push( fetchOwner.getPropertyPath() ); fetchOwnerStack.addFirst( fetchOwner ); } @@ -442,7 +472,30 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder private FetchOwner popFromStack() { final FetchOwner last = fetchOwnerStack.removeFirst(); - log.trace( "Popped fetch owner from stack : " + last ); +// if ( last instanceof AbstractIdentifierAttributeCollector ) { +// if ( currentIdentifierAttributeCollector == null ) { +// throw new WalkingException( +// String.format( +// "Popped fetch owner was an AbstractIdentifierAttributeCollector [%s], but none in process (currentIdentifierAttributeCollector == null)", +// last +// ) +// ); +// } +// else if ( currentIdentifierAttributeCollector != last ) { +// throw new WalkingException( +// String.format( +// "Expected popped fetch owner to be [%s], but instead it was [%s])", +// currentIdentifierAttributeCollector, +// last +// ) +// ); +// } +// currentIdentifierAttributeCollector = null; +// log.trace( "Popped AbstractIdentifierAttributeCollector fetch owner from stack : " + last ); +// } +// else { + log.trace( "Popped fetch owner from stack : " + last ); +// } mdcStack().pop(); if ( FetchStackAware.class.isInstance( last ) ) { ( (FetchStackAware) last ).poppedFromStack(); @@ -487,15 +540,13 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder implements FetchOwner, EntityReference, FetchStackAware { protected final EntityReference entityReference; - private final PropertyPath propertyPath; - protected final List identifierFetches = new ArrayList(); - protected final Map fetchToHydratedStateExtractorMap - = new HashMap(); + protected final List identifierFetches = new ArrayList(); + protected final Map fetchToHydratedStateExtractorMap + = new HashMap(); public AbstractIdentifierAttributeCollector(EntityReference entityReference) { this.entityReference = entityReference; - this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath().append( "" ); } @Override @@ -533,7 +584,7 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder LoadPlanBuildingContext loadPlanBuildingContext) { // we have a key-many-to-one // - // IMPL NOTE: we pass ourselves as the FetchOwner which will route the fetch back throw our #addFetch + // IMPL NOTE: we pass ourselves as the FetchOwner which will route the fetch back through our #addFetch // impl. We collect them there and later build the IdentifierDescription final EntityFetch fetch = LoadPlanBuildingHelper.buildStandardEntityFetch( this, @@ -551,7 +602,7 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder CompositionDefinition attributeDefinition, LoadPlanBuildingContext loadPlanBuildingContext) { // nested composition. Unusual, but not disallowed. // - // IMPL NOTE: we pass ourselves as the FetchOwner which will route the fetch back throw our #addFetch + // IMPL NOTE: we pass ourselves as the FetchOwner which will route the fetch back through our #addFetch // impl. We collect them there and later build the IdentifierDescription return LoadPlanBuildingHelper.buildStandardCompositeFetch( this, @@ -570,7 +621,7 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder @Override public void addFetch(Fetch fetch) { - identifierFetches.add( (EntityFetch) fetch ); + identifierFetches.add( (AbstractSingularAttributeFetch) fetch ); } @Override @@ -588,11 +639,6 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder return ( (FetchOwner) entityReference ).retrieveFetchSourcePersister(); } - @Override - public PropertyPath getPropertyPath() { - return propertyPath; - } - @Override public void injectIdentifierDescription(IdentifierDescription identifierDescription) { throw new WalkingException( @@ -602,43 +648,59 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder } protected static class EncapsulatedIdentifierAttributeCollector extends AbstractIdentifierAttributeCollector { + private final PropertyPath propertyPath; + public EncapsulatedIdentifierAttributeCollector(EntityReference entityReference) { super( entityReference ); + this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath(); } @Override protected IdentifierDescription buildIdentifierDescription() { return new IdentifierDescriptionImpl( entityReference, - identifierFetches.toArray( new EntityFetch[ identifierFetches.size() ] ), + identifierFetches.toArray( new AbstractSingularAttributeFetch[ identifierFetches.size() ] ), null ); } + + @Override + public PropertyPath getPropertyPath() { + return propertyPath; + } } protected static class NonEncapsulatedIdentifierAttributeCollector extends AbstractIdentifierAttributeCollector { + private final PropertyPath propertyPath; public NonEncapsulatedIdentifierAttributeCollector(EntityReference entityReference) { super( entityReference ); + this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath().append( "" ); } @Override protected IdentifierDescription buildIdentifierDescription() { return new IdentifierDescriptionImpl( entityReference, - identifierFetches.toArray( new EntityFetch[ identifierFetches.size() ] ), + identifierFetches.toArray( new AbstractSingularAttributeFetch[ identifierFetches.size() ] ), fetchToHydratedStateExtractorMap ); } + + @Override + public PropertyPath getPropertyPath() { + return propertyPath; + } } private static class IdentifierDescriptionImpl implements IdentifierDescription { private final EntityReference entityReference; - private final EntityFetch[] identifierFetches; - private final Map fetchToHydratedStateExtractorMap; + private final AbstractSingularAttributeFetch[] identifierFetches; + private final Map fetchToHydratedStateExtractorMap; private IdentifierDescriptionImpl( - EntityReference entityReference, EntityFetch[] identifierFetches, - Map fetchToHydratedStateExtractorMap) { + EntityReference entityReference, + AbstractSingularAttributeFetch[] identifierFetches, + Map fetchToHydratedStateExtractorMap) { this.entityReference = entityReference; this.identifierFetches = identifierFetches; this.fetchToHydratedStateExtractorMap = fetchToHydratedStateExtractorMap; @@ -656,24 +718,28 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder final Object ownerIdentifierHydratedState = ownerIdentifierResolutionContext.getHydratedForm(); if ( ownerIdentifierHydratedState != null ) { - for ( EntityFetch fetch : identifierFetches ) { - final IdentifierResolutionContext identifierResolutionContext = - context.getIdentifierResolutionContext( fetch ); - // if the identifier was already hydrated, nothing to do - if ( identifierResolutionContext.getHydratedForm() != null ) { - continue; - } + for ( AbstractSingularAttributeFetch fetch : identifierFetches ) { + if ( fetch instanceof EntityFetch ) { + final IdentifierResolutionContext identifierResolutionContext = + context.getIdentifierResolutionContext( (EntityFetch) fetch ); + // if the identifier was already hydrated, nothing to do + if ( identifierResolutionContext.getHydratedForm() != null ) { + continue; + } + // try to extract the sub-hydrated value from the owners tuple array + if ( fetchToHydratedStateExtractorMap != null && ownerIdentifierHydratedState != null ) { + Serializable extracted = (Serializable) fetchToHydratedStateExtractorMap.get( fetch ) + .extract( ownerIdentifierHydratedState ); + identifierResolutionContext.registerHydratedForm( extracted ); + continue; + } - // try to extract the sub-hydrated value from the owners tuple array - if ( fetchToHydratedStateExtractorMap != null && ownerIdentifierHydratedState != null ) { - Serializable extracted = (Serializable) fetchToHydratedStateExtractorMap.get( fetch ) - .extract( ownerIdentifierHydratedState ); - identifierResolutionContext.registerHydratedForm( extracted ); - continue; + // if we can't, then read from result set + fetch.hydrate( resultSet, context ); + } + else { + throw new NotYetImplementedException( "Cannot hydrate identifier Fetch that is not an EntityFetch" ); } - - // if we can't, then read from result set - fetch.hydrate( resultSet, context ); } return; } @@ -689,15 +755,8 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder @Override public EntityKey resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - for ( EntityFetch fetch : identifierFetches ) { - final IdentifierResolutionContext identifierResolutionContext = - context.getIdentifierResolutionContext( fetch ); - if ( identifierResolutionContext.getEntityKey() != null ) { - continue; - } - - EntityKey fetchKey = fetch.resolveInIdentifier( resultSet, context ); - identifierResolutionContext.registerEntityKey( fetchKey ); + for ( AbstractSingularAttributeFetch fetch : identifierFetches ) { + resolveIdentifierFetch( resultSet, context, fetch ); } final IdentifierResolutionContext ownerIdentifierResolutionContext = @@ -710,6 +769,28 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder } } + private static void resolveIdentifierFetch( + ResultSet resultSet, + ResultSetProcessingContext context, + AbstractSingularAttributeFetch fetch) throws SQLException { + if ( fetch instanceof EntityFetch ) { + EntityFetch entityFetch = (EntityFetch) fetch; + final IdentifierResolutionContext identifierResolutionContext = + context.getIdentifierResolutionContext( entityFetch ); + if ( identifierResolutionContext.getEntityKey() != null ) { + return; + } + + EntityKey fetchKey = entityFetch.resolveInIdentifier( resultSet, context ); + identifierResolutionContext.registerEntityKey( fetchKey ); + } + else if ( fetch instanceof CompositeFetch ) { + for ( Fetch subFetch : fetch.getFetches() ) { + resolveIdentifierFetch( resultSet, context, (AbstractSingularAttributeFetch) subFetch ); + } + } + } + public static class MDCStack { private ArrayDeque pathStack = new ArrayDeque(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index e2cb4a1108..51062e1524 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -109,6 +109,7 @@ import org.hibernate.metamodel.binding.SimpleValueBinding; import org.hibernate.metamodel.binding.SingularAttributeBinding; import org.hibernate.metamodel.relational.DerivedValue; import org.hibernate.metamodel.relational.Value; +import org.hibernate.persister.walking.internal.EntityIdentifierDefinitionHelper; import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.AttributeSource; import org.hibernate.persister.walking.spi.CompositionDefinition; @@ -5122,129 +5123,20 @@ public abstract class AbstractEntityPersister final Type idType = getIdentifierType(); if ( !idType.isComponentType() ) { - entityIdentifierDefinition = buildSimpleEncapsulatedIdentifierDefinition(); + entityIdentifierDefinition = + EntityIdentifierDefinitionHelper.buildSimpleEncapsulatedIdentifierDefinition( this ); return; } final CompositeType cidType = (CompositeType) idType; if ( !cidType.isEmbedded() ) { - entityIdentifierDefinition = buildEncapsulatedCompositeIdentifierDefinition(); + entityIdentifierDefinition = + EntityIdentifierDefinitionHelper.buildEncapsulatedCompositeIdentifierDefinition( this ); return; } - entityIdentifierDefinition = new NonEncapsulatedEntityIdentifierDefinition() { - @Override - public Iterable getAttributes() { - // todo : implement - throw new NotYetImplementedException(); - } - - @Override - public Class getSeparateIdentifierMappingClass() { - // todo : implement - throw new NotYetImplementedException(); - } - - @Override - public boolean isEncapsulated() { - return false; - } - - @Override - public EntityDefinition getEntityDefinition() { - return AbstractEntityPersister.this; - } - }; - } - - private EntityIdentifierDefinition buildSimpleEncapsulatedIdentifierDefinition() { - final AttributeDefinition simpleIdentifierAttributeAdapter = new AttributeDefinition() { - @Override - public String getName() { - return entityMetamodel.getIdentifierProperty().getName(); - } - - @Override - public Type getType() { - return entityMetamodel.getIdentifierProperty().getType(); - } - - @Override - public AttributeSource getSource() { - return AbstractEntityPersister.this; - } - - @Override - public String toString() { - return ""; - } - }; - - return new EncapsulatedEntityIdentifierDefinition() { - @Override - public AttributeDefinition getAttributeDefinition() { - return simpleIdentifierAttributeAdapter; - } - - @Override - public boolean isEncapsulated() { - return true; - } - - @Override - public EntityDefinition getEntityDefinition() { - return AbstractEntityPersister.this; - } - }; - } - - private EntityIdentifierDefinition buildEncapsulatedCompositeIdentifierDefinition() { - final CompositionDefinition compositeIdentifierAttributeAdapter = new CompositionDefinition() { - @Override - public String getName() { - return entityMetamodel.getIdentifierProperty().getName(); - } - - @Override - public Type getType() { - return entityMetamodel.getIdentifierProperty().getType(); - } - - @Override - public AttributeSource getSource() { - return AbstractEntityPersister.this; - } - - @Override - public String toString() { - return ""; - } - - @Override - public Iterable getAttributes() { - ComponentType componentType = (ComponentType) getType(); - //for ( Type type : componentType.getSubtypes() ) { - throw new NotYetImplementedException( "cannot create sub-attribute definitions for a ComponentType yet." ); - //} - } - }; - - return new EncapsulatedEntityIdentifierDefinition() { - @Override - public AttributeDefinition getAttributeDefinition() { - return compositeIdentifierAttributeAdapter; - } - - @Override - public boolean isEncapsulated() { - return true; - } - - @Override - public EntityDefinition getEntityDefinition() { - return AbstractEntityPersister.this; - } - }; + entityIdentifierDefinition = + EntityIdentifierDefinitionHelper.buildNonEncapsulatedCompositeIdentifierDefinition( this ); } private void collectAttributeDefinitions() { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/EntityIdentifierDefinitionHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/EntityIdentifierDefinitionHelper.java new file mode 100644 index 0000000000..abb5e5b958 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/EntityIdentifierDefinitionHelper.java @@ -0,0 +1,313 @@ +/* + * 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.persister.walking.internal; + +import java.util.Iterator; + +import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.FetchStyle; +import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.spi.CascadeStyle; +import org.hibernate.engine.spi.CascadeStyles; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.loader.PropertyPath; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Joinable; +import org.hibernate.persister.spi.HydratedCompoundValueHandler; +import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; +import org.hibernate.persister.walking.spi.AssociationKey; +import org.hibernate.persister.walking.spi.AttributeDefinition; +import org.hibernate.persister.walking.spi.AttributeSource; +import org.hibernate.persister.walking.spi.CollectionDefinition; +import org.hibernate.persister.walking.spi.CompositionDefinition; +import org.hibernate.persister.walking.spi.EncapsulatedEntityIdentifierDefinition; +import org.hibernate.persister.walking.spi.EntityDefinition; +import org.hibernate.persister.walking.spi.EntityIdentifierDefinition; +import org.hibernate.persister.walking.spi.NonEncapsulatedEntityIdentifierDefinition; +import org.hibernate.persister.walking.spi.WalkingException; +import org.hibernate.type.AssociationType; +import org.hibernate.type.ComponentType; +import org.hibernate.type.Type; + +import static org.hibernate.engine.internal.JoinHelper.getLHSColumnNames; +import static org.hibernate.engine.internal.JoinHelper.getLHSTableName; + +/** + * @author Gail Badner + */ +public class EntityIdentifierDefinitionHelper { + + public static EntityIdentifierDefinition buildSimpleEncapsulatedIdentifierDefinition(final AbstractEntityPersister entityPersister) { + return new EncapsulatedEntityIdentifierDefinition() { + @Override + public AttributeDefinition getAttributeDefinition() { + return new AttributeDefinitionAdapter( entityPersister); + } + + @Override + public boolean isEncapsulated() { + return true; + } + + @Override + public EntityDefinition getEntityDefinition() { + return entityPersister; + } + }; + } + + public static EntityIdentifierDefinition buildEncapsulatedCompositeIdentifierDefinition(final AbstractEntityPersister entityPersister) { + + return new EncapsulatedEntityIdentifierDefinition() { + @Override + public AttributeDefinition getAttributeDefinition() { + return new CompositeAttributeDefinitionAdapter( entityPersister ); + } + + @Override + public boolean isEncapsulated() { + return true; + } + + @Override + public EntityDefinition getEntityDefinition() { + return entityPersister; + } + }; + } + + public static EntityIdentifierDefinition buildNonEncapsulatedCompositeIdentifierDefinition(final AbstractEntityPersister entityPersister) { + return new NonEncapsulatedEntityIdentifierDefinition() { + @Override + public Iterable getAttributes() { + return new CompositeAttributeDefinitionAdapter( entityPersister ).getAttributes(); + } + + @Override + public Class getSeparateIdentifierMappingClass() { + return entityPersister.getEntityMetamodel().getIdentifierProperty().getType().getReturnedClass(); + } + + @Override + public boolean isEncapsulated() { + return false; + } + + @Override + public EntityDefinition getEntityDefinition() { + return entityPersister; + } + }; + } + + private static class AttributeDefinitionAdapter implements AttributeDefinition { + private final AbstractEntityPersister entityPersister; + + AttributeDefinitionAdapter(AbstractEntityPersister entityPersister) { + this.entityPersister = entityPersister; + } + + @Override + public String getName() { + return entityPersister.getEntityMetamodel().getIdentifierProperty().getName(); + } + + @Override + public Type getType() { + return entityPersister.getEntityMetamodel().getIdentifierProperty().getType(); + } + + @Override + public AttributeSource getSource() { + return entityPersister; + } + + @Override + public String toString() { + return ""; + } + + protected AbstractEntityPersister getEntityPersister() { + return entityPersister; + } + } + + private static class CompositeAttributeDefinitionAdapter extends AttributeDefinitionAdapter implements CompositionDefinition { + + CompositeAttributeDefinitionAdapter(AbstractEntityPersister entityPersister) { + super( entityPersister ); + } + + @Override + public Iterable getAttributes() { + return new Iterable() { + @Override + public Iterator iterator() { + final ComponentType componentType = (ComponentType) getType(); + return new Iterator() { + private final int numberOfAttributes = componentType.getSubtypes().length; + private int currentSubAttributeNumber = 0; + private int currentColumnPosition = 0; + + @Override + public boolean hasNext() { + return currentSubAttributeNumber < numberOfAttributes; + } + + @Override + public AttributeDefinition next() { + final int subAttributeNumber = currentSubAttributeNumber; + currentSubAttributeNumber++; + + final AttributeSource source = getSource(); + final String name = componentType.getPropertyNames()[subAttributeNumber]; + final Type type = componentType.getSubtypes()[subAttributeNumber]; + + final int columnPosition = currentColumnPosition; + currentColumnPosition += type.getColumnSpan( getEntityPersister().getFactory() ); + + if ( type.isAssociationType() ) { + final AssociationType aType = (AssociationType) type; + final Joinable joinable = aType.getAssociatedJoinable( getEntityPersister().getFactory() ); + return new AssociationAttributeDefinition() { + @Override + public AssociationKey getAssociationKey() { + /* TODO: is this always correct? */ + //return new AssociationKey( + // joinable.getTableName(), + // JoinHelper.getRHSColumnNames( aType, getEntityPersister().getFactory() ) + //); + return new AssociationKey( + getEntityPersister().getTableName(), + getLHSColumnNames( + aType, + -1, + columnPosition, + getEntityPersister(), + getEntityPersister().getFactory() + ) + ); + + } + + @Override + public boolean isCollection() { + return false; + } + + @Override + public EntityDefinition toEntityDefinition() { + return (EntityPersister) joinable; + } + + @Override + public CollectionDefinition toCollectionDefinition() { + throw new WalkingException( "A collection cannot be mapped to a composite ID sub-attribute." ); + } + + @Override + public FetchStrategy determineFetchPlan(LoadQueryInfluencers loadQueryInfluencers, PropertyPath propertyPath) { + return new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.JOIN ); + } + + @Override + public CascadeStyle determineCascadeStyle() { + return CascadeStyles.NONE; + } + + @Override + public HydratedCompoundValueHandler getHydratedCompoundValueExtractor() { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public Type getType() { + return type; + } + + @Override + public AttributeSource getSource() { + return source; + } + }; + } + else if ( type.isComponentType() ) { + return new CompositionDefinition() { + @Override + public String getName() { + return name; + } + + @Override + public Type getType() { + return type; + } + + @Override + public AttributeSource getSource() { + return source; + } + + @Override + public Iterable getAttributes() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + }; + } + else { + return new AttributeDefinition() { + @Override + public String getName() { + return name; + } + + @Override + public Type getType() { + return type; + } + + @Override + public AttributeSource getSource() { + return source; + } + }; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException( "Remove operation not supported here" ); + } + }; + } + }; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/Helper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java similarity index 99% rename from hibernate-core/src/main/java/org/hibernate/persister/walking/internal/Helper.java rename to hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java index 29eb9f5dbe..7d6cf84335 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/Helper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java @@ -42,7 +42,7 @@ import org.hibernate.type.AssociationType; /** * @author Steve Ebersole */ -public class Helper { +public class FetchStrategyHelper { /** * Determine the fetch-style (if one) explicitly set for this association via fetch profiles. *

diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetadataDrivenModelGraphVisitor.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetadataDrivenModelGraphVisitor.java index 8a9ceb77a9..c48461a885 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetadataDrivenModelGraphVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetadataDrivenModelGraphVisitor.java @@ -28,12 +28,21 @@ import java.util.Set; import org.jboss.logging.Logger; +import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.FetchStyle; +import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.spi.CascadeStyle; +import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.PropertyPath; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Joinable; +import org.hibernate.persister.spi.HydratedCompoundValueHandler; import org.hibernate.type.Type; +import static org.hibernate.engine.internal.JoinHelper.getRHSColumnNames; + /** * Provides model graph visitation based on the defined metadata (as opposed to based on the incoming graph * as we see in cascade processing). In layman terms, we are walking the graph of the users model as defined by @@ -116,7 +125,7 @@ public class MetadataDrivenModelGraphVisitor { final boolean continueWalk; if ( attributeDefinition.getType().isAssociationType() ) { continueWalk = - ! isDuplicateAssociation( ( (AssociationAttributeDefinition) attributeDefinition ).getAssociationKey() ) && + ! isDuplicateAssociationKey( ( (AssociationAttributeDefinition) attributeDefinition ).getAssociationKey() ) && strategy.startingAttribute( attributeDefinition ); } else { @@ -143,6 +152,11 @@ public class MetadataDrivenModelGraphVisitor { private void visitAssociation(AssociationAttributeDefinition attribute) { // todo : do "too deep" checks; but see note about adding depth to PropertyPath + if ( !addAssociationKey( attribute.getAssociationKey() ) ) { + log.debug( "Property path deemed to be circular : " + currentPropertyPath.getFullPath() ); + return; + } + if ( attribute.isCollection() ) { visitCollectionDefinition( attribute.toCollectionDefinition() ); } @@ -212,15 +226,18 @@ public class MetadataDrivenModelGraphVisitor { private final Set visitedAssociationKeys = new HashSet(); - protected boolean isDuplicateAssociation(AssociationKey associationKey) { - boolean isDuplicate = !visitedAssociationKeys.add( associationKey ); - if ( isDuplicate ) { - log.debug( "Property path deemed to be circular : " + currentPropertyPath.getFullPath() ); - return true; - } - else { - return false; - } + /** + * Add association key. + * @param associationKey - the association key. + * @return true, if the association key was added; + * false, otherwise (indicating the association key was already visited). + */ + protected boolean addAssociationKey(AssociationKey associationKey) { + return visitedAssociationKeys.add( associationKey ); + } + + protected boolean isDuplicateAssociationKey(AssociationKey associationKey) { + return visitedAssociationKeys.contains( associationKey ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionDefinition.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionDefinition.java index 3f4dcf216e..554a630ed6 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionDefinition.java @@ -101,13 +101,13 @@ public abstract class AbstractCompositionDefinition extends AbstractNonIdentifie getLHSTableName( aType, attributeNumber(), - (OuterJoinLoadable) joinable + (OuterJoinLoadable) locateOwningPersister() ), getLHSColumnNames( aType, attributeNumber(), columnPosition, - (OuterJoinLoadable) joinable, + (OuterJoinLoadable) locateOwningPersister(), sessionFactory() ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/CompositeBasedAssociationAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/CompositeBasedAssociationAttribute.java index eb7224d94d..81c7f343f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/CompositeBasedAssociationAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/CompositeBasedAssociationAttribute.java @@ -23,8 +23,6 @@ */ package org.hibernate.tuple.component; -import java.io.Serializable; - import org.hibernate.FetchMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; @@ -37,7 +35,7 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.spi.HydratedCompoundValueHandler; -import org.hibernate.persister.walking.internal.Helper; +import org.hibernate.persister.walking.internal.FetchStrategyHelper; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; import org.hibernate.persister.walking.spi.AssociationKey; import org.hibernate.persister.walking.spi.CollectionDefinition; @@ -132,7 +130,7 @@ public class CompositeBasedAssociationAttribute EntityPersister owningPersister, PropertyPath propertyPath, int ownerAttributeNumber) { - return Helper.determineFetchStyleByProfile( + return FetchStrategyHelper.determineFetchStyleByProfile( loadQueryInfluencers, owningPersister, propertyPath, @@ -141,11 +139,11 @@ public class CompositeBasedAssociationAttribute } protected FetchStyle determineFetchStyleByMetadata(FetchMode fetchMode, AssociationType type) { - return Helper.determineFetchStyleByMetadata( fetchMode, type, sessionFactory() ); + return FetchStrategyHelper.determineFetchStyleByMetadata( fetchMode, type, sessionFactory() ); } private FetchTiming determineFetchTiming(FetchStyle style) { - return Helper.determineFetchTiming( style, getType(), sessionFactory() ); + return FetchStrategyHelper.determineFetchTiming( style, getType(), sessionFactory() ); } private EntityPersister locateOwningPersister() { diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityBasedAssociationAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityBasedAssociationAttribute.java index 88405553db..78d803e43b 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityBasedAssociationAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityBasedAssociationAttribute.java @@ -34,7 +34,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.persister.spi.HydratedCompoundValueHandler; -import org.hibernate.persister.walking.internal.Helper; +import org.hibernate.persister.walking.internal.FetchStrategyHelper; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; import org.hibernate.persister.walking.spi.AssociationKey; import org.hibernate.persister.walking.spi.CollectionDefinition; @@ -129,22 +129,22 @@ public class EntityBasedAssociationAttribute public FetchStrategy determineFetchPlan(LoadQueryInfluencers loadQueryInfluencers, PropertyPath propertyPath) { final EntityPersister owningPersister = getSource().getEntityPersister(); - FetchStyle style = Helper.determineFetchStyleByProfile( + FetchStyle style = FetchStrategyHelper.determineFetchStyleByProfile( loadQueryInfluencers, owningPersister, propertyPath, attributeNumber() ); if ( style == null ) { - style = Helper.determineFetchStyleByMetadata( - ((OuterJoinLoadable) getSource().getEntityPersister()).getFetchMode( attributeNumber() ), + style = FetchStrategyHelper.determineFetchStyleByMetadata( + ( (OuterJoinLoadable) getSource().getEntityPersister() ).getFetchMode( attributeNumber() ), getType(), sessionFactory() ); } return new FetchStrategy( - Helper.determineFetchTiming( style, getType(), sessionFactory() ), + FetchStrategyHelper.determineFetchTiming( style, getType(), sessionFactory() ), style ); } diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeIdResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeIdResultSetProcessorTest.java new file mode 100644 index 0000000000..2482fcf4ca --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeIdResultSetProcessorTest.java @@ -0,0 +1,434 @@ +/* + * 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.io.Serializable; +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 javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.junit.Test; + +import org.hibernate.LockOptions; +import org.hibernate.Session; +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.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.ExtraAssertions; +import org.hibernate.type.Type; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class EncapsulatedCompositeIdResultSetProcessorTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, CardField.class, Card.class }; + } + + @Test + public void testSimpleCompositeId() throws Exception { + + // create some test data + Session session = openSession(); + session.beginTransaction(); + Parent parent = new Parent(); + parent.id = new ParentPK(); + parent.id.firstName = "Joe"; + parent.id.lastName = "Blow"; + session.save( parent ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + Parent parentGotten = (Parent) session.get( Parent.class, parent.id ); + assertEquals( parent, parentGotten ); + session.getTransaction().commit(); + session.close(); + + final List results = getResults( + sessionFactory().getEntityPersister( Parent.class.getName() ), + new Callback() { + @Override + public void bind(PreparedStatement ps) throws SQLException { + ps.setString( 1, "Joe" ); + ps.setString( 2, "Blow" ); + } + + @Override + public QueryParameters getQueryParameters() { + return new QueryParameters(); + } + + } + ); + assertEquals( 1, results.size() ); + Object result = results.get( 0 ); + assertNotNull( result ); + + Parent parentWork = ExtraAssertions.assertTyping( Parent.class, result ); + assertEquals( parent, parentWork ); + + // clean up test data + session = openSession(); + session.beginTransaction(); + session.createQuery( "delete Parent" ).executeUpdate(); + session.getTransaction().commit(); + session.close(); + } + + @Test + public void testCompositeIdWithKeyManyToOne() throws Exception { + final String cardId = "ace-of-spades"; + + // create some test data + Session session = openSession(); + session.beginTransaction(); + Card card = new Card( cardId ); + final CardField cardField = new CardField( card, 1 ); + session.persist( card ); + session.persist( cardField ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + Card cardProxy = (Card) session.load( Card.class, cardId ); + final CardFieldPK cardFieldPK = new CardFieldPK( cardProxy, 1 ); + CardField cardFieldGotten = (CardField) session.get( CardField.class, cardFieldPK ); + + //assertEquals( card, cardGotten ); + session.getTransaction().commit(); + session.close(); + + final EntityPersister entityPersister = sessionFactory().getEntityPersister( CardField.class.getName() ); + + final List results = getResults( + entityPersister, + new Callback() { + @Override + public void bind(PreparedStatement ps) throws SQLException { + ps.setString( 1, cardField.primaryKey.card.id ); + ps.setInt( 2, cardField.primaryKey.fieldNumber ); + } + + @Override + public QueryParameters getQueryParameters() { + QueryParameters qp = new QueryParameters(); + qp.setPositionalParameterTypes( new Type[] { entityPersister.getIdentifierType() } ); + qp.setPositionalParameterValues( new Object[] { cardFieldPK } ); + qp.setOptionalObject( null ); + qp.setOptionalEntityName( entityPersister.getEntityName() ); + qp.setOptionalId( cardFieldPK ); + 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 + session = openSession(); + session.beginTransaction(); + session.createQuery( "delete CardField" ).executeUpdate(); + session.createQuery( "delete Card" ).executeUpdate(); + session.getTransaction().commit(); + session.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 { + 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 (); + } + + @Entity ( name = "Parent" ) + public static class Parent { + @EmbeddedId + public ParentPK id; + + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof Parent ) ) return false; + + final Parent parent = (Parent) o; + + if ( !id.equals( parent.id ) ) return false; + + return true; + } + + public int hashCode() { + return id.hashCode(); + } + } + + @Embeddable + public static class ParentPK implements Serializable { + private String firstName; + private String lastName; + + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof ParentPK ) ) return false; + + final ParentPK parentPk = (ParentPK) o; + + if ( !firstName.equals( parentPk.firstName ) ) return false; + if ( !lastName.equals( parentPk.lastName ) ) return false; + + return true; + } + + public int hashCode() { + int result; + result = firstName.hashCode(); + result = 29 * result + lastName.hashCode(); + return result; + } + } + + @Entity ( name = "CardField" ) + public static class CardField implements Serializable { + + @EmbeddedId + private CardFieldPK primaryKey; + + CardField(Card card, int fieldNumber) { + this.primaryKey = new CardFieldPK(card, fieldNumber); + } + + CardField() { + } + + public CardFieldPK getPrimaryKey() { + return primaryKey; + } + + public void setPrimaryKey(CardFieldPK primaryKey) { + this.primaryKey = primaryKey; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + CardField cardField = (CardField) o; + + if ( primaryKey != null ? !primaryKey.equals( cardField.primaryKey ) : cardField.primaryKey != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return primaryKey != null ? primaryKey.hashCode() : 0; + } + } + + @Embeddable + public static class CardFieldPK implements Serializable { + @ManyToOne(optional = false) + private Card card; + + private int fieldNumber; + + public CardFieldPK(Card card, int fieldNumber) { + this.card = card; + this.fieldNumber = fieldNumber; + } + + CardFieldPK() { + } + + public Card getCard() { + return card; + } + + public void setCard(Card card) { + this.card = card; + } + + public int getFieldNumber() { + return fieldNumber; + } + + public void setFieldNumber(int fieldNumber) { + this.fieldNumber = fieldNumber; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + CardFieldPK that = (CardFieldPK) o; + + if ( fieldNumber != that.fieldNumber ) { + return false; + } + if ( card != null ? !card.equals( that.card ) : that.card != null ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = card != null ? card.hashCode() : 0; + result = 31 * result + fieldNumber; + return result; + } + } + + @Entity ( name = "Card" ) + public static class Card implements Serializable { + @Id + private String id; + + public Card(String id) { + this(); + this.id = id; + } + + Card() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/AdviceHelper.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/AdviceHelper.java index 297912b513..3f7bcab15b 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/AdviceHelper.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/AdviceHelper.java @@ -29,11 +29,13 @@ import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.jpa.graph.spi.AttributeNodeImplementor; +import org.hibernate.jpa.internal.metamodel.Helper; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CompositeFetch; import org.hibernate.loader.plan.spi.EntityFetch; import org.hibernate.loader.plan.spi.Fetch; import org.hibernate.loader.plan.spi.FetchOwner; +import org.hibernate.type.EntityType; /** * @author Steve Ebersole @@ -54,11 +56,16 @@ public class AdviceHelper { ); } else { + EntityType entityType = (EntityType) Helper.resolveType( + (SessionFactoryImplementor) attributeNode.entityManagerFactory().getSessionFactory(), + attributeNode.getAttribute() + ); return new EntityFetch( (SessionFactoryImplementor) attributeNode.entityManagerFactory().getSessionFactory(), LockMode.NONE, fetchOwner, attributeNode.getAttributeName(), + entityType, new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.SELECT ) ); }