HHH-13756 implement entity graph in v6

This commit is contained in:
Nathan Xu 2020-03-07 15:55:27 -05:00 committed by Steve Ebersole
parent 62e9a674ad
commit 335c1ecd75
7 changed files with 751 additions and 493 deletions

View File

@ -12,18 +12,21 @@ import java.util.LinkedHashMap;
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.QuerySpec;
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 @@ import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnRefere
* 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 class LoaderSelectBuilder {
}
/**
* 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 class LoaderSelectBuilder {
private final LoadQueryInfluencers loadQueryInfluencers;
private final LockOptions lockOptions;
private final Consumer<JdbcParameter> jdbcParameterConsumer;
private final EntityGraphNavigator entityGraphNavigator;
private int fetchDepth;
private Map<OrderByFragment, TableGroup> orderByFragments;
private LoaderSelectBuilder(
SqlAstCreationContext creationContext,
@ -170,6 +180,14 @@ public class 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 @@ public class LoaderSelectBuilder {
}
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 @@ public class LoaderSelectBuilder {
final InListPredicate predicate = new InListPredicate( tuple );
for ( int i = 0; i < numberOfKeysToLoad; i++ ) {
final List<JdbcParameter> tupleParams = new ArrayList<>( );
final List<JdbcParameter> 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 @@ public class LoaderSelectBuilder {
}
}
private Map<OrderByFragment,TableGroup> orderByFragments;
private void applyOrdering(TableGroup tableGroup, PluralAttributeMapping pluralAttributeMapping) {
if ( pluralAttributeMapping.getOrderByFragment() != null ) {
applyOrdering( tableGroup, pluralAttributeMapping.getOrderByFragment() );
@ -366,36 +382,33 @@ public class LoaderSelectBuilder {
}
}
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<Fetch> visitFetches(FetchParent fetchParent, QuerySpec querySpec, LoaderSqlAstCreationState creationState) {
log.tracef( "Starting visitation of FetchParent's Fetchables : %s", fetchParent.getNavigablePath() );
final List<Fetch> fetches = new ArrayList<>();
final Consumer<Fetchable> processor = createFetchableConsumer( fetchParent, querySpec, creationState, fetches );
final BiConsumer<Fetchable, Boolean> 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<Fetchable> createFetchableConsumer(
private BiConsumer<Fetchable, Boolean> createFetchableBiConsumer(
FetchParent fetchParent,
QuerySpec querySpec,
LoaderSqlAstCreationState creationState, List<Fetch> fetches) {
return fetchable -> {
LoaderSqlAstCreationState creationState,
List<Fetch> fetches) {
return (fetchable, isKeyFetchable) -> {
final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
@ -410,10 +423,37 @@ public class LoaderSelectBuilder {
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 @@ public class LoaderSelectBuilder {
if ( !( fetchable instanceof BasicValuedModelPart ) ) {
fetchDepth++;
}
Fetch fetch = fetchable.generateFetch(
final Fetch fetch = fetchable.generateFetch(
fetchParent,
fetchablePath,
fetchTiming,
@ -453,6 +493,9 @@ public class LoaderSelectBuilder {
if ( !( fetchable instanceof BasicValuedModelPart ) ) {
fetchDepth--;
}
if ( entityGraphNavigator != null && navigateResult != null ) {
entityGraphNavigator.backtrack( navigateResult.getPreviousContext() );
}
}
};
}
@ -512,7 +555,7 @@ public class LoaderSelectBuilder {
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 @@ public class LoaderSelectBuilder {
else {
final List<ColumnReference> 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 @@ public class LoaderSelectBuilder {
this.creationContext.getSessionFactory()
)
)
);
}
)
);
fkExpression = new SqlTuple( columnReferences, fkDescriptor );

View File

@ -11,7 +11,6 @@ import java.util.List;
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.NotYetImplementedFor6Exception;
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.QuerySpec;
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<DomainResult> domainResults = CollectionHelper.arrayList( 10 );
private GraphImplementor<?> currentJpaGraphNode;
private final EntityGraphNavigator entityGraphNavigator;
public StandardSqmSelectTranslator(
QueryOptions queryOptions,
@ -97,10 +96,13 @@ public class 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 class StandardSqmSelectTranslator
public List<Fetch> visitFetches(FetchParent fetchParent) {
final List<Fetch> fetches = CollectionHelper.arrayList( fetchParent.getReferencedMappingType().getNumberOfFetchables() );
//noinspection Convert2Lambda
final Consumer<Fetchable> fetchableConsumer = new Consumer<Fetchable>() {
@Override
public void accept(Fetchable fetchable) {
final BiConsumer<Fetchable, Boolean> fetchableBiConsumer = (fetchable, isKeyFetchable) -> {
final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
final Fetch biDirectionalFetch = fetchable.resolveCircularFetch(
@ -253,7 +252,7 @@ public class StandardSqmSelectTranslator
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 class StandardSqmSelectTranslator
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 @@ public class StandardSqmSelectTranslator
// 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,24 +418,12 @@ public class StandardSqmSelectTranslator
);
}
finally {
currentJpaGraphNode = previousGraphNode;
if ( entityGraphNavigator != null && navigateResult != null ) {
entityGraphNavigator.backtrack( navigateResult.getPreviousContext() );
}
}
}
private static boolean appliesTo(GraphImplementor<?> graphNode, FetchParent fetchParent) {
if ( ! ( fetchParent instanceof EntityResultGraphNode ) ) {
return false;
}
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
public FromClauseAccess getFromClauseAccess() {
return getFromClauseIndex();

View File

@ -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
* <ul>
* <li>previous entity graph node so later on navigator can backtrack to it</li>
* <li>whether the new graph node should be eagerly loaded or not</li>
* <li>whether the new graph node fetching is joined</li>
* </ul>
*/
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);
}

View File

@ -73,4 +73,11 @@ public class EntityFetchJoinedImpl extends AbstractNonLazyEntityFetch {
return true;
}
public EntityResultImpl getEntityResult() {
return entityResult;
}
public LockMode getLockMode() {
return lockMode;
}
}

View File

@ -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<Class<?>, 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;
}
}

View File

@ -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<Cat> 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<Cat> 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<Cat> 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<TableGroupJoin> tableGroupJoins = tableGroup.getTableGroupJoins();
Map<String, Class<? extends TableGroup>> tableGroupByName = tableGroupJoins.stream()
.map( TableGroupJoin::getJoinedGroup )
.collect( Collectors.toMap(
tg -> tg.getModelPart().getPartName(),
TableGroup::getClass
) );
Map<String, Class<? extends TableGroup> > 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<String, Class<? extends Fetch>> fetchClassByAttributeName = ownerEntityResult.getFetches()
.stream().collect( Collectors.toMap(
fetch -> fetch.getFetchedMapping().getPartName(),
Fetch::getClass
) );
final Map<String, Class<? extends Fetch>> 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<Cat> 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<Cat> 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<String, Class<? extends Fetch>> fetchClassByAttributeName = entityResult.getFetches().stream().collect( Collectors.toMap(
fetch -> fetch.getFetchedMapping().getPartName(),
Fetch::getClass
) );
final Map<String, Class<? extends Fetch>> 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<Dog> 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<Dog> 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<ExpressCompany> 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<TableGroup> 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<EntityFetch> 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<ExpressCompany> 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 <T> SelectStatement buildSqlSelectAst(
Class<T> entityType,
RootGraphImplementor<T> 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<String> 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<Cat> 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<Address> shipAddresses;
}
@Entity
public static class Country {
@Id
String name;
}
}

View File

@ -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<Join> 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<String> 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<Cat> pets;
@Embedded
Address homeAddress;
}
@Embeddable
public static class Address {
@ManyToOne
Country country;
}
@Entity
public static class ExpressCompany {
@Id
String name;
@ElementCollection
Set<Address> shipAddresses;
}
@Entity
public static class Country {
@Id
String name;
}
}