diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java index 48d6651c1ae9..4fc38ceab585 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java @@ -12,18 +12,21 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import java.util.function.Consumer; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.profile.FetchProfile; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.loader.ast.spi.Loadable; import org.hibernate.loader.ast.spi.Loader; import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ModelPart; @@ -49,12 +52,15 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterImpl; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.EntityGraphNavigator; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.FetchableContainer; import org.hibernate.sql.results.graph.collection.internal.CollectionDomainResult; +import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.sql.results.internal.StandardEntityGraphNavigatorImpl; import org.jboss.logging.Logger; @@ -64,12 +70,13 @@ * Builder for SQL AST trees used by {@link Loader} implementations. * * @author Steve Ebersole + * @author Nahtan Xu */ public class LoaderSelectBuilder { private static final Logger log = Logger.getLogger( LoaderSelectBuilder.class ); /** - * Create a SQL AST select-statement based on matching one-or-more keys + * Create an SQL AST select-statement based on matching one-or-more keys * * @param loadable The root Loadable * @param partsToSelect Parts of the Loadable to select. Null/empty indicates to select the Loadable itself @@ -107,7 +114,7 @@ public static SelectStatement createSelect( } /** - * Create a SQL AST select-statement used for subselect-based CollectionLoader + * Create an SQL AST select-statement used for subselect-based CollectionLoader * * @see CollectionLoaderSubSelectFetch * @@ -151,7 +158,10 @@ public static SelectStatement createSubSelectFetchSelect( private final LoadQueryInfluencers loadQueryInfluencers; private final LockOptions lockOptions; private final Consumer jdbcParameterConsumer; + private final EntityGraphNavigator entityGraphNavigator; + private int fetchDepth; + private Map orderByFragments; private LoaderSelectBuilder( SqlAstCreationContext creationContext, @@ -170,6 +180,14 @@ private LoaderSelectBuilder( this.cachedDomainResult = cachedDomainResult; this.numberOfKeysToLoad = numberOfKeysToLoad; this.loadQueryInfluencers = loadQueryInfluencers; + if ( loadQueryInfluencers != null + && loadQueryInfluencers.getEffectiveEntityGraph() != null + && loadQueryInfluencers.getEffectiveEntityGraph().getSemantic() != null ) { + this.entityGraphNavigator = new StandardEntityGraphNavigatorImpl( loadQueryInfluencers.getEffectiveEntityGraph() ); + } + else { + this.entityGraphNavigator = null; + } this.lockOptions = lockOptions != null ? lockOptions : LockOptions.NONE; this.jdbcParameterConsumer = jdbcParameterConsumer; } @@ -208,7 +226,7 @@ private SelectStatement generateSelect() { } if ( partsToSelect != null && !partsToSelect.isEmpty() ) { - domainResults = new ArrayList<>(); + domainResults = new ArrayList<>( partsToSelect.size() ); for ( ModelPart part : partsToSelect ) { final NavigablePath navigablePath = rootNavigablePath.append( part.getPartName() ); domainResults.add( @@ -339,7 +357,7 @@ private void applyKeyRestriction( final InListPredicate predicate = new InListPredicate( tuple ); for ( int i = 0; i < numberOfKeysToLoad; i++ ) { - final List tupleParams = new ArrayList<>( ); + final List tupleParams = new ArrayList<>( numberOfKeyColumns ); for ( int j = 0; j < numberOfKeyColumns; j++ ) { final ColumnReference columnReference = columnReferences.get( j ); final JdbcParameter jdbcParameter = new JdbcParameterImpl( columnReference.getJdbcMapping() ); @@ -354,8 +372,6 @@ private void applyKeyRestriction( } } - private Map orderByFragments; - private void applyOrdering(TableGroup tableGroup, PluralAttributeMapping pluralAttributeMapping) { if ( pluralAttributeMapping.getOrderByFragment() != null ) { applyOrdering( tableGroup, pluralAttributeMapping.getOrderByFragment() ); @@ -366,36 +382,33 @@ private void applyOrdering(TableGroup tableGroup, PluralAttributeMapping pluralA } } - private void applyOrdering( - TableGroup tableGroup, - OrderByFragment orderByFragment) { + private void applyOrdering(TableGroup tableGroup, OrderByFragment orderByFragment) { if ( orderByFragments == null ) { orderByFragments = new LinkedHashMap<>(); } orderByFragments.put( orderByFragment, tableGroup ); } - private int fetchDepth = 0; - private List visitFetches(FetchParent fetchParent, QuerySpec querySpec, LoaderSqlAstCreationState creationState) { log.tracef( "Starting visitation of FetchParent's Fetchables : %s", fetchParent.getNavigablePath() ); final List fetches = new ArrayList<>(); - final Consumer processor = createFetchableConsumer( fetchParent, querySpec, creationState, fetches ); + final BiConsumer processor = createFetchableBiConsumer( fetchParent, querySpec, creationState, fetches ); final FetchableContainer referencedMappingContainer = fetchParent.getReferencedMappingContainer(); - referencedMappingContainer.visitKeyFetchables( processor, null ); - referencedMappingContainer.visitFetchables( processor, null ); + referencedMappingContainer.visitKeyFetchables( fetchable -> processor.accept( fetchable, true ), null ); + referencedMappingContainer.visitFetchables( fetchable -> processor.accept( fetchable, false ), null ); return fetches; } - private Consumer createFetchableConsumer( + private BiConsumer createFetchableBiConsumer( FetchParent fetchParent, QuerySpec querySpec, - LoaderSqlAstCreationState creationState, List fetches) { - return fetchable -> { + LoaderSqlAstCreationState creationState, + List fetches) { + return (fetchable, isKeyFetchable) -> { final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() ); @@ -410,10 +423,37 @@ private Consumer createFetchableConsumer( return; } - LockMode lockMode = LockMode.READ; + final LockMode lockMode = LockMode.READ; FetchTiming fetchTiming = fetchable.getMappedFetchStrategy().getTiming(); boolean joined = fetchable.getMappedFetchStrategy().getStyle() == FetchStyle.JOIN; + EntityGraphNavigator.NavigateResult navigateResult = null; + + // 'entity graph' takes precedence over 'fetch profile' + if ( entityGraphNavigator != null) { + navigateResult = entityGraphNavigator.navigateIfApplicable( fetchParent, fetchable, isKeyFetchable ); + if ( navigateResult != null ) { + fetchTiming = navigateResult.getFetchStrategy(); + joined = navigateResult.isJoined(); + } + } + else if ( loadQueryInfluencers.hasEnabledFetchProfiles() ) { + if ( fetchParent instanceof EntityResultGraphNode ) { + final EntityResultGraphNode entityFetchParent = (EntityResultGraphNode) fetchParent; + final EntityMappingType entityMappingType = entityFetchParent.getEntityValuedModelPart().getEntityMappingType(); + final String fetchParentEntityName = entityMappingType.getEntityName(); + final String fetchableRole = fetchParentEntityName + "." + fetchable.getFetchableName(); + + for ( String enabledFetchProfileName : loadQueryInfluencers.getEnabledFetchProfileNames() ) { + final FetchProfile enabledFetchProfile = creationContext.getSessionFactory().getFetchProfile( enabledFetchProfileName ); + final org.hibernate.engine.profile.Fetch profileFetch = enabledFetchProfile.getFetchByRole( fetchableRole ); + + fetchTiming = FetchTiming.IMMEDIATE; + joined = joined || profileFetch.getStyle() == org.hibernate.engine.profile.Fetch.Style.JOIN; + } + } + } + final Integer maximumFetchDepth = creationContext.getMaximumFetchDepth(); if ( maximumFetchDepth != null ) { @@ -429,7 +469,7 @@ else if ( fetchDepth > maximumFetchDepth ) { if ( !( fetchable instanceof BasicValuedModelPart ) ) { fetchDepth++; } - Fetch fetch = fetchable.generateFetch( + final Fetch fetch = fetchable.generateFetch( fetchParent, fetchablePath, fetchTiming, @@ -453,6 +493,9 @@ else if ( fetchDepth > maximumFetchDepth ) { if ( !( fetchable instanceof BasicValuedModelPart ) ) { fetchDepth--; } + if ( entityGraphNavigator != null && navigateResult != null ) { + entityGraphNavigator.backtrack( navigateResult.getPreviousContext() ); + } } }; } @@ -512,7 +555,7 @@ private SelectStatement generateSelect(SubselectFetch subselect) { sqlAstCreationState.getFromClauseAccess().registerTableGroup( rootNavigablePath, rootTableGroup ); // NOTE : no need to check - we are explicitly processing a plural-attribute - applyOrdering( rootTableGroup, (PluralAttributeMapping) loadable ); + applyOrdering( rootTableGroup, attributeMapping ); // generate and apply the restriction applySubSelectRestriction( @@ -577,7 +620,7 @@ private void applySubSelectRestriction( else { final List columnReferences = new ArrayList<>( jdbcTypeCount ); fkDescriptor.visitColumns( - (containingTableExpression, columnExpression, jdbcMapping) -> { + (containingTableExpression, columnExpression, jdbcMapping) -> columnReferences.add( (ColumnReference) sqlAstCreationState.getSqlExpressionResolver().resolveSqlExpression( createColumnReferenceKey( containingTableExpression, columnExpression ), @@ -588,8 +631,7 @@ private void applySubSelectRestriction( this.creationContext.getSessionFactory() ) ) - ); - } + ) ); fkExpression = new SqlTuple( columnReferences, fkDescriptor ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java index 0b0ba281f0b4..50d0acd6fc30 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/StandardSqmSelectTranslator.java @@ -11,7 +11,6 @@ import java.util.Locale; import java.util.Map; import java.util.function.BiConsumer; -import java.util.function.Consumer; import org.hibernate.HibernateException; import org.hibernate.LockMode; @@ -20,8 +19,6 @@ import org.hibernate.engine.FetchTiming; import org.hibernate.engine.profile.FetchProfile; import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.graph.spi.AttributeNodeImplementor; -import org.hibernate.graph.spi.GraphImplementor; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; @@ -64,11 +61,13 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.EntityGraphNavigator; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; import org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiation; +import org.hibernate.sql.results.internal.StandardEntityGraphNavigatorImpl; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** @@ -86,7 +85,7 @@ public class StandardSqmSelectTranslator // prepare for 10 root selections to avoid list growth in most cases private final List domainResults = CollectionHelper.arrayList( 10 ); - private GraphImplementor currentJpaGraphNode; + private final EntityGraphNavigator entityGraphNavigator; public StandardSqmSelectTranslator( QueryOptions queryOptions, @@ -97,10 +96,13 @@ public StandardSqmSelectTranslator( super( creationContext, queryOptions, domainParameterXref, domainParameterBindings ); this.fetchInfluencers = fetchInfluencers; - if ( fetchInfluencers != null ) { - if ( fetchInfluencers.getEffectiveEntityGraph().getSemantic() != null ) { - currentJpaGraphNode = fetchInfluencers.getEffectiveEntityGraph().getGraph(); - } + if ( fetchInfluencers != null + && fetchInfluencers.getEffectiveEntityGraph() != null + && fetchInfluencers.getEffectiveEntityGraph().getSemantic() != null ) { + this.entityGraphNavigator = new StandardEntityGraphNavigatorImpl( fetchInfluencers.getEffectiveEntityGraph() ); + } + else { + this.entityGraphNavigator = null; } } @@ -234,10 +236,7 @@ public ModelPart resolveModelPart(NavigablePath navigablePath) { public List visitFetches(FetchParent fetchParent) { final List fetches = CollectionHelper.arrayList( fetchParent.getReferencedMappingType().getNumberOfFetchables() ); - //noinspection Convert2Lambda - final Consumer fetchableConsumer = new Consumer() { - @Override - public void accept(Fetchable fetchable) { + final BiConsumer fetchableBiConsumer = (fetchable, isKeyFetchable) -> { final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() ); final Fetch biDirectionalFetch = fetchable.resolveCircularFetch( @@ -253,7 +252,7 @@ public void accept(Fetchable fetchable) { try { fetchDepth++; - final Fetch fetch = buildFetch( fetchablePath, fetchParent, fetchable ); + final Fetch fetch = buildFetch( fetchablePath, fetchParent, fetchable, isKeyFetchable ); if ( fetch != null ) { fetches.add( fetch ); @@ -262,32 +261,31 @@ public void accept(Fetchable fetchable) { finally { fetchDepth--; } - } }; // todo (6.0) : determine how to best handle TREAT -// fetchParent.getReferencedMappingContainer().visitKeyFetchables( fetchableConsumer, treatTargetType ); -// fetchParent.getReferencedMappingContainer().visitFetchables( fetchableConsumer, treatTargetType ); - fetchParent.getReferencedMappingContainer().visitKeyFetchables( fetchableConsumer, null ); - fetchParent.getReferencedMappingContainer().visitFetchables( fetchableConsumer, null ); +// fetchParent.getReferencedMappingContainer().visitKeyFetchables( fetchableBiConsumer, treatTargetType ); +// fetchParent.getReferencedMappingContainer().visitFetchables( fetchableBiConsumer, treatTargetType ); + fetchParent.getReferencedMappingContainer().visitKeyFetchables( fetchable -> fetchableBiConsumer.accept( fetchable, true ), null ); + fetchParent.getReferencedMappingContainer().visitFetchables( fetchable -> fetchableBiConsumer.accept( fetchable, false ), null ); return fetches; } - private Fetch buildFetch(NavigablePath fetchablePath, FetchParent fetchParent, Fetchable fetchable) { + private Fetch buildFetch(NavigablePath fetchablePath, FetchParent fetchParent, Fetchable fetchable, boolean isKeyFetchable) { // fetch has access to its parent in addition to the parent having its fetches. // // we could sever the parent -> fetch link ... it would not be "seen" while walking // but it would still have access to its parent info - and be able to access its // "initializing" state as part of AfterLoadAction - final GraphImplementor previousGraphNode = currentJpaGraphNode; - final String alias; LockMode lockMode = LockMode.READ; FetchTiming fetchTiming = fetchable.getMappedFetchStrategy().getTiming(); boolean joined = false; + EntityGraphNavigator.NavigateResult navigateResult = null; + final SqmAttributeJoin fetchedJoin = getFromClauseIndex().findFetchedJoinByPath( fetchablePath ); if ( fetchedJoin != null ) { @@ -308,13 +306,11 @@ private Fetch buildFetch(NavigablePath fetchablePath, FetchParent fetchParent, F // there was not an explicit fetch in the SQM alias = null; - // see if we have any "influencer" in effect that indicates - if ( this.currentJpaGraphNode != null && appliesTo( this.currentJpaGraphNode, fetchParent ) ) { - final AttributeNodeImplementor attributeNode = this.currentJpaGraphNode.findAttributeNode( fetchable.getFetchableName() ); - // todo (6.0) : need to account for `org.hibernate.graph.GraphSemantic` here as well - if ( attributeNode != null ) { - fetchTiming = FetchTiming.IMMEDIATE; - joined = true; + if ( entityGraphNavigator != null ) { + navigateResult = entityGraphNavigator.navigateIfApplicable( fetchParent, fetchable, isKeyFetchable ); + if ( navigateResult != null ) { + fetchTiming = navigateResult.getFetchStrategy(); + joined = navigateResult.isJoined(); } } else if ( fetchInfluencers.hasEnabledFetchProfiles() ) { @@ -422,22 +418,10 @@ else if ( fetchDepth > maxDepth ) { ); } finally { - currentJpaGraphNode = previousGraphNode; - } - } - - private static boolean appliesTo(GraphImplementor graphNode, FetchParent fetchParent) { - if ( ! ( fetchParent instanceof EntityResultGraphNode ) ) { - return false; + if ( entityGraphNavigator != null && navigateResult != null ) { + entityGraphNavigator.backtrack( navigateResult.getPreviousContext() ); + } } - - final EntityResultGraphNode entityFetchParent = (EntityResultGraphNode) fetchParent; - final EntityMappingType entityFetchParentMappingType = entityFetchParent.getEntityValuedModelPart().getEntityMappingType(); - - assert graphNode.getGraphedType() instanceof EntityDomainType; - final EntityDomainType entityDomainType = (EntityDomainType) graphNode.getGraphedType(); - - return entityDomainType.getHibernateEntityName().equals( entityFetchParentMappingType.getEntityName() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/EntityGraphNavigator.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/EntityGraphNavigator.java new file mode 100644 index 000000000000..7f34fff03e75 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/EntityGraphNavigator.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.graph; + +import org.hibernate.engine.FetchTiming; +import org.hibernate.graph.spi.GraphImplementor; + +/** + * @author Nathan Xu + */ +public interface EntityGraphNavigator { + + /** + * Pojo class to store the result of applied entity graph navigation, including + *
    + *
  • previous entity graph node so later on navigator can backtrack to it
  • + *
  • whether the new graph node should be eagerly loaded or not
  • + *
  • whether the new graph node fetching is joined
  • + *
+ */ + class NavigateResult { + + private GraphImplementor previousContext; + private FetchTiming fetchTiming; + private boolean joined; + + public NavigateResult(GraphImplementor previousContext, FetchTiming fetchTiming, boolean joined) { + this.previousContext = previousContext; + this.fetchTiming = fetchTiming; + this.joined = joined; + } + + public GraphImplementor getPreviousContext() { + return previousContext; + } + + public FetchTiming getFetchStrategy() { + return fetchTiming; + } + + public boolean isJoined() { + return joined; + } + } + + /** + * Backtrack to previous entity graph status after the current children navigating has been done. + * Mainly reset the current context entity graph node to the passed method parameter. + * + * @param previousContext The stored previous invocation result; should not be null + * @see #navigateIfApplicable(FetchParent, Fetchable, boolean) + */ + void backtrack(GraphImplementor previousContext); + + /** + * Tries to navigate from parent to child node within entity graph and returns non-null {@code NavigateResult} + * if applicable. Returns null value if not applicable. + * + * @apiNote If applicable, internal state will be mutated. Not thread safe and should be used within single thread. + * @param parent The FetchParent + * @param fetchable The Fetchable + * @param exploreKeySubgraph true if only key sub graph is explored; false if key sub graph is excluded + * @return {@link NavigateResult} if applicable; null otherwise + */ + NavigateResult navigateIfApplicable(FetchParent parent, Fetchable fetchable, boolean exploreKeySubgraph); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java index eefbd579f2af..e3db64b96aea 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java @@ -73,4 +73,11 @@ public boolean hasTableGroup() { return true; } + public EntityResultImpl getEntityResult() { + return entityResult; + } + + public LockMode getLockMode() { + return lockMode; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphNavigatorImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphNavigatorImpl.java new file mode 100644 index 000000000000..5a96487a5588 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphNavigatorImpl.java @@ -0,0 +1,121 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal; + +import java.util.Map; +import javax.persistence.metamodel.PluralAttribute; + +import org.hibernate.engine.FetchStyle; +import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.spi.EffectiveEntityGraph; +import org.hibernate.graph.GraphSemantic; +import org.hibernate.graph.spi.AttributeNodeImplementor; +import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.graph.spi.SubGraphImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.sql.results.graph.EntityGraphNavigator; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; + +/** + * @author Nathan Xu + */ +public class StandardEntityGraphNavigatorImpl implements EntityGraphNavigator { + + private final GraphSemantic graphSemantic; + private GraphImplementor currentGraphContext; + + public StandardEntityGraphNavigatorImpl(EffectiveEntityGraph effectiveEntityGraph) { + assert effectiveEntityGraph != null; + if ( effectiveEntityGraph.getSemantic() == null ) { + throw new IllegalArgumentException( "The graph has not defined semantic: " + effectiveEntityGraph ); + } + this.graphSemantic = effectiveEntityGraph.getSemantic(); + this.currentGraphContext = effectiveEntityGraph.getGraph(); + } + + @Override + public void backtrack(GraphImplementor previousContext) { + currentGraphContext = previousContext; + } + + @Override + public NavigateResult navigateIfApplicable(FetchParent fetchParent, Fetchable fetchable, boolean exploreKeySubgraph) { + final GraphImplementor previousContextRoot = currentGraphContext; + FetchTiming fetchTiming = null; + boolean joined = false; + if ( appliesTo( fetchParent ) ) { + final AttributeNodeImplementor attributeNode = currentGraphContext.findAttributeNode( fetchable.getFetchableName() ); + if ( attributeNode != null ) { + fetchTiming = FetchTiming.IMMEDIATE; + joined = true; + + final Map, SubGraphImplementor> subgraphMap; + final Class subgraphMapKey; + + if ( fetchable instanceof PluralAttributeMapping ) { + PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; + + // avoid '^' bitwise operator to improve code readability + assert exploreKeySubgraph && isJpaMapCollectionType( pluralAttributeMapping ) + || !exploreKeySubgraph && !isJpaMapCollectionType( pluralAttributeMapping ); + + if ( exploreKeySubgraph ) { + subgraphMap = attributeNode.getKeySubGraphMap(); + subgraphMapKey = pluralAttributeMapping.getIndexDescriptor().getClass(); + } + else { + subgraphMap = attributeNode.getSubGraphMap(); + subgraphMapKey = pluralAttributeMapping.getElementDescriptor().getClass(); + } + } + else { + assert !exploreKeySubgraph; + subgraphMap = attributeNode.getSubGraphMap(); + subgraphMapKey = fetchable.getJavaTypeDescriptor().getJavaType(); + } + currentGraphContext = subgraphMap == null ? null : subgraphMap.get( subgraphMapKey ); + } + else { + currentGraphContext = null; + } + } + if ( fetchTiming == null ) { + if ( graphSemantic == GraphSemantic.FETCH ) { + fetchTiming = FetchTiming.DELAYED; + joined = false; + } + else { + fetchTiming = fetchable.getMappedFetchStrategy().getTiming(); + joined = fetchable.getMappedFetchStrategy().getStyle() == FetchStyle.JOIN; + } + } + return new NavigateResult( previousContextRoot, fetchTiming, joined ); + } + + private boolean appliesTo(FetchParent fetchParent) { + if ( currentGraphContext == null || !( fetchParent instanceof EntityResultGraphNode ) ) { + return false; + } + + final EntityResultGraphNode entityFetchParent = (EntityResultGraphNode) fetchParent; + final EntityMappingType entityFetchParentMappingType = entityFetchParent.getEntityValuedModelPart().getEntityMappingType(); + + assert currentGraphContext.getGraphedType() instanceof EntityDomainType; + final EntityDomainType entityDomainType = (EntityDomainType) currentGraphContext.getGraphedType(); + + return entityDomainType.getHibernateEntityName().equals( entityFetchParentMappingType.getEntityName() ); + } + + private static boolean isJpaMapCollectionType(PluralAttributeMapping pluralAttributeMapping) { + return pluralAttributeMapping.getCollectionDescriptor().getCollectionSemantics().getCollectionClassification().toJpaClassification() == PluralAttribute.CollectionType.MAP; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/entitygraph/EntityGraphLoadPlanBuilderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/entitygraph/EntityGraphLoadPlanBuilderTest.java new file mode 100644 index 000000000000..7a8e378ed121 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/entitygraph/EntityGraphLoadPlanBuilderTest.java @@ -0,0 +1,461 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.loading.entitygraph; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.EffectiveEntityGraph; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.graph.GraphSemantic; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.loader.ast.internal.LoaderSelectBuilder; +import org.hibernate.metamodel.mapping.EntityValuedModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.sql.ast.tree.from.CompositeTableGroup; +import org.hibernate.sql.ast.tree.from.FromClause; +import org.hibernate.sql.ast.tree.from.StandardTableGroup; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableGroupJoin; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.collection.internal.DelayedCollectionFetch; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.entity.EntityFetch; +import org.hibernate.sql.results.graph.entity.EntityResult; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchDelayedImpl; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hibernate.testing.hamcrest.AssignableMatcher.assignableTo; +import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize; +import static org.hibernate.testing.hamcrest.CollectionMatchers.isEmpty; +import static org.junit.Assert.assertThat; + +/** + * @author Strong Liu + * @author Steve Ebersole + * @author Nathan Xu + */ +@DomainModel( + annotatedClasses = { + EntityGraphLoadPlanBuilderTest.Cat.class, + EntityGraphLoadPlanBuilderTest.Person.class, + EntityGraphLoadPlanBuilderTest.Country.class, + EntityGraphLoadPlanBuilderTest.Dog.class, + EntityGraphLoadPlanBuilderTest.ExpressCompany.class + } +) +@SessionFactory +public class EntityGraphLoadPlanBuilderTest { + + @Test + void testBasicFetchLoadPlanBuilding(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + final RootGraphImplementor eg = em.createEntityGraph( Cat.class ); + + final SelectStatement sqlAst = buildSqlSelectAst( Cat.class, eg, GraphSemantic.FETCH, scope ); + + // Check the from-clause + assertEmptyJoinedGroup( sqlAst ); + + // Check the domain-result graph + assertDomainResult( sqlAst, Cat.class, "owner", Person.class, + entityFetch -> assertThat( entityFetch, instanceOf( EntityFetchDelayedImpl.class ) ) + ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13756" ) + void testFetchLoadPlanBuildingWithSubgraph(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + final RootGraphImplementor eg = em.createEntityGraph( Cat.class ); + eg.addSubgraph( "owner", Person.class ); + + final SelectStatement sqlAst = buildSqlSelectAst( Cat.class, eg, GraphSemantic.FETCH, scope ); + + // Check the from-clause + assertEntityValuedJoinedGroup( sqlAst, "owner", Person.class, this::assertPersonHomeAddressJoinedGroup ); + + // Check the domain-result graph + assertDomainResult( sqlAst, Cat.class, "owner", Person.class, entityFetch -> {} ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13756" ) + void testFetchLoadPlanBuildingWithDeepSubgraph(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + final RootGraphImplementor eg = em.createEntityGraph( Cat.class ); + eg.addSubgraph( "owner", Person.class ).addSubgraph( "company", ExpressCompany.class ); + + final SelectStatement sqlAst = buildSqlSelectAst( Cat.class, eg, GraphSemantic.FETCH, scope ); + + // Check the from-clause + assertEntityValuedJoinedGroup( sqlAst, "owner", Person.class, tableGroup -> { + Set tableGroupJoins = tableGroup.getTableGroupJoins(); + Map> tableGroupByName = tableGroupJoins.stream() + .map( TableGroupJoin::getJoinedGroup ) + .collect( Collectors.toMap( + tg -> tg.getModelPart().getPartName(), + TableGroup::getClass + ) ); + Map > expectedTableGroupByName = new HashMap<>(); + expectedTableGroupByName.put( "homeAddress", CompositeTableGroup.class ); + expectedTableGroupByName.put( "company", StandardTableGroup.class ); + assertThat( tableGroupByName, is( expectedTableGroupByName ) ); + } ); + + // Check the domain-result graph + assertDomainResult( sqlAst, Cat.class, "owner", Person.class, entityFetch -> { + assertThat( entityFetch, instanceOf( EntityFetchJoinedImpl.class ) ); + final EntityResult ownerEntityResult = ( (EntityFetchJoinedImpl) entityFetch ).getEntityResult(); + final Map> fetchClassByAttributeName = ownerEntityResult.getFetches() + .stream().collect( Collectors.toMap( + fetch -> fetch.getFetchedMapping().getPartName(), + Fetch::getClass + ) ); + final Map> expectedFetchClassByAttributeName = new HashMap<>(); + expectedFetchClassByAttributeName.put( "homeAddress", EmbeddableFetchImpl.class ); + expectedFetchClassByAttributeName.put( "pets", DelayedCollectionFetch.class ); + expectedFetchClassByAttributeName.put( "company", EntityFetchJoinedImpl.class ); + assertThat( fetchClassByAttributeName, is( expectedFetchClassByAttributeName ) ); + + final Fetch companyFetch = ownerEntityResult.findFetch( "company" ); + assertThat( companyFetch, notNullValue() ); + + final EntityResult companyEntityResult = ( (EntityFetchJoinedImpl) companyFetch).getEntityResult(); + assertThat( companyEntityResult.getFetches(), hasSize( 1 ) ); + + final Fetch shipAddressesFetch = companyEntityResult.getFetches().get( 0 ); + assertThat( shipAddressesFetch.getFetchedMapping().getPartName(), is( "shipAddresses" ) ); + assertThat( shipAddressesFetch, instanceOf( DelayedCollectionFetch.class ) ); + } ); + } + ); + } + + @Test + void testBasicLoadLoadPlanBuilding(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + final RootGraphImplementor eg = em.createEntityGraph( Cat.class ); + + final SelectStatement sqlAst = buildSqlSelectAst( Cat.class, eg, GraphSemantic.LOAD, scope ); + + // Check the from-clause + assertEmptyJoinedGroup( sqlAst ); + + // Check the domain-result graph + assertDomainResult( sqlAst, Cat.class, "owner", Person.class, + entityFetch -> assertThat( entityFetch, instanceOf( EntityFetchDelayedImpl.class ) ) ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13756" ) + void testLoadLoadPlanBuildingWithSubgraph(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + final RootGraphImplementor eg = em.createEntityGraph( Cat.class ); + eg.addSubgraph( "owner", Person.class ); + + final SelectStatement sqlAst = buildSqlSelectAst( Cat.class, eg, GraphSemantic.LOAD, scope ); + + // Check the from-clause + assertEntityValuedJoinedGroup( sqlAst, "owner", Person.class, this::assertPersonHomeAddressJoinedGroup ); + + // Check the domain-result graph + assertDomainResult( sqlAst, Cat.class, "owner", Person.class, entityFetch -> { + assertThat( entityFetch, instanceOf( EntityFetchJoinedImpl.class ) ); + final EntityResult entityResult = ( (EntityFetchJoinedImpl) entityFetch ).getEntityResult(); + final Map> fetchClassByAttributeName = entityResult.getFetches().stream().collect( Collectors.toMap( + fetch -> fetch.getFetchedMapping().getPartName(), + Fetch::getClass + ) ); + final Map> expectedFetchClassByAttributeName = new HashMap<>(); + expectedFetchClassByAttributeName.put( "pets", DelayedCollectionFetch.class ); + expectedFetchClassByAttributeName.put( "homeAddress", EmbeddableFetchImpl.class ); + expectedFetchClassByAttributeName.put( "company", EntityFetchDelayedImpl.class ); + assertThat( fetchClassByAttributeName, is( expectedFetchClassByAttributeName ) ); + } ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13756" ) + void testBasicElementCollectionsLoadGraph(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + final RootGraphImplementor eg = em.createEntityGraph( Dog.class ); + eg.addAttributeNodes( "favorites" ); + + final SelectStatement sqlAst = buildSqlSelectAst( Dog.class, eg, GraphSemantic.LOAD, scope ); + + // Check the from-clause + assertPluralAttributeJoinedGroup( sqlAst, "favorites" ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13756" ) + void testBasicElementCollectionsFetchGraph(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + final RootGraphImplementor eg = em.createEntityGraph( Dog.class ); + eg.addAttributeNodes( "favorites" ); + + final SelectStatement sqlAst = buildSqlSelectAst( Dog.class, eg, GraphSemantic.FETCH, scope ); + + // Check the from-clause + assertPluralAttributeJoinedGroup( sqlAst, "favorites" ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13756" ) + void testEmbeddedCollectionLoadSubgraph(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + final RootGraphImplementor eg = em.createEntityGraph( ExpressCompany.class ); + eg.addAttributeNodes( "shipAddresses" ); + + final SelectStatement sqlAst = buildSqlSelectAst( + ExpressCompany.class, + eg, GraphSemantic.LOAD, + scope + ); + + // Check the from-clause + assertPluralAttributeJoinedGroup( sqlAst, "shipAddresses" ); + + } + ); + } + + // util methods for verifying 'from-clause' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private void assertEmptyJoinedGroup(SelectStatement sqlAst) { + final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); + assertThat( fromClause.getRoots(), hasSize( 1 ) ); + + final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); + assertThat( rootTableGroup.getTableGroupJoins(), isEmpty() ); + } + + private void assertEntityValuedJoinedGroup(SelectStatement sqlAst, String expectedAttributeName, Class expectedEntityJpaClass, Consumer tableGroupConsumer) { + final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); + assertThat( fromClause.getRoots(), hasSize( 1 ) ); + + final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); + assertThat( rootTableGroup.getTableGroupJoins(), hasSize( 1 ) ); + + final TableGroup joinedGroup = rootTableGroup.getTableGroupJoins().iterator().next().getJoinedGroup(); + assertThat( joinedGroup.getModelPart(), instanceOf( EntityValuedModelPart.class ) ); + + final EntityValuedModelPart entityValuedModelPart = (EntityValuedModelPart) joinedGroup.getModelPart(); + assertThat( entityValuedModelPart.getPartName(), is( expectedAttributeName ) ); + assertThat( entityValuedModelPart.getEntityMappingType().getJavaTypeDescriptor().getJavaType(), assignableTo( expectedEntityJpaClass ) ); + tableGroupConsumer.accept( joinedGroup ); + } + + private void assertPluralAttributeJoinedGroup(SelectStatement sqlAst, String expectedPluralAttributeName) { + final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); + assertThat( fromClause.getRoots(), hasSize( 1 ) ); + + final TableGroup root = fromClause.getRoots().get( 0 ); + assertThat( root.getTableGroupJoins(), hasSize( 1 ) ); + + final TableGroup joinedGroup = root.getTableGroupJoins().iterator().next().getJoinedGroup(); + assertThat( joinedGroup.getModelPart(), instanceOf( PluralAttributeMapping.class ) ); + + final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) joinedGroup.getModelPart(); + assertThat( pluralAttributeMapping.getAttributeName(), is( expectedPluralAttributeName ) ); + assertThat( joinedGroup.getTableGroupJoins(), isEmpty() ); + } + + private void assertPersonHomeAddressJoinedGroup(TableGroup tableGroup) { + assertThat( tableGroup.getTableGroupJoins(), hasSize( 1 ) ); + + final TableGroupJoin tableGroupJoin = tableGroup.getTableGroupJoins().iterator().next(); + assertThat( tableGroupJoin.getJoinedGroup(), instanceOf( CompositeTableGroup.class ) ); + + final CompositeTableGroup compositeTableGroup = (CompositeTableGroup) tableGroupJoin.getJoinedGroup(); + assertThat( compositeTableGroup.getModelPart(), instanceOf( EmbeddedAttributeMapping.class ) ); + + final EmbeddedAttributeMapping embeddedAttributeMapping = (EmbeddedAttributeMapping) compositeTableGroup.getModelPart(); + assertThat( embeddedAttributeMapping.getPartName(), is( "homeAddress" ) ); + } + + // util methods for verifying 'domain-result' graph ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private void assertDomainResult(SelectStatement sqlAst, + Class expectedEntityJpaClass, + String expectedAttributeName, + Class expectedAttributeEntityJpaClass, + Consumer entityFetchConsumer) { + assertThat( sqlAst.getDomainResultDescriptors(), hasSize( 1 ) ); + + final DomainResult domainResult = sqlAst.getDomainResultDescriptors().get( 0 ); + assertThat( domainResult, instanceOf( EntityResult.class ) ); + + final EntityResult entityResult = (EntityResult) domainResult; + assertThat( entityResult.getReferencedModePart().getJavaTypeDescriptor().getJavaType(), assignableTo( expectedEntityJpaClass ) ); + assertThat( entityResult.getFetches(), hasSize( 1 ) ); + + final Fetch fetch = entityResult.getFetches().get( 0 ); + assertThat( fetch, instanceOf( EntityFetch.class ) ); + + final EntityFetch entityFetch = (EntityFetch) fetch; + assertThat( entityFetch.getFetchedMapping().getFetchableName(), is( expectedAttributeName ) ); + assertThat( entityFetch.getReferencedModePart().getJavaTypeDescriptor().getJavaType(), assignableTo( expectedAttributeEntityJpaClass ) ); + + entityFetchConsumer.accept( entityFetch ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13756" ) + void testEmbeddedCollectionFetchSubgraph(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + final RootGraphImplementor eg = em.createEntityGraph( ExpressCompany.class ); + eg.addAttributeNodes( "shipAddresses" ); + + final SelectStatement sqlAst = buildSqlSelectAst( + ExpressCompany.class, + eg, GraphSemantic.FETCH, + scope + ); + + final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); + assertThat( fromClause.getRoots(), hasSize( 1 ) ); + + final TableGroup root = fromClause.getRoots().get( 0 ); + assertThat( root.getTableGroupJoins(), hasSize( 1 ) ); + + final TableGroup joinedGroup = root.getTableGroupJoins().iterator().next().getJoinedGroup(); + assertThat( joinedGroup.getModelPart(), instanceOf( PluralAttributeMapping.class ) ); + + final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) joinedGroup.getModelPart(); + assertThat( pluralAttributeMapping.getAttributeName(), is( "shipAddresses" ) ); + assertThat( joinedGroup.getTableGroupJoins(), isEmpty() ); + } + ); + } + + private SelectStatement buildSqlSelectAst( + Class entityType, + RootGraphImplementor entityGraph, + GraphSemantic mode, + SessionFactoryScope scope) { + final EntityPersister entityDescriptor = scope.getSessionFactory().getDomainModel().getEntityDescriptor( entityType ); + + final LoadQueryInfluencers loadQueryInfluencers = new LoadQueryInfluencers( scope.getSessionFactory() ); + final EffectiveEntityGraph effectiveEntityGraph = loadQueryInfluencers.getEffectiveEntityGraph(); + effectiveEntityGraph.applyGraph( entityGraph, mode ); + + return LoaderSelectBuilder.createSelect( + entityDescriptor, + null, + entityDescriptor.getIdentifierMapping(), + null, + 1, + loadQueryInfluencers, + LockOptions.READ, + jdbcParameter -> {}, + scope.getSessionFactory() + ); + } + + + @Entity + public static class Dog { + @Id + String name; + + @ElementCollection + Set favorites; + } + + @Entity + public static class Cat { + @Id + String name; + + @ManyToOne(fetch = FetchType.LAZY) + Person owner; + } + + @Entity + public static class Person { + @Id + String name; + + @OneToMany(mappedBy = "owner") + Set pets; + + @Embedded + Address homeAddress; + + @ManyToOne(fetch = FetchType.LAZY) + ExpressCompany company; + } + + @Embeddable + public static class Address { + @ManyToOne + Country country; + } + + @Entity + public static class ExpressCompany { + @Id + String name; + + @ElementCollection + Set
shipAddresses; + } + + @Entity + public static class Country { + @Id + String name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/graphs/EntityGraphLoadPlanBuilderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/graphs/EntityGraphLoadPlanBuilderTest.java deleted file mode 100644 index c681657966a5..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/graphs/EntityGraphLoadPlanBuilderTest.java +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.orm.test.loading.graphs; - -import java.util.Set; -import javax.persistence.ElementCollection; -import javax.persistence.Embeddable; -import javax.persistence.Embedded; -import javax.persistence.Entity; -import javax.persistence.EntityGraph; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; - -import org.hibernate.LockOptions; -import org.hibernate.engine.spi.EffectiveEntityGraph; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.graph.GraphSemantic; -import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.loader.ast.internal.LoaderSelectBuilder; -import org.hibernate.metamodel.mapping.EntityValuedModelPart; -import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.sql.ast.tree.from.FromClause; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableGroupJoin; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.results.graph.entity.EntityFetch; -import org.hibernate.sql.results.graph.entity.internal.EntityFetchDelayedImpl; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.entity.EntityResult; -import org.hibernate.sql.results.graph.Fetch; - -import org.hibernate.testing.hamcrest.AssignableMatcher; -import org.hibernate.testing.hamcrest.CollectionMatchers; -import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.FailureExpected; -import org.hibernate.testing.orm.junit.SessionFactory; -import org.hibernate.testing.orm.junit.SessionFactoryScope; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize; -import static org.hibernate.testing.hamcrest.CollectionMatchers.isEmpty; -import static org.junit.Assert.assertThat; - -/** - * @author Strong Liu - * @author Steve Ebersole - */ -@DomainModel( - annotatedClasses = { - EntityGraphLoadPlanBuilderTest.Cat.class, - EntityGraphLoadPlanBuilderTest.Person.class, - EntityGraphLoadPlanBuilderTest.Country.class, - EntityGraphLoadPlanBuilderTest.Dog.class, - EntityGraphLoadPlanBuilderTest.ExpressCompany.class - } -) -@SessionFactory -public class EntityGraphLoadPlanBuilderTest { - - /** - * EntityGraph: - * - * Cat - * - * LoadPlan: - * - * Cat - */ - @Test - public void testBasicFetchLoadPlanBuilding(SessionFactoryScope scope) { - scope.inTransaction( - em -> { - EntityGraph eg = em.createEntityGraph( Cat.class ); - - final SelectStatement sqlAst = buildSqlSelectAst( Cat.class, eg, GraphSemantic.FETCH, scope ); - - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots().size(), is( 1 ) ); - final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); - assertThat( rootTableGroup.getTableGroupJoins(), CollectionMatchers.isEmpty() ); - - assertThat( sqlAst.getDomainResultDescriptors(), hasSize( 1 ) ); - final DomainResult domainResult = sqlAst.getDomainResultDescriptors().get( 0 ); - - assertThat( domainResult, instanceOf( EntityResult.class ) ); - final EntityResult entityResult = (EntityResult) domainResult; - - assertThat( - domainResult.getResultJavaTypeDescriptor().getJavaType(), - AssignableMatcher.assignableTo( Cat.class ) - ); - - assertThat( entityResult.getFetches(), hasSize( 1 ) ); - assertThat( entityResult.getFetches().get( 0 ), instanceOf( EntityFetchDelayedImpl.class ) ); - } - ); - } - - - /** - * EntityGraph: - * - * Cat - * owner -- Person - * - * LoadPlan: - * - * Cat - * owner -- Person - * address --- Address - */ - @Test - @FailureExpected( jiraKey = "HHH-13756", reason = "EntityGraph support not yet implemented" ) - public void testFetchLoadPlanBuildingWithSubgraph(SessionFactoryScope scope) { - scope.inTransaction( - em -> { - EntityGraph eg = em.createEntityGraph( Cat.class ); - eg.addSubgraph( "owner", Person.class ); - - final SelectStatement sqlAst = buildSqlSelectAst( Cat.class, eg, GraphSemantic.FETCH, scope ); - - // Check the from-clause - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots().size(), is( 1 ) ); - final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); - assertThat( rootTableGroup.getTableGroupJoins(), hasSize( 1 ) ); - final TableGroupJoin ownerJoin = rootTableGroup.getTableGroupJoins().iterator().next(); - assertThat( ownerJoin, notNullValue() ); - assertThat( ownerJoin.getJoinedGroup().getModelPart(), instanceOf( EntityValuedModelPart.class ) ); - - - // Check the domain-result graph - assertThat( sqlAst.getDomainResultDescriptors(), hasSize( 1 ) ); - final DomainResult domainResult = sqlAst.getDomainResultDescriptors().get( 0 ); - assertThat( domainResult, instanceOf( EntityResult.class ) ); - final EntityResult catResult = (EntityResult) domainResult; - assertThat( catResult.getFetches(), hasSize( 1 ) ); - final Fetch fetch = catResult.getFetches().get( 0 ); - assertThat( fetch, instanceOf( EntityFetch.class ) ); - final EntityFetch ownerFetch = (EntityFetch) fetch; - assertThat( ownerFetch.getFetchedMapping().getFetchableName(), is( "owner" ) ); - assertThat( ownerFetch.getEntityValuedModelPart().getEntityMappingType().getEntityName(), is( Person.class.getName() ) ); - } - ); - } - - /** - * EntityGraph: - * - * Cat - * - * LoadPlan: - * - * Cat - */ - @Test - public void testBasicLoadLoadPlanBuilding(SessionFactoryScope scope) { - scope.inTransaction( - em -> { - EntityGraph eg = em.createEntityGraph( Cat.class ); - - final SelectStatement sqlAst = buildSqlSelectAst( Cat.class, eg, GraphSemantic.LOAD, scope ); - - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots(), hasSize( 1 ) ); - final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); - assertThat( rootTableGroup.getTableGroupJoins(), isEmpty() ); - - assertThat( sqlAst.getDomainResultDescriptors(), hasSize( 1 ) ); - final DomainResult domainResult = sqlAst.getDomainResultDescriptors().get( 0 ); - assertThat( domainResult, instanceOf( EntityResult.class ) ); - assertThat( domainResult.getResultJavaTypeDescriptor().getJavaType().getName(), is( Cat.class.getName() ) ); - final EntityResult entityResult = (EntityResult) domainResult; - assertThat( entityResult.getFetches(), hasSize( 1 ) ); - assertThat( entityResult.getFetches().get( 0 ), instanceOf( EntityFetchDelayedImpl.class ) ); - } - ); - } - - /** - *EntityGraph: - * - * Cat - * owner -- Person - * - * LoadPlan: - * - * Cat - * owner -- Person - * address --- Address - * country -- Country - */ - @Test - @FailureExpected( jiraKey = "HHH-13756", reason = "EntityGraph support not yet implemented" ) - public void testLoadLoadPlanBuildingWithSubgraph(SessionFactoryScope scope) { - scope.inTransaction( - em -> { - EntityGraph eg = em.createEntityGraph( Cat.class ); - eg.addSubgraph( "owner", Person.class ); - - final SelectStatement sqlAst = buildSqlSelectAst( Cat.class, eg, GraphSemantic.LOAD, scope ); - - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots(), hasSize( 1 ) ); - final TableGroup rootTableGroup = fromClause.getRoots().get( 0 ); - assertThat( rootTableGroup.getTableGroupJoins(), hasSize( 1 ) ); - - assertThat( sqlAst.getDomainResultDescriptors(), hasSize( 1 ) ); - final DomainResult domainResult = sqlAst.getDomainResultDescriptors().get( 0 ); - assertThat( domainResult, instanceOf( EntityResult.class ) ); - assertThat( ( (EntityResult) domainResult ).getFetches(), hasSize( 1 ) ); - final Fetch fetch = ( (EntityResult) domainResult ).getFetches().get( 0 ); - assertThat( fetch, instanceOf( EntityFetch.class ) ); - final EntityFetch ownerFetch = (EntityFetch) fetch; - assertThat( ownerFetch.getFetchedMapping().getFetchableName(), is( "owner" ) ); - assertThat( ownerFetch.getEntityValuedModelPart().getEntityMappingType().getEntityName(), is( Person.class.getName() ) ); - - // todo (6.0) : check the sub-fetches for Address - } - ); - } - - - @Test - @FailureExpected( jiraKey = "HHH-13756", reason = "EntityGraph support not yet implemented" ) - public void testBasicElementCollectionsLoadGraph(SessionFactoryScope scope) { - scope.inTransaction( - em -> { - EntityGraph eg = em.createEntityGraph( Dog.class ); - eg.addAttributeNodes( "favorites" ); - - final SelectStatement sqlAst = buildSqlSelectAst( Dog.class, eg, GraphSemantic.LOAD, scope ); - - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots(), hasSize( 1 ) ); - final TableGroup root = fromClause.getRoots().get( 0 ); - assertThat( root.getTableGroupJoins(), hasSize( 1 ) ); - final TableGroup joinedGroup = root.getTableGroupJoins().iterator().next().getJoinedGroup(); - assertThat( joinedGroup.getModelPart(), instanceOf( PluralAttributeMapping.class ) ); - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) joinedGroup.getModelPart(); - assertThat( pluralAttributeMapping.getAttributeName(), is( "favorites" ) ); - } - ); - } - - @Test - @FailureExpected( jiraKey = "HHH-13756", reason = "EntityGraph support not yet implemented" ) - public void testBasicElementCollectionsFetchGraph(SessionFactoryScope scope) { - scope.inTransaction( - em -> { - EntityGraph eg = em.createEntityGraph( Dog.class ); - eg.addAttributeNodes( "favorites" ); - - final SelectStatement sqlAst = buildSqlSelectAst( Dog.class, eg, GraphSemantic.FETCH, scope ); - - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots(), hasSize( 1 ) ); - final TableGroup root = fromClause.getRoots().get( 0 ); - assertThat( root.getTableGroupJoins(), hasSize( 1 ) ); - final TableGroup joinedGroup = root.getTableGroupJoins().iterator().next().getJoinedGroup(); - assertThat( joinedGroup.getModelPart(), instanceOf( PluralAttributeMapping.class ) ); - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) joinedGroup.getModelPart(); - assertThat( pluralAttributeMapping.getAttributeName(), is( "favorites" ) ); - } - ); - } - - - @Test - @FailureExpected( jiraKey = "HHH-13756", reason = "EntityGraph support not yet implemented" ) - public void testEmbeddedCollectionLoadSubgraph(SessionFactoryScope scope) { - scope.inTransaction( - em -> { - EntityGraph eg = em.createEntityGraph( ExpressCompany.class ); - eg.addAttributeNodes( "shipAddresses" ); - - final SelectStatement sqlAst = buildSqlSelectAst( - ExpressCompany.class, - eg, GraphSemantic.LOAD, - scope - ); - - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots(), hasSize( 1 ) ); - final TableGroup root = fromClause.getRoots().get( 0 ); - assertThat( root.getTableGroupJoins(), hasSize( 1 ) ); - final TableGroup joinedGroup = root.getTableGroupJoins().iterator().next().getJoinedGroup(); - assertThat( joinedGroup.getModelPart(), instanceOf( PluralAttributeMapping.class ) ); - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) joinedGroup.getModelPart(); - assertThat( pluralAttributeMapping.getAttributeName(), is( "shipAddresses" ) ); - assertThat( joinedGroup.getTableGroupJoins(), isEmpty() ); - - -// QuerySpace querySpace = loadLoadPlan.getQuerySpaces().getRootQuerySpaces().iterator().next(); -// Iterator iterator = querySpace.getJoins().iterator(); -// assertTrue( iterator.hasNext() ); -// Join collectionJoin = iterator.next(); -// assertEquals( QuerySpace.Disposition.COLLECTION, collectionJoin.getRightHandSide().getDisposition() ); -// assertFalse( iterator.hasNext() ); -// -// iterator = collectionJoin.getRightHandSide().getJoins().iterator(); -// assertTrue( iterator.hasNext() ); -// Join collectionElementJoin = iterator.next(); -// assertFalse( iterator.hasNext() ); -// assertEquals( QuerySpace.Disposition.COMPOSITE, collectionElementJoin.getRightHandSide().getDisposition() ); -// -// iterator = collectionElementJoin.getRightHandSide().getJoins().iterator(); -// assertTrue( iterator.hasNext() ); -// Join countryJoin = iterator.next(); -// assertFalse( iterator.hasNext() ); -// assertEquals( QuerySpace.Disposition.ENTITY, countryJoin.getRightHandSide().getDisposition() ); - - } - ); - } - - @Test - @FailureExpected( jiraKey = "HHH-13756", reason = "EntityGraph support not yet implemented" ) - public void testEmbeddedCollectionFetchSubgraph(SessionFactoryScope scope) { - scope.inTransaction( - em -> { - EntityGraph eg = em.createEntityGraph( ExpressCompany.class ); - eg.addAttributeNodes( "shipAddresses" ); - - final SelectStatement sqlAst = buildSqlSelectAst( - ExpressCompany.class, - eg, GraphSemantic.FETCH, - scope - ); - - final FromClause fromClause = sqlAst.getQuerySpec().getFromClause(); - assertThat( fromClause.getRoots(), hasSize( 1 ) ); - final TableGroup root = fromClause.getRoots().get( 0 ); - assertThat( root.getTableGroupJoins(), hasSize( 1 ) ); - final TableGroup joinedGroup = root.getTableGroupJoins().iterator().next().getJoinedGroup(); - assertThat( joinedGroup.getModelPart(), instanceOf( PluralAttributeMapping.class ) ); - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) joinedGroup.getModelPart(); - assertThat( pluralAttributeMapping.getAttributeName(), is( "shipAddresses" ) ); - assertThat( joinedGroup.getTableGroupJoins(), isEmpty() ); - } - ); - } - - private SelectStatement buildSqlSelectAst( - Class entityType, - EntityGraph entityGraph, - GraphSemantic mode, - SessionFactoryScope scope) { - final EntityPersister entityDescriptor = scope.getSessionFactory().getDomainModel().getEntityDescriptor( entityType ); - - final LoadQueryInfluencers loadQueryInfluencers = new LoadQueryInfluencers( scope.getSessionFactory() ); - final EffectiveEntityGraph effectiveEntityGraph = loadQueryInfluencers.getEffectiveEntityGraph(); - effectiveEntityGraph.applyGraph( ( RootGraphImplementor) entityGraph, mode ); - - return LoaderSelectBuilder.createSelect( - entityDescriptor, - null, - entityDescriptor.getIdentifierMapping(), - null, - 1, - loadQueryInfluencers, - LockOptions.READ, - jdbcParameter -> {}, - scope.getSessionFactory() - ); - } - - - - @Entity - public static class Dog { - @Id - String name; - @ElementCollection - Set favorites; - } - - @Entity - public static class Cat { - @Id - String name; - @ManyToOne(fetch = FetchType.LAZY) - Person owner; - - } - - @Entity - public static class Person { - @Id - String name; - @OneToMany(mappedBy = "owner") - Set pets; - @Embedded - Address homeAddress; - } - - @Embeddable - public static class Address { - @ManyToOne - Country country; - } - - @Entity - public static class ExpressCompany { - @Id - String name; - @ElementCollection - Set
shipAddresses; - } - - @Entity - public static class Country { - @Id - String name; - } -}