HHH-13756 implement entity graph in v6
This commit is contained in:
parent
62e9a674ad
commit
335c1ecd75
|
@ -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 );
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -73,4 +73,11 @@ public class EntityFetchJoinedImpl extends AbstractNonLazyEntityFetch {
|
|||
return true;
|
||||
}
|
||||
|
||||
public EntityResultImpl getEntityResult() {
|
||||
return entityResult;
|
||||
}
|
||||
|
||||
public LockMode getLockMode() {
|
||||
return lockMode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue