diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java index 045481c3abc8..bae24ed4e5af 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java @@ -13,6 +13,7 @@ import jakarta.persistence.EmbeddedId; import jakarta.persistence.Id; import jakarta.persistence.IdClass; +import org.hibernate.sql.results.graph.Fetchable; /** @@ -22,7 +23,7 @@ * @see EmbeddedId * @see Nature */ -public interface EntityIdentifierMapping extends ValuedModelPart { +public interface EntityIdentifierMapping extends ValuedModelPart, Fetchable { String ID_ROLE_NAME = "{id}"; String LEGACY_ID_NAME = "id"; diff --git a/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java b/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java index 45037ea79889..387fce67678b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java @@ -21,6 +21,7 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.BasicTypeReference; @@ -504,6 +505,8 @@ interface FetchReturn extends ResultNode { String getOwnerAlias(); + Fetchable getFetchable(); + String getFetchableName(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/FetchMementoHbmStandard.java b/hibernate-core/src/main/java/org/hibernate/query/internal/FetchMementoHbmStandard.java index c0ee765a9df1..8409bca37367 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/FetchMementoHbmStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/FetchMementoHbmStandard.java @@ -30,6 +30,7 @@ public class FetchMementoHbmStandard implements FetchMemento, FetchMemento.Parent { private static final String ELEMENT_PREFIX = "element."; + private static final int ELEMENT_PREFIX_LENGTH = 8; public interface FetchParentMemento { NavigablePath getNavigablePath(); @@ -74,55 +75,110 @@ public FetchBuilder resolve( Parent parent, Consumer querySpaceConsumer, ResultSetMappingResolutionContext context) { - final Map fetchBuilderMap = new HashMap<>(); - fetchMementoMap.forEach( - (attrName, fetchMemento) -> fetchBuilderMap.put( - attrName, - fetchMemento.resolve(this, querySpaceConsumer, context ) - ) - ); - final DynamicResultBuilderEntityStandard resultBuilder; - if ( fetchable instanceof PluralAttributeMapping ) { - resultBuilder = new DynamicResultBuilderEntityStandard( - (EntityMappingType) ( (PluralAttributeMapping) fetchable ).getElementDescriptor().getPartMappingType(), - tableAlias, - navigablePath - ); - FetchBuilder element = fetchBuilderMap.get( "element" ); - if ( element != null ) { - if ( element instanceof DynamicFetchBuilder ) { - resultBuilder.addIdColumnAliases( - ( (DynamicFetchBuilder) element ).getColumnAliases().toArray( new String[0] ) - ); - } - else { - resultBuilder.addIdColumnAliases( - ( (CompleteFetchBuilderEntityValuedModelPart) element ).getColumnAliases().toArray( new String[0] ) - ); - } - } - FetchBuilder index = fetchBuilderMap.get( "index" ); - if ( index != null ) { - resultBuilder.addFetchBuilder( CollectionPart.Nature.INDEX.getName(), index ); - } - for ( Map.Entry entry : fetchBuilderMap.entrySet() ) { - if ( entry.getKey().startsWith( ELEMENT_PREFIX ) ) { - resultBuilder.addFetchBuilder( entry.getKey().substring( ELEMENT_PREFIX.length() ), entry.getValue() ); - } - } + if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { + return resolve( pluralAttributeMapping, querySpaceConsumer, context ); } else { - resultBuilder = new DynamicResultBuilderEntityStandard( - ( (ToOneAttributeMapping) fetchable ).getEntityMappingType(), - tableAlias, - navigablePath - ); - fetchBuilderMap.forEach( resultBuilder::addFetchBuilder ); + return resolve( (ToOneAttributeMapping) fetchable, querySpaceConsumer, context ); } + } + + private FetchBuilder resolve( + PluralAttributeMapping pluralAttributeMapping, + Consumer querySpaceConsumer, + ResultSetMappingResolutionContext context) { + final DynamicResultBuilderEntityStandard resultBuilder; + EntityMappingType partMappingType = (EntityMappingType) pluralAttributeMapping.getElementDescriptor() + .getPartMappingType(); + resultBuilder = new DynamicResultBuilderEntityStandard( + partMappingType, + tableAlias, + navigablePath + ); + final Map fetchBuilderMap = new HashMap<>(); + fetchMementoMap.forEach( + (attrName, fetchMemento) -> { + final FetchBuilder fetchBuilder = fetchMemento.resolve( this, querySpaceConsumer, context ); + + if ( attrName.equals( "element" ) ) { + if ( fetchBuilder instanceof DynamicFetchBuilder dynamicFetchBuilder ) { + resultBuilder.addIdColumnAliases( + dynamicFetchBuilder.getColumnAliases().toArray( new String[0] ) + ); + } + else { + resultBuilder.addIdColumnAliases( + ((CompleteFetchBuilderEntityValuedModelPart) fetchBuilder).getColumnAliases() + .toArray( new String[0] ) + ); + } + fetchBuilderMap.put( + pluralAttributeMapping.getElementDescriptor(), + fetchBuilder + ); + } + else if ( attrName.equals( "index" ) ) { + final CollectionPart indexDescriptor = pluralAttributeMapping.getIndexDescriptor(); + resultBuilder.addFetchBuilder( indexDescriptor, fetchBuilder ); + fetchBuilderMap.put( + indexDescriptor, + fetchBuilder + ); + } + else if ( attrName.startsWith( ELEMENT_PREFIX ) ) { + final Fetchable attributeMapping = (Fetchable) partMappingType.findByPath( + attrName.substring( ELEMENT_PREFIX_LENGTH ) ); + resultBuilder.addFetchBuilder( attributeMapping, fetchBuilder ); + fetchBuilderMap.put( + attributeMapping, + fetchBuilder + ); + } + else { + final Fetchable attributeMapping = (Fetchable) partMappingType.findByPath( attrName ); + resultBuilder.addFetchBuilder( attributeMapping, fetchBuilder ); + fetchBuilderMap.put( + attributeMapping, + fetchBuilder + ); + } + } + ); + return new DynamicFetchBuilderLegacy( + tableAlias, + ownerTableAlias, + fetchable, + keyColumnNames, + fetchBuilderMap, + resultBuilder + ); + } + + private FetchBuilder resolve( + ToOneAttributeMapping toOneAttributeMapping, + Consumer querySpaceConsumer, + ResultSetMappingResolutionContext context) { + final Map fetchBuilderMap = new HashMap<>(); + fetchMementoMap.forEach( + (attrName, fetchMemento) -> + fetchBuilderMap.put( + (Fetchable) toOneAttributeMapping.findSubPart( attrName ), + fetchMemento.resolve( this, querySpaceConsumer, context ) + ) + ); + final DynamicResultBuilderEntityStandard resultBuilder; + resultBuilder = new DynamicResultBuilderEntityStandard( + toOneAttributeMapping.getEntityMappingType(), + tableAlias, + navigablePath + ); + fetchBuilderMap.forEach( (fetchable, fetchBuilder) -> + resultBuilder.addFetchBuilder( fetchable, fetchBuilder ) + ); return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, - fetchable.getFetchableName(), + fetchable, keyColumnNames, fetchBuilderMap, resultBuilder diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java index fd3e77ba359e..22ca3be001d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java @@ -23,6 +23,7 @@ import org.hibernate.query.results.internal.complete.DelayedFetchBuilderBasicPart; import org.hibernate.query.results.internal.implicit.ImplicitFetchBuilderBasic; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.Fetchable; /** * @author Steve Ebersole @@ -70,13 +71,13 @@ public ResultBuilderEntityValued resolve( } } - final HashMap explicitFetchBuilderMap = new HashMap<>(); + final HashMap explicitFetchBuilderMap = new HashMap<>(); // If there are no explicit fetches, we don't register DELAYED builders to get implicit fetching of all basic fetchables if ( !explicitFetchMementoMap.isEmpty() ) { explicitFetchMementoMap.forEach( (relativePath, fetchMemento) -> explicitFetchBuilderMap.put( - relativePath, + (Fetchable) entityDescriptor.findByPath( relativePath ), fetchMemento.resolve( this, querySpaceConsumer, context ) ) ); @@ -87,13 +88,13 @@ public ResultBuilderEntityValued resolve( attributeMapping -> { final BasicValuedModelPart basicPart = attributeMapping.asBasicValuedModelPart(); if ( basicPart != null ) { - final Function fetchBuilderCreator = k -> new DelayedFetchBuilderBasicPart( - navigablePath.append( k ), + final Function fetchBuilderCreator = k -> new DelayedFetchBuilderBasicPart( + navigablePath.append( k.getFetchableName() ), basicPart, isEnhancedForLazyLoading ); explicitFetchBuilderMap.computeIfAbsent( - attributeMapping.getFetchableName(), + attributeMapping, fetchBuilderCreator ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityStandard.java index 6be6cc3463c2..86ac9f19de89 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityStandard.java @@ -10,15 +10,16 @@ import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.query.results.FetchBuilderBasicValued; import org.hibernate.spi.NavigablePath; import org.hibernate.query.QueryLogging; import org.hibernate.query.named.FetchMemento; import org.hibernate.query.named.FetchMementoBasic; import org.hibernate.query.named.ResultMementoEntity; -import org.hibernate.query.results.FetchBuilderBasicValued; import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.ResultBuilderEntityValued; import org.hibernate.query.results.internal.complete.CompleteResultBuilderEntityStandard; +import org.hibernate.sql.results.graph.Fetchable; /** * @author Steve Ebersole @@ -64,11 +65,11 @@ public ResultBuilderEntityValued resolve( ? (FetchBuilderBasicValued) discriminatorMemento.resolve( this, querySpaceConsumer, context ) : null; - final HashMap fetchBuilderMap = new HashMap<>(); + final HashMap fetchBuilderMap = new HashMap<>(); fetchMementoMap.forEach( (attrName, fetchMemento) -> fetchBuilderMap.put( - attrName, + (Fetchable) entityDescriptor.findByPath( attrName ), fetchMemento.resolve(this, querySpaceConsumer, context ) ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java index 092cfbd74b36..502be58d1c59 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java @@ -9,6 +9,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; 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.jdbc.spi.JdbcValuesMetadata; import java.util.function.BiConsumer; @@ -33,7 +34,7 @@ Fetch buildFetch( JdbcValuesMetadata jdbcResultsMetadata, DomainResultCreationState domainResultCreationState); - default void visitFetchBuilders(BiConsumer consumer) { + default void visitFetchBuilders(BiConsumer consumer) { } FetchBuilder cacheKeyInstance(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/LegacyFetchBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/LegacyFetchBuilder.java index c1f818b5bf99..fa52fe9393ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/LegacyFetchBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/LegacyFetchBuilder.java @@ -4,6 +4,8 @@ */ package org.hibernate.query.results; +import org.hibernate.sql.results.graph.Fetchable; + /** * Specialized FetchBuilder implementations which handle building fetches defined via:
    *
  • {@code hbm.xml} definitions
  • @@ -30,4 +32,6 @@ public interface LegacyFetchBuilder extends FetchBuilder { @Override LegacyFetchBuilder cacheKeyInstance(); + + Fetchable getFetchable(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java index b7f98b60bc73..3b4c572fe0d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java @@ -7,6 +7,7 @@ import org.hibernate.Incubating; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import java.util.function.BiConsumer; @@ -46,6 +47,6 @@ DomainResult buildResult( ResultBuilder cacheKeyInstance(); - default void visitFetchBuilders(BiConsumer consumer) { + default void visitFetchBuilders(BiConsumer consumer) { } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/Builders.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/Builders.java index 3a5cf54e1e0a..66b9b3e12629 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/Builders.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/Builders.java @@ -240,8 +240,8 @@ public static DynamicResultBuilderEntityCalculated entityCalculated( return new DynamicResultBuilderEntityCalculated( entityMapping, tableAlias, explicitLockMode ); } - public static DynamicFetchBuilderLegacy fetch(String tableAlias, String ownerTableAlias, String joinPropertyName) { - return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, joinPropertyName, new ArrayList<>(), new HashMap<>() ); + public static DynamicFetchBuilderLegacy fetch(String tableAlias, String ownerTableAlias, Fetchable fetchable) { + return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, fetchable, new ArrayList<>(), new HashMap<>() ); } public static ResultBuilder resultClassBuilder( diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/DomainResultCreationStateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/DomainResultCreationStateImpl.java index 5351c60dfac7..c7ee3c7740cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/DomainResultCreationStateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/DomainResultCreationStateImpl.java @@ -12,14 +12,10 @@ import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.mapping.Association; -import org.hibernate.metamodel.mapping.AttributeMapping; -import org.hibernate.metamodel.mapping.CompositeIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; -import org.hibernate.metamodel.mapping.internal.BasicValuedCollectionPart; import org.hibernate.metamodel.mapping.internal.CaseStatementDiscriminatorMappingImpl; import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.LegacyFetchBuilder; @@ -47,7 +43,6 @@ import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.spi.TypeConfiguration; -import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; @@ -83,7 +78,6 @@ public class DomainResultCreationStateImpl private final SessionFactoryImplementor sessionFactory; private final Stack fetchBuilderResolverStack = new StandardStack<>( Function.class, fetchableName -> null ); - private final Stack relativePathStack = new StandardStack<>( Map.Entry.class ); private Map registeredLockModes; private boolean processingKeyFetches = false; private boolean resolvingCircularFetch; @@ -92,7 +86,7 @@ public class DomainResultCreationStateImpl public DomainResultCreationStateImpl( String stateIdentifier, JdbcValuesMetadata jdbcResultsMetadata, - Map> legacyFetchBuilders, + Map> legacyFetchBuilders, Consumer sqlSelectionConsumer, LoadQueryInfluencers loadQueryInfluencers, SessionFactoryImplementor sessionFactory) { @@ -129,33 +123,26 @@ public JdbcValuesMetadata getJdbcResultsMetadata() { return jdbcResultsMetadata; } - public Map.Entry getCurrentRelativePath() { - //noinspection unchecked - return relativePathStack.getCurrent(); - } - - public void pushExplicitFetchMementoResolver(Function resolver) { + public void pushExplicitFetchMementoResolver(Function resolver) { fetchBuilderResolverStack.push( resolver ); } - public Function getCurrentExplicitFetchMementoResolver() { - //noinspection unchecked + public Function getCurrentExplicitFetchMementoResolver() { return fetchBuilderResolverStack.getCurrent(); } - public Function popExplicitFetchMementoResolver() { - //noinspection unchecked + public Function popExplicitFetchMementoResolver() { return fetchBuilderResolverStack.pop(); } @SuppressWarnings( "unused" ) - public void withExplicitFetchMementoResolver(Function resolver, Runnable runnable) { + public void withExplicitFetchMementoResolver(Function resolver, Runnable runnable) { pushExplicitFetchMementoResolver( resolver ); try { runnable.run(); } finally { - final Function popped = popExplicitFetchMementoResolver(); + final Function popped = popExplicitFetchMementoResolver(); assert popped == resolver; } } @@ -270,7 +257,7 @@ public Expression resolveSqlExpression( sqlSelectionMap.put( key, (ResultSetMappingSqlSelection) created ); sqlSelectionConsumer.accept( (ResultSetMappingSqlSelection) created ); } - else if ( created instanceof ColumnReference columnReference ) { + else if ( created instanceof ColumnReference columnReference) { final String selectableName = columnReference.getSelectableName(); final int valuesArrayPosition; if ( nestingFetchParent != null ) { @@ -328,23 +315,23 @@ public SqlSelection resolveSqlSelection( } private static class LegacyFetchResolver { - private final Map> legacyFetchBuilders; + private final Map> legacyFetchBuilders; - public LegacyFetchResolver(Map> legacyFetchBuilders) { + public LegacyFetchResolver(Map> legacyFetchBuilders) { this.legacyFetchBuilders = legacyFetchBuilders; } - public LegacyFetchBuilder resolve(String ownerTableAlias, String fetchedPartPath) { + public LegacyFetchBuilder resolve(String ownerTableAlias, Fetchable fetchedPart) { if ( legacyFetchBuilders == null ) { return null; } - final Map fetchBuilders = legacyFetchBuilders.get( ownerTableAlias ); + final Map fetchBuilders = legacyFetchBuilders.get( ownerTableAlias ); if ( fetchBuilders == null ) { return null; } - return fetchBuilders.get( fetchedPartPath ); + return fetchBuilders.get( fetchedPart ); } } @@ -362,28 +349,18 @@ public Fetch visitIdentifierFetch(EntityResultGraphNode fetchParent) { final EntityValuedModelPart parentModelPart = fetchParent.getEntityValuedModelPart(); final EntityIdentifierMapping identifierMapping = parentModelPart.getEntityMappingType().getIdentifierMapping(); final String identifierAttributeName = attributeName( identifierMapping ); - //noinspection unchecked - final Map.Entry oldEntry = relativePathStack.getCurrent(); - final String fullPath; - if ( identifierMapping instanceof NonAggregatedIdentifierMapping ) { - fullPath = oldEntry == null ? "" : oldEntry.getKey(); - } - else { - fullPath = oldEntry == null ? - identifierAttributeName : - oldEntry.getKey() + "." + identifierAttributeName; - } - final Fetchable identifierFetchable = (Fetchable) identifierMapping; //noinspection unchecked - final FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack.getCurrent().apply( fullPath ); + final FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack + .getCurrent() + .apply( identifierMapping ); LegacyFetchBuilder fetchBuilderLegacy; if ( explicitFetchBuilder == null ) { fetchBuilderLegacy = legacyFetchResolver.resolve( fromClauseAccess.findTableGroup( fetchParent.getNavigablePath() ) .getPrimaryTableReference() .getIdentificationVariable(), - identifierAttributeName + identifierMapping ); } else { @@ -397,9 +374,6 @@ public Fetch visitIdentifierFetch(EntityResultGraphNode fetchParent) { final boolean processingKeyFetches = this.processingKeyFetches; this.processingKeyFetches = true; - if ( identifierMapping instanceof CompositeIdentifierMapping ) { - relativePathStack.push( new AbstractMap.SimpleEntry<>( fullPath, fetchPath ) ); - } try { final FetchBuilder fetchBuilder; @@ -410,7 +384,7 @@ else if ( fetchBuilderLegacy != null ) { fetchBuilder = fetchBuilderLegacy; } else { - fetchBuilder = Builders.implicitFetchBuilder( fetchPath, identifierFetchable, this ); + fetchBuilder = Builders.implicitFetchBuilder( fetchPath, identifierMapping, this ); } return fetchBuilder.buildFetch( @@ -422,9 +396,6 @@ else if ( fetchBuilderLegacy != null ) { } finally { this.processingKeyFetches = processingKeyFetches; - if ( identifierMapping instanceof CompositeIdentifierMapping ) { - this.relativePathStack.pop(); - } } } @@ -443,34 +414,16 @@ private Consumer createFetchableConsumer(FetchParent fetchParent, Imm if ( !fetchable.isSelectable() ) { return; } - final String fetchableName = fetchable.getFetchableName(); - Map.Entry currentEntry; - if ( relativePathStack.isEmpty() ) { - currentEntry = new AbstractMap.SimpleEntry<>( - getRelativePath( "", fetchable ), - new NavigablePath( fetchableName ) - ); - } - else { - //noinspection unchecked - final Map.Entry oldEntry = relativePathStack.getCurrent(); - final String key = oldEntry.getKey(); - currentEntry = new AbstractMap.SimpleEntry<>( - getRelativePath( key, fetchable ), - oldEntry.getValue().append( fetchableName ) - ); - } - // todo (6.0): figure out if we can somehow create the navigable paths in a better way - final String fullPath = currentEntry.getKey(); - //noinspection unchecked - FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack.getCurrent().apply( fullPath ); + FetchBuilder explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack + .getCurrent() + .apply( fetchable ); LegacyFetchBuilder fetchBuilderLegacy; if ( explicitFetchBuilder == null ) { fetchBuilderLegacy = legacyFetchResolver.resolve( fromClauseAccess.findTableGroup( fetchParent.getNavigablePath() ) .getPrimaryTableReference() .getIdentificationVariable(), - fetchableName + fetchable ); } else { @@ -478,67 +431,44 @@ private Consumer createFetchableConsumer(FetchParent fetchParent, Imm } if ( fetchable instanceof Association association && fetchable.getMappedFetchOptions().getTiming() == FetchTiming.DELAYED ) { final ForeignKeyDescriptor foreignKeyDescriptor = association.getForeignKeyDescriptor(); - - final String partName = attributeName( - foreignKeyDescriptor.getSide( association.getSideNature().inverse() ).getModelPart() - ); - // If there are no fetch builders for this association, we only want to fetch the FK - if ( explicitFetchBuilder == null && fetchBuilderLegacy == null && partName != null ) { - currentEntry = new AbstractMap.SimpleEntry<>( - currentEntry.getKey() + "." + partName, - currentEntry.getValue().append( partName ) - ); - //noinspection unchecked - explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack.getCurrent().apply( currentEntry.getKey() ); + if ( explicitFetchBuilder == null && fetchBuilderLegacy == null ) { + explicitFetchBuilder = (FetchBuilder) fetchBuilderResolverStack + .getCurrent() + .apply( foreignKeyDescriptor.getSide( association.getSideNature().inverse() ).getModelPart() ); if ( explicitFetchBuilder == null ) { fetchBuilderLegacy = legacyFetchResolver.resolve( fromClauseAccess.findTableGroup( fetchParent.getNavigablePath() ) .getPrimaryTableReference() .getIdentificationVariable(), - fetchableName + fetchable ); } } } - relativePathStack.push( currentEntry ); - try { - final NavigablePath fetchPath = fetchParent.resolveNavigablePath( fetchable ); - final FetchBuilder fetchBuilder; - if ( explicitFetchBuilder != null ) { - fetchBuilder = explicitFetchBuilder; - } - else if ( fetchBuilderLegacy == null ) { + final NavigablePath fetchPath = fetchParent.resolveNavigablePath( fetchable ); + final FetchBuilder fetchBuilder; + if ( explicitFetchBuilder != null ) { + fetchBuilder = explicitFetchBuilder; + } + else { + if ( fetchBuilderLegacy == null ) { fetchBuilder = Builders.implicitFetchBuilder( fetchPath, fetchable, this ); } else { fetchBuilder = fetchBuilderLegacy; } - final Fetch fetch = fetchBuilder.buildFetch( - fetchParent, - fetchPath, - jdbcResultsMetadata, - this - ); - fetches.add( fetch ); } - finally { - relativePathStack.pop(); - } - + final Fetch fetch = fetchBuilder.buildFetch( + fetchParent, + fetchPath, + jdbcResultsMetadata, + this + ); + fetches.add( fetch ); }; } - private String getRelativePath(String oldEntry, Fetchable fetchable) { - if ( fetchable instanceof AttributeMapping || fetchable instanceof BasicValuedCollectionPart ) { - if ( !"".equals( oldEntry ) ) { - return oldEntry + '.' + fetchable.getFetchableName(); - } - return fetchable.getFetchableName(); - } - return oldEntry; - } - @Override public boolean isResolvingCircularFetch() { return resolvingCircularFetch; diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultSetMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultSetMappingImpl.java index 9d99cad9643e..2ba16bc412d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultSetMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultSetMappingImpl.java @@ -16,6 +16,7 @@ import org.hibernate.query.results.ResultSetMapping; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; @@ -44,7 +45,7 @@ public class ResultSetMappingImpl implements ResultSetMapping { private final String mappingIdentifier; private final boolean isDynamic; private List resultBuilders; - private Map> legacyFetchBuilders; + private Map> legacyFetchBuilders; public ResultSetMappingImpl(String mappingIdentifier) { this( mappingIdentifier, false ); @@ -72,15 +73,15 @@ private ResultSetMappingImpl(ResultSetMappingImpl original) { this.legacyFetchBuilders = null; } else { - final Map> legacyFetchBuilders = new HashMap<>( original.legacyFetchBuilders.size() ); - for ( Map.Entry> entry : original.legacyFetchBuilders.entrySet() ) { - final Map newValue = new HashMap<>( entry.getValue().size() ); - for ( Map.Entry builderEntry : entry.getValue().entrySet() ) { + final Map> builders = new HashMap<>( original.legacyFetchBuilders.size() ); + for ( Map.Entry> entry : original.legacyFetchBuilders.entrySet() ) { + final Map newValue = new HashMap<>( entry.getValue().size() ); + for ( Map.Entry builderEntry : entry.getValue().entrySet() ) { newValue.put( builderEntry.getKey(), builderEntry.getValue().cacheKeyInstance() ); } - legacyFetchBuilders.put( entry.getKey(), newValue ); + builders.put( entry.getKey(), newValue ); } - this.legacyFetchBuilders = legacyFetchBuilders; + this.legacyFetchBuilders = builders; } } @@ -123,7 +124,7 @@ public void visitLegacyFetchBuilders(Consumer resultBuilderC return; } - for ( Map.Entry> entry : legacyFetchBuilders.entrySet() ) { + for ( Map.Entry> entry : legacyFetchBuilders.entrySet() ) { for ( LegacyFetchBuilder fetchBuilder : entry.getValue().values() ) { resultBuilderConsumer.accept( fetchBuilder ); } @@ -140,7 +141,7 @@ public void addResultBuilder(ResultBuilder resultBuilder) { @Override public void addLegacyFetchBuilder(LegacyFetchBuilder fetchBuilder) { - final Map existingFetchBuildersByOwner; + final Map existingFetchBuildersByOwner; if ( legacyFetchBuilders == null ) { legacyFetchBuilders = new HashMap<>(); @@ -150,7 +151,7 @@ public void addLegacyFetchBuilder(LegacyFetchBuilder fetchBuilder) { existingFetchBuildersByOwner = legacyFetchBuilders.get( fetchBuilder.getOwnerAlias() ); } - final Map fetchBuildersByOwner; + final Map fetchBuildersByOwner; if ( existingFetchBuildersByOwner == null ) { fetchBuildersByOwner = new HashMap<>(); legacyFetchBuilders.put( fetchBuilder.getOwnerAlias(), fetchBuildersByOwner ); @@ -159,7 +160,7 @@ public void addLegacyFetchBuilder(LegacyFetchBuilder fetchBuilder) { fetchBuildersByOwner = existingFetchBuildersByOwner; } - fetchBuildersByOwner.put( fetchBuilder.getFetchableName(), fetchBuilder ); + fetchBuildersByOwner.put( fetchBuilder.getFetchable(), fetchBuilder ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultsHelper.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultsHelper.java index 7f1b46a26dfd..562dcf553070 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultsHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/ResultsHelper.java @@ -4,6 +4,7 @@ */ package org.hibernate.query.results.internal; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; @@ -13,6 +14,7 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey; +import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createDiscriminatorColumnReferenceKey; /** * @author Steve Ebersole @@ -60,6 +62,25 @@ public static Expression resolveSqlExpression( ); } + public static Expression resolveSqlExpression( + DomainResultCreationStateImpl resolver, + JdbcValuesMetadata jdbcValuesMetadata, + TableReference tableReference, + EntityDiscriminatorMapping discriminatorMapping, + String columnAlias) { + return resolver.resolveSqlExpression( + createDiscriminatorColumnReferenceKey( + tableReference, + discriminatorMapping + ), + processingState -> { + final int jdbcPosition = jdbcValuesMetadata.resolveColumnPosition( columnAlias ); + final int valuesArrayPosition = jdbcPositionToValuesArrayPosition( jdbcPosition ); + return new ResultSetMappingSqlSelection( valuesArrayPosition, discriminatorMapping.getJdbcMapping() ); + } + ); + } + public static Expression resolveSqlExpression( DomainResultCreationStateImpl resolver, TableReference tableReference, diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityJpa.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityJpa.java index 8cbddb2c8371..9abf4f185d18 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityJpa.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityJpa.java @@ -15,6 +15,7 @@ import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlAliasBase; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; @@ -37,14 +38,14 @@ public class CompleteResultBuilderEntityJpa implements CompleteResultBuilderEnti private final EntityMappingType entityDescriptor; private final LockMode lockMode; private final FetchBuilderBasicValued discriminatorFetchBuilder; - private final HashMap explicitFetchBuilderMap; + private final HashMap explicitFetchBuilderMap; public CompleteResultBuilderEntityJpa( NavigablePath navigablePath, EntityMappingType entityDescriptor, LockMode lockMode, FetchBuilderBasicValued discriminatorFetchBuilder, - HashMap explicitFetchBuilderMap) { + HashMap explicitFetchBuilderMap) { this.navigablePath = navigablePath; this.entityDescriptor = entityDescriptor; this.lockMode = lockMode; @@ -132,7 +133,7 @@ public EntityResult buildResult( } @Override - public void visitFetchBuilders(BiConsumer consumer) { + public void visitFetchBuilders(BiConsumer consumer) { explicitFetchBuilderMap.forEach( consumer ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityStandard.java index ea16de58818c..31e11c89917b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/complete/CompleteResultBuilderEntityStandard.java @@ -16,6 +16,7 @@ import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlAliasBaseConstant; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; @@ -32,7 +33,7 @@ public class CompleteResultBuilderEntityStandard implements CompleteResultBuilde private final EntityMappingType entityDescriptor; private final LockMode lockMode; private final FetchBuilderBasicValued discriminatorFetchBuilder; - private final HashMap explicitFetchBuilderMap; + private final HashMap explicitFetchBuilderMap; public CompleteResultBuilderEntityStandard( String tableAlias, @@ -40,7 +41,7 @@ public CompleteResultBuilderEntityStandard( EntityMappingType entityDescriptor, LockMode lockMode, FetchBuilderBasicValued discriminatorFetchBuilder, - HashMap explicitFetchBuilderMap) { + HashMap explicitFetchBuilderMap) { this.tableAlias = tableAlias; this.navigablePath = navigablePath; this.entityDescriptor = entityDescriptor; @@ -161,7 +162,7 @@ public EntityResult buildResult( } @Override - public void visitFetchBuilders(BiConsumer consumer) { + public void visitFetchBuilders(BiConsumer consumer) { explicitFetchBuilderMap.forEach( consumer ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/AbstractFetchBuilderContainer.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/AbstractFetchBuilderContainer.java index a1bebfb4f297..d568fe3fbcaf 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/AbstractFetchBuilderContainer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/AbstractFetchBuilderContainer.java @@ -11,13 +11,14 @@ import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.results.FetchBuilder; +import org.hibernate.sql.results.graph.Fetchable; /** * @author Steve Ebersole */ public abstract class AbstractFetchBuilderContainer> implements DynamicFetchBuilderContainer { - private Map fetchBuilderMap; + private Map fetchBuilderMap; protected AbstractFetchBuilderContainer() { } @@ -25,7 +26,7 @@ protected AbstractFetchBuilderContainer() { protected AbstractFetchBuilderContainer(AbstractFetchBuilderContainer original) { if ( original.fetchBuilderMap != null ) { fetchBuilderMap = new HashMap<>( original.fetchBuilderMap.size() ); - for ( Map.Entry entry : original.fetchBuilderMap.entrySet() ) { + for ( Map.Entry entry : original.fetchBuilderMap.entrySet() ) { final FetchBuilder fetchBuilder = entry.getValue() instanceof DynamicFetchBuilderStandard dynamicFetchBuilderStandard ? dynamicFetchBuilderStandard.cacheKeyInstance( this ) @@ -38,54 +39,55 @@ protected AbstractFetchBuilderContainer(AbstractFetchBuilderContainer origina protected abstract String getPropertyBase(); @Override - public FetchBuilder findFetchBuilder(String fetchableName) { - return fetchBuilderMap == null ? null : fetchBuilderMap.get( fetchableName ); + public FetchBuilder findFetchBuilder(Fetchable fetchable) { + return fetchBuilderMap == null ? null : fetchBuilderMap.get( fetchable ); } @Override - public T addProperty(String propertyName, String columnAlias) { - final DynamicFetchBuilder fetchBuilder = addProperty( propertyName ); + public T addProperty(Fetchable fetchable, String columnAlias) { + final DynamicFetchBuilder fetchBuilder = addProperty( fetchable ); fetchBuilder.addColumnAlias( columnAlias ); return (T) this; } @Override - public T addProperty(String propertyName, String... columnAliases) { - final DynamicFetchBuilder fetchBuilder = addProperty( propertyName ); + public T addProperty(Fetchable fetchable, String... columnAliases) { + final DynamicFetchBuilder fetchBuilder = addProperty( fetchable ); ArrayHelper.forEach( columnAliases, fetchBuilder::addColumnAlias ); return (T) this; } @Override - public DynamicFetchBuilder addProperty(String propertyName) { + public DynamicFetchBuilder addProperty(Fetchable fetchable) { if ( fetchBuilderMap == null ) { fetchBuilderMap = new HashMap<>(); } else { - final FetchBuilder existing = fetchBuilderMap.get( propertyName ); + final FetchBuilder existing = fetchBuilderMap.get( fetchable ); if ( existing != null ) { throw new IllegalArgumentException( String.format( Locale.ROOT, "Fetch was already defined for %s.%s : %s", getPropertyBase(), - propertyName, + fetchable, existing ) ); } } - final DynamicFetchBuilderStandard fetchBuilder = new DynamicFetchBuilderStandard( propertyName ); - fetchBuilderMap.put( propertyName, fetchBuilder ); + final DynamicFetchBuilderStandard fetchBuilder = new DynamicFetchBuilderStandard( fetchable ); + fetchBuilderMap.put( fetchable, fetchBuilder ); return fetchBuilder; } - public void addFetchBuilder(String propertyName, FetchBuilder fetchBuilder) { + @Override + public void addFetchBuilder(Fetchable fetchable, FetchBuilder fetchBuilder) { if ( fetchBuilderMap == null ) { fetchBuilderMap = new HashMap<>(); } - fetchBuilderMap.put( propertyName, fetchBuilder ); + fetchBuilderMap.put( fetchable, fetchBuilder ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderContainer.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderContainer.java index 6dcc3dd3495d..e7cc2c6ccacc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderContainer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderContainer.java @@ -5,6 +5,7 @@ package org.hibernate.query.results.internal.dynamic; import org.hibernate.query.results.FetchBuilder; +import org.hibernate.sql.results.graph.Fetchable; /** * @author Steve Ebersole @@ -13,22 +14,22 @@ public interface DynamicFetchBuilderContainer { /** * Locate an explicit fetch definition for the named fetchable */ - FetchBuilder findFetchBuilder(String fetchableName); + FetchBuilder findFetchBuilder(Fetchable fetchable); /** * Add a property mapped to a single column. */ - DynamicFetchBuilderContainer addProperty(String propertyName, String columnAlias); + DynamicFetchBuilderContainer addProperty(Fetchable fetchable, String columnAlias); /** * Add a property mapped to multiple columns */ - DynamicFetchBuilderContainer addProperty(String propertyName, String... columnAliases); + DynamicFetchBuilderContainer addProperty(Fetchable fetchable, String... columnAliases); /** * Add a property whose columns can later be defined using {@link DynamicFetchBuilder#addColumnAlias} */ - DynamicFetchBuilder addProperty(String propertyName); + DynamicFetchBuilder addProperty(Fetchable fetchable); - void addFetchBuilder(String propertyName, FetchBuilder fetchBuilder); + void addFetchBuilder(Fetchable fetchable, FetchBuilder fetchBuilder); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderLegacy.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderLegacy.java index 28a89a9f13cb..f2d74316ce0f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderLegacy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderLegacy.java @@ -7,11 +7,14 @@ import org.hibernate.AssertionFailure; import org.hibernate.LockMode; import org.hibernate.engine.FetchTiming; -import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.query.NativeQuery; import org.hibernate.query.results.FetchBuilder; @@ -28,6 +31,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; 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.jdbc.spi.JdbcValuesMetadata; import java.util.HashMap; @@ -46,16 +50,16 @@ public class DynamicFetchBuilderLegacy implements LegacyFetchBuilder, DynamicFetchBuilder, NativeQuery.FetchReturn, NativeQuery.ReturnableResultNode, DynamicFetchBuilderContainer { - private static final String ELEMENT_PREFIX = CollectionPart.Nature.ELEMENT.getName() + "."; - private static final String INDEX_PREFIX = CollectionPart.Nature.INDEX.getName() + "."; + private static final String ELEMENT_PREFIX = "element."; + private static final int ELEMENT_PREFIX_LENGTH = 8; private final String tableAlias; private final String ownerTableAlias; - private final String fetchableName; + private final Fetchable fetchable; private final List columnNames; - private final Map fetchBuilderMap; + private final Map fetchBuilderMap; private final DynamicResultBuilderEntityStandard resultBuilderEntity; private LockMode lockMode; @@ -63,22 +67,22 @@ public class DynamicFetchBuilderLegacy public DynamicFetchBuilderLegacy( String tableAlias, String ownerTableAlias, - String fetchableName, + Fetchable fetchable, List columnNames, - Map fetchBuilderMap) { - this( tableAlias, ownerTableAlias, fetchableName, columnNames, fetchBuilderMap, null ); + Map fetchBuilderMap) { + this( tableAlias, ownerTableAlias, fetchable, columnNames, fetchBuilderMap, null ); } public DynamicFetchBuilderLegacy( String tableAlias, String ownerTableAlias, - String fetchableName, + Fetchable fetchable, List columnNames, - Map fetchBuilderMap, + Map fetchBuilderMap, DynamicResultBuilderEntityStandard resultBuilderEntity) { this.tableAlias = tableAlias; this.ownerTableAlias = ownerTableAlias; - this.fetchableName = fetchableName; + this.fetchable = fetchable; this.columnNames = columnNames; this.fetchBuilderMap = fetchBuilderMap; this.resultBuilderEntity = resultBuilderEntity; @@ -94,9 +98,55 @@ public String getOwnerAlias() { return ownerTableAlias; } + @Override + public Fetchable getFetchable() { + return fetchable; + } + @Override public String getFetchableName() { - return fetchableName; + return fetchable.getFetchableName(); + } + + @Override + public NativeQuery.FetchReturn setLockMode(LockMode lockMode) { + this.lockMode = lockMode; + return this; + } + + @Override + public NativeQuery.FetchReturn addProperty(String propertyName, String columnAlias) { + addProperty( resolveFetchable( propertyName ), columnAlias ); + return this; + } + + private Fetchable resolveFetchable(String propertyName) { + if ( fetchable instanceof EntityAssociationMapping attributeMapping ) { + return (Fetchable) attributeMapping.findByPath( propertyName ); + } + else if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { + if ( propertyName.equals( "key" ) ) { + return pluralAttributeMapping.getIndexDescriptor(); + } + else if ( propertyName.equals( "element" ) ) { + return pluralAttributeMapping.getElementDescriptor().getCollectionAttribute(); + } + else { + final CollectionPart elementDescriptor = pluralAttributeMapping.getElementDescriptor(); + if ( elementDescriptor instanceof EntityCollectionPart entityCollectionPart ) { + if ( propertyName.startsWith( ELEMENT_PREFIX ) ) { + propertyName = propertyName.substring( ELEMENT_PREFIX_LENGTH ); + } + return (Fetchable) entityCollectionPart.getEntityMappingType().findByPath( propertyName ); + } + } + } + throw new UnsupportedOperationException( "Unsupported fetchable type: " + fetchable.getClass().getName() ); + } + + @Override + public NativeQuery.ReturnProperty addProperty(String propertyName) { + return addProperty( resolveFetchable( propertyName ) ); } @Override @@ -104,20 +154,20 @@ public DynamicFetchBuilderLegacy cacheKeyInstance() { return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, - fetchableName, + fetchable, columnNames == null ? null : List.copyOf( columnNames ), fetchBuilderMap(), resultBuilderEntity == null ? null : resultBuilderEntity.cacheKeyInstance() ); } - private Map fetchBuilderMap() { + private Map fetchBuilderMap() { if ( this.fetchBuilderMap == null ) { return null; } else { - final Map fetchBuilderMap = new HashMap<>( this.fetchBuilderMap.size() ); - for ( Map.Entry entry : this.fetchBuilderMap.entrySet() ) { + final Map fetchBuilderMap = new HashMap<>( this.fetchBuilderMap.size() ); + for ( Map.Entry entry : this.fetchBuilderMap.entrySet() ) { fetchBuilderMap.put( entry.getKey(), entry.getValue().cacheKeyInstance() ); } return fetchBuilderMap; @@ -132,52 +182,69 @@ public Fetch buildFetch( DomainResultCreationState domainResultCreationState) { final DomainResultCreationStateImpl creationState = impl( domainResultCreationState ); final TableGroup ownerTableGroup = creationState.getFromClauseAccess().findByAlias( ownerTableAlias ); - final AttributeMapping attributeMapping = - parent.getReferencedMappingContainer().findContainingEntityMapping() - .findDeclaredAttributeMapping( fetchableName ); - final TableGroup tableGroup = tableGroup( fetchPath, attributeMapping, ownerTableGroup, creationState ); - + final TableGroup tableGroup = tableGroup( fetchPath, ownerTableGroup, creationState ); + if ( lockMode != null ) { + domainResultCreationState.getSqlAstCreationState().registerLockMode( tableAlias, lockMode ); + } if ( columnNames != null ) { - final ForeignKeyDescriptor keyDescriptor = getForeignKeyDescriptor( attributeMapping ); - if ( !columnNames.isEmpty() ) { - keyDescriptor.forEachSelectable( (selectionIndex, selectableMapping) -> { - resolveSqlSelection( - columnNames.get( selectionIndex ), - tableGroup.resolveTableReference( - fetchPath, - keyDescriptor.getKeyPart(), - selectableMapping.getContainingTableExpression() - ), - selectableMapping, - jdbcResultsMetadata, - domainResultCreationState - ); } + if ( fetchable instanceof EmbeddedAttributeMapping embeddedAttributeMapping ) { + embeddedAttributeMapping.forEachSelectable( + (selectionIndex, selectableMapping) -> + resolveSqlSelection( + columnNames.get( selectionIndex ), + tableGroup.resolveTableReference( + fetchPath, + (ValuedModelPart) selectableMapping, + selectableMapping.getContainingTableExpression() + ), + selectableMapping, + jdbcResultsMetadata, + domainResultCreationState + ) ); } - + else { + final ForeignKeyDescriptor keyDescriptor = getForeignKeyDescriptor( fetchable ); + if ( !columnNames.isEmpty() ) { + keyDescriptor.forEachSelectable( (selectionIndex, selectableMapping) -> { + resolveSqlSelection( + columnNames.get( selectionIndex ), + tableGroup.resolveTableReference( + fetchPath, + keyDescriptor.getKeyPart(), + selectableMapping.getContainingTableExpression() + ), + selectableMapping, + jdbcResultsMetadata, + domainResultCreationState + ); + } + ); + } + } // We process the fetch builder such that it contains a resultBuilderEntity before calling this method in ResultSetMappingProcessor if ( resultBuilderEntity != null ) { return resultBuilderEntity.buildFetch( parent, - attributeMapping, + fetchable, jdbcResultsMetadata, creationState ); } + } try { - final String prefix = DynamicResultBuilderEntityStandard.prefix( creationState, ELEMENT_PREFIX, INDEX_PREFIX ); creationState.pushExplicitFetchMementoResolver( - relativePath -> { - if ( relativePath.startsWith( prefix ) ) { - return findFetchBuilder( relativePath.substring( prefix.length() ) ); + fetchable -> { + if ( fetchable != null ) { + return findFetchBuilder( fetchable ); } return null; } ); return parent.generateFetchableFetch( - attributeMapping, - parent.resolveNavigablePath( attributeMapping ), + fetchable, + parent.resolveNavigablePath( fetchable ), FetchTiming.IMMEDIATE, true, null, @@ -191,10 +258,9 @@ public Fetch buildFetch( private TableGroup tableGroup( NavigablePath fetchPath, - AttributeMapping attributeMapping, TableGroup ownerTableGroup, DomainResultCreationStateImpl creationState) { - if ( attributeMapping instanceof TableGroupJoinProducer tableGroupJoinProducer ) { + if ( fetchable instanceof TableGroupJoinProducer tableGroupJoinProducer ) { final TableGroupJoin tableGroupJoin = tableGroupJoinProducer.createTableGroupJoin( fetchPath, ownerTableGroup, @@ -215,11 +281,11 @@ private TableGroup tableGroup( } } - private static ForeignKeyDescriptor getForeignKeyDescriptor(AttributeMapping attributeMapping) { - if ( attributeMapping instanceof PluralAttributeMapping pluralAttributeMapping ) { + private static ForeignKeyDescriptor getForeignKeyDescriptor(Fetchable fetchable) { + if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { return pluralAttributeMapping.getKeyDescriptor(); } - else if ( attributeMapping instanceof ToOneAttributeMapping toOneAttributeMapping ) { + else if ( fetchable instanceof ToOneAttributeMapping toOneAttributeMapping ) { return toOneAttributeMapping.getForeignKeyDescriptor(); } else { @@ -228,6 +294,11 @@ else if ( attributeMapping instanceof ToOneAttributeMapping toOneAttributeMappin } } + @Override + public void visitFetchBuilders(BiConsumer consumer) { + fetchBuilderMap.forEach( consumer ); + } + private void resolveSqlSelection( String columnAlias, TableReference tableReference, @@ -262,32 +333,27 @@ public List getColumnAliases() { } @Override - public NativeQuery.FetchReturn setLockMode(LockMode lockMode) { - this.lockMode = lockMode; - return this; - } - - @Override - public DynamicFetchBuilderLegacy addProperty(String propertyName, String columnAlias) { - addProperty( propertyName ).addColumnAlias( columnAlias ); - return this; + public DynamicFetchBuilder addProperty(Fetchable fetchable) { + DynamicFetchBuilderStandard fetchBuilder = new DynamicFetchBuilderStandard( fetchable ); + fetchBuilderMap.put( fetchable, fetchBuilder ); + return fetchBuilder; } @Override - public DynamicFetchBuilder addProperty(String propertyName) { - DynamicFetchBuilderStandard fetchBuilder = new DynamicFetchBuilderStandard( propertyName ); - fetchBuilderMap.put( propertyName, fetchBuilder ); - return fetchBuilder; + public FetchBuilder findFetchBuilder(Fetchable fetchable) { + return fetchBuilderMap.get( fetchable ); } @Override - public FetchBuilder findFetchBuilder(String fetchableName) { - return fetchBuilderMap.get( fetchableName ); + public DynamicFetchBuilderContainer addProperty(Fetchable fetchable, String columnAlias) { + final DynamicFetchBuilder fetchBuilder = addProperty( fetchable ); + fetchBuilder.addColumnAlias( columnAlias ); + return this; } @Override - public DynamicFetchBuilderContainer addProperty(String propertyName, String... columnAliases) { - final DynamicFetchBuilder fetchBuilder = addProperty( propertyName ); + public DynamicFetchBuilderContainer addProperty(Fetchable fetchable, String... columnAliases) { + final DynamicFetchBuilder fetchBuilder = addProperty( fetchable ); for ( String columnAlias : columnAliases ) { fetchBuilder.addColumnAlias( columnAlias ); } @@ -295,13 +361,8 @@ public DynamicFetchBuilderContainer addProperty(String propertyName, String... c } @Override - public void addFetchBuilder(String propertyName, FetchBuilder fetchBuilder) { - fetchBuilderMap.put( propertyName, fetchBuilder ); - } - - @Override - public void visitFetchBuilders(BiConsumer consumer) { - fetchBuilderMap.forEach( consumer ); + public void addFetchBuilder(Fetchable fetchable, FetchBuilder fetchBuilder) { + fetchBuilderMap.put( fetchable, fetchBuilder ); } @Override @@ -316,7 +377,8 @@ public boolean equals(Object o) { final DynamicFetchBuilderLegacy that = (DynamicFetchBuilderLegacy) o; return tableAlias.equals( that.tableAlias ) && ownerTableAlias.equals( that.ownerTableAlias ) - && fetchableName.equals( that.fetchableName ) + && fetchable.equals( that.fetchable ) + && lockMode.equals( that.lockMode ) && Objects.equals( columnNames, that.columnNames ) && Objects.equals( fetchBuilderMap, that.fetchBuilderMap ) && Objects.equals( resultBuilderEntity, that.resultBuilderEntity ); @@ -326,7 +388,8 @@ public boolean equals(Object o) { public int hashCode() { int result = tableAlias.hashCode(); result = 31 * result + ownerTableAlias.hashCode(); - result = 31 * result + fetchableName.hashCode(); + result = 31 * result + fetchable.hashCode(); + result = 31 * result + lockMode.hashCode(); result = 31 * result + ( columnNames != null ? columnNames.hashCode() : 0 ); result = 31 * result + ( fetchBuilderMap != null ? fetchBuilderMap.hashCode() : 0 ); result = 31 * result + ( resultBuilderEntity != null ? resultBuilderEntity.hashCode() : 0 ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderStandard.java index b6421a0b3fbc..9f955e8f805e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicFetchBuilderStandard.java @@ -36,30 +36,29 @@ public class DynamicFetchBuilderStandard implements DynamicFetchBuilder, NativeQuery.ReturnProperty { - private final String fetchableName; + private Fetchable fetchable; private final List columnNames; - public DynamicFetchBuilderStandard(String fetchableName) { - this.fetchableName = fetchableName; - this.columnNames = new ArrayList<>(); + public DynamicFetchBuilderStandard(Fetchable fetchable) { + this( fetchable, new ArrayList<>() ); } - private DynamicFetchBuilderStandard(String fetchableName, List columnNames) { - this.fetchableName = fetchableName; + private DynamicFetchBuilderStandard(Fetchable fetchable, List columnNames) { + this.fetchable = fetchable; this.columnNames = columnNames; } @Override public DynamicFetchBuilderStandard cacheKeyInstance() { return new DynamicFetchBuilderStandard( - fetchableName, + fetchable, List.copyOf( columnNames ) ); } public DynamicFetchBuilderStandard cacheKeyInstance(DynamicFetchBuilderContainer container) { return new DynamicFetchBuilderStandard( - fetchableName, + fetchable, List.copyOf( columnNames ) ); } @@ -74,14 +73,12 @@ public Fetch buildFetch( final TableGroup ownerTableGroup = creationStateImpl.getFromClauseAccess().getTableGroup( parent.getNavigablePath() ); - final Fetchable attributeMapping = - (Fetchable) parent.getReferencedMappingContainer().findSubPart( fetchableName, null ); final SqlExpressionResolver sqlExpressionResolver = domainResultCreationState.getSqlAstCreationState().getSqlExpressionResolver(); - final BasicValuedModelPart basicPart = attributeMapping.asBasicValuedModelPart(); + final BasicValuedModelPart basicPart = fetchable.asBasicValuedModelPart(); if ( basicPart != null ) { - attributeMapping.forEachSelectable( + fetchable.forEachSelectable( getSelectableConsumer( fetchPath, jdbcResultsMetadata, @@ -93,7 +90,7 @@ public Fetch buildFetch( ) ); return parent.generateFetchableFetch( - attributeMapping, + fetchable, fetchPath, FetchTiming.IMMEDIATE, true, @@ -101,8 +98,8 @@ public Fetch buildFetch( creationStateImpl ); } - else if ( attributeMapping instanceof EmbeddableValuedFetchable ) { - attributeMapping.forEachSelectable( + else if ( fetchable instanceof EmbeddableValuedFetchable embeddableValuedFetchable ) { + fetchable.forEachSelectable( getSelectableConsumer( fetchPath, jdbcResultsMetadata, @@ -110,11 +107,11 @@ else if ( attributeMapping instanceof EmbeddableValuedFetchable ) { creationStateImpl, ownerTableGroup, sqlExpressionResolver, - (EmbeddableValuedFetchable) attributeMapping + embeddableValuedFetchable ) ); return parent.generateFetchableFetch( - attributeMapping, + fetchable, fetchPath, FetchTiming.IMMEDIATE, false, @@ -122,7 +119,7 @@ else if ( attributeMapping instanceof EmbeddableValuedFetchable ) { creationStateImpl ); } - else if ( attributeMapping instanceof ToOneAttributeMapping toOneAttributeMapping ) { + else if ( fetchable instanceof ToOneAttributeMapping toOneAttributeMapping ) { toOneAttributeMapping.getForeignKeyDescriptor() .getPart( toOneAttributeMapping.getSideNature() ) .forEachSelectable( @@ -137,15 +134,15 @@ else if ( attributeMapping instanceof ToOneAttributeMapping toOneAttributeMappin ) ); return parent.generateFetchableFetch( - attributeMapping, + fetchable, fetchPath, - attributeMapping.getMappedFetchOptions().getTiming(), + fetchable.getMappedFetchOptions().getTiming(), false, null, creationStateImpl ); } - else if ( attributeMapping instanceof PluralAttributeMapping pluralAttributeMapping ) { + else if ( fetchable instanceof PluralAttributeMapping pluralAttributeMapping ) { pluralAttributeMapping.getKeyDescriptor().visitTargetSelectables( getSelectableConsumer( fetchPath, @@ -158,9 +155,9 @@ else if ( attributeMapping instanceof PluralAttributeMapping pluralAttributeMapp ) ); return parent.generateFetchableFetch( - attributeMapping, + fetchable, fetchPath, - attributeMapping.getMappedFetchOptions().getTiming(), + fetchable.getMappedFetchOptions().getTiming(), false, null, creationStateImpl @@ -215,7 +212,7 @@ public List getColumnAliases() { @Override public int hashCode() { - int result = fetchableName.hashCode(); + int result = fetchable.hashCode(); result = 31 * result + columnNames.hashCode(); return result; } @@ -230,7 +227,7 @@ public boolean equals(Object o) { } final DynamicFetchBuilderStandard that = (DynamicFetchBuilderStandard) o; - return fetchableName.equals( that.fetchableName ) + return fetchable.equals( that.fetchable ) && columnNames.equals( that.columnNames ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicResultBuilderEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicResultBuilderEntityStandard.java index 08ca18768735..02a044a88184 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicResultBuilderEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/dynamic/DynamicResultBuilderEntityStandard.java @@ -7,13 +7,12 @@ import org.hibernate.LockMode; import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.CollectionPart; -import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.internal.ManyToManyCollectionPart; -import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; import org.hibernate.query.NativeQuery; import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.internal.DomainResultCreationStateImpl; @@ -34,7 +33,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.function.Function; @@ -48,9 +46,6 @@ public class DynamicResultBuilderEntityStandard extends AbstractFetchBuilderContainer implements DynamicResultBuilderEntity, NativeQuery.RootReturn { - private static final String ELEMENT_PREFIX = CollectionPart.Nature.ELEMENT.getName() + "."; - private static final String INDEX_PREFIX = CollectionPart.Nature.INDEX.getName() + "."; - private final NavigablePath navigablePath; private final EntityMappingType entityMapping; @@ -84,18 +79,6 @@ private DynamicResultBuilderEntityStandard(DynamicResultBuilderEntityStandard or this.discriminatorColumnName = original.discriminatorColumnName; } - static String prefix(DomainResultCreationStateImpl creationState, String elementPrefix, String indexPrefix) { - final Map.Entry currentRelativePath = creationState.getCurrentRelativePath(); - if ( currentRelativePath == null ) { - return ""; - } - else { - return currentRelativePath.getKey() - .replace( elementPrefix, "" ) - .replace( indexPrefix, "" ) + "."; - } - } - @Override public Class getJavaType() { return entityMapping.getJavaType().getJavaTypeClass(); @@ -277,7 +260,7 @@ else if ( idFetchBuilder != null ) { } if ( discriminatorColumnName != null ) { - resolveSqlSelection( + resolveDiscriminatorSqlSelection( discriminatorColumnName, tableReference, entityMapping.getDiscriminatorMapping(), @@ -287,18 +270,10 @@ else if ( idFetchBuilder != null ) { } try { - final String prefix = prefix( creationState, ELEMENT_PREFIX, INDEX_PREFIX ); creationState.pushExplicitFetchMementoResolver( - relativePath -> { - if ( relativePath.startsWith( prefix ) ) { - final int startIndex; - if ( relativePath.regionMatches( prefix.length(), ELEMENT_PREFIX, 0, ELEMENT_PREFIX.length() ) ) { - startIndex = prefix.length() + ELEMENT_PREFIX.length(); - } - else { - startIndex = prefix.length(); - } - return findFetchBuilder( relativePath.substring( startIndex ) ); + f -> { + if ( f != null ) { + return findFetchBuilder( f ); } return null; } @@ -310,12 +285,27 @@ else if ( idFetchBuilder != null ) { } } + private static void resolveDiscriminatorSqlSelection(String columnAlias, TableReference tableReference, EntityDiscriminatorMapping discriminatorMapping, JdbcValuesMetadata jdbcResultsMetadata, DomainResultCreationState domainResultCreationState) { + final DomainResultCreationStateImpl creationStateImpl = impl( domainResultCreationState ); + creationStateImpl.resolveSqlSelection( + ResultsHelper.resolveSqlExpression( + creationStateImpl, + jdbcResultsMetadata, + tableReference, + discriminatorMapping, + columnAlias + ), + discriminatorMapping.getJdbcMapping().getJdbcJavaType(), + null, + domainResultCreationState.getSqlAstCreationState() + .getCreationContext() + .getSessionFactory() + .getTypeConfiguration() + ); + } + private FetchBuilder findIdFetchBuilder() { - final EntityIdentifierMapping identifierMapping = entityMapping.getIdentifierMapping(); - if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) { - return findFetchBuilder( ( (SingleAttributeIdentifierMapping) identifierMapping ).getAttributeName() ); - } - return findFetchBuilder( identifierMapping.getPartName() ); + return findFetchBuilder( entityMapping.getIdentifierMapping() ); } private void resolveSqlSelection( @@ -354,6 +344,18 @@ public DynamicResultBuilderEntityStandard setDiscriminatorAlias(String columnNam return this; } + @Override + public NativeQuery.RootReturn addProperty(String propertyName, String columnAlias) { + final ModelPart subPart = entityMapping.findSubPart( propertyName ); + addProperty( (Fetchable) subPart, columnAlias ); + return this; + } + + @Override + public NativeQuery.ReturnProperty addProperty(String propertyName) { + return addProperty( (Fetchable) entityMapping.findSubPart( propertyName ) ); + } + @Override public int hashCode() { int result = super.hashCode(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderBasic.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderBasic.java index a2babf7f3be9..331d8463c586 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderBasic.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderBasic.java @@ -15,6 +15,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; @@ -42,9 +43,7 @@ public ImplicitFetchBuilderBasic( DomainResultCreationState creationState) { this.fetchPath = fetchPath; this.fetchable = fetchable; - this.fetchBuilder = - impl( creationState ).getCurrentExplicitFetchMementoResolver() - .apply( fetchable.getFetchableName() ); + this.fetchBuilder = impl( creationState ).getCurrentExplicitFetchMementoResolver().apply( fetchable ); } @Override @@ -135,9 +134,9 @@ public int hashCode() { } @Override - public void visitFetchBuilders(BiConsumer consumer) { + public void visitFetchBuilders(BiConsumer consumer) { if ( fetchBuilder != null ) { - consumer.accept( fetchPath.getLocalName(), fetchBuilder ); + consumer.accept( fetchable, fetchBuilder ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEmbeddable.java index 3e469ea5a5c2..3b5d36c68f57 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEmbeddable.java @@ -34,7 +34,7 @@ public class ImplicitFetchBuilderEmbeddable implements ImplicitFetchBuilder { private final NavigablePath fetchPath; private final EmbeddableValuedFetchable fetchable; - private final Map fetchBuilders; + private final Map fetchBuilders; public ImplicitFetchBuilderEmbeddable( NavigablePath fetchPath, @@ -45,20 +45,18 @@ public ImplicitFetchBuilderEmbeddable( this.fetchBuilders = fetchBuilderMap( fetchPath, fetchable, impl( creationState ) ); } - private static Map fetchBuilderMap( + private static Map fetchBuilderMap( NavigablePath fetchPath, EmbeddableValuedFetchable fetchable, DomainResultCreationStateImpl creationStateImpl) { - final Function fetchBuilderResolver = + final Function fetchBuilderResolver = creationStateImpl.getCurrentExplicitFetchMementoResolver(); - final Map.Entry relativePath = creationStateImpl.getCurrentRelativePath(); final int size = fetchable.getNumberOfFetchables(); - final Map fetchBuilders = linkedMapOfSize( size ); + final Map fetchBuilders = linkedMapOfSize( size ); for ( int i = 0; i < size; i++ ) { final Fetchable subFetchable = fetchable.getFetchable( i ); - final NavigablePath subFetchPath = relativePath.getValue().append( subFetchable.getFetchableName() ); - final FetchBuilder explicitFetchBuilder = fetchBuilderResolver.apply( subFetchPath.getFullPath() ); - fetchBuilders.put( subFetchPath, + final FetchBuilder explicitFetchBuilder = fetchBuilderResolver.apply( subFetchable ); + fetchBuilders.put( subFetchable, explicitFetchBuilder == null ? implicitFetchBuilder( fetchPath, subFetchable, creationStateImpl ) : explicitFetchBuilder ); @@ -74,7 +72,7 @@ private ImplicitFetchBuilderEmbeddable(ImplicitFetchBuilderEmbeddable original) } else { fetchBuilders = new HashMap<>( original.fetchBuilders.size() ); - for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { + for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { fetchBuilders.put( entry.getKey(), entry.getValue().cacheKeyInstance() ); } } @@ -169,7 +167,7 @@ public String toString() { } @Override - public void visitFetchBuilders(BiConsumer consumer) { - fetchBuilders.forEach( (k, v) -> consumer.accept( k.getLocalName(), v ) ); + public void visitFetchBuilders(BiConsumer consumer) { + fetchBuilders.forEach( (k, v) -> consumer.accept( k, v ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEntity.java b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEntity.java index 609284f12c0b..60d06e52b914 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEntity.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/internal/implicit/ImplicitFetchBuilderEntity.java @@ -34,7 +34,7 @@ public class ImplicitFetchBuilderEntity implements ImplicitFetchBuilder { private final NavigablePath fetchPath; private final ToOneAttributeMapping fetchable; - private final Map fetchBuilders; + private final Map fetchBuilders; public ImplicitFetchBuilderEntity( NavigablePath fetchPath, @@ -43,30 +43,28 @@ public ImplicitFetchBuilderEntity( this.fetchPath = fetchPath; this.fetchable = fetchable; final DomainResultCreationStateImpl creationStateImpl = impl( creationState ); - final Map.Entry relativePath = creationStateImpl.getCurrentRelativePath(); - final Function fetchBuilderResolver = creationStateImpl.getCurrentExplicitFetchMementoResolver(); + final Function fetchBuilderResolver = creationStateImpl.getCurrentExplicitFetchMementoResolver(); final ForeignKeyDescriptor foreignKeyDescriptor = fetchable.getForeignKeyDescriptor(); final String associationKeyPropertyName; final NavigablePath associationKeyFetchPath; + final Fetchable associationKey; if ( fetchable.getReferencedPropertyName() == null ) { associationKeyPropertyName = fetchable.getEntityMappingType().getIdentifierMapping().getPartName(); - associationKeyFetchPath = relativePath.getValue().append( associationKeyPropertyName ); - } + associationKey = (Fetchable) fetchable.findSubPart( associationKeyPropertyName ); } else { associationKeyPropertyName = fetchable.getReferencedPropertyName(); - NavigablePath path = relativePath.getValue(); + String keyName = associationKeyPropertyName; for ( String part : split( ".", associationKeyPropertyName ) ) { - path = path.append( part ); + keyName = part; } - associationKeyFetchPath = path; + associationKey = (Fetchable) fetchable.findSubPart( keyName ); } final FetchBuilder explicitAssociationKeyFetchBuilder = - fetchBuilderResolver.apply( relativePath.getKey() + "." + associationKeyPropertyName ); + fetchBuilderResolver.apply( fetchable); if ( explicitAssociationKeyFetchBuilder == null ) { if ( foreignKeyDescriptor.getPartMappingType() instanceof EmbeddableMappingType embeddableType ) { fetchBuilders = fetchBuilderMap( fetchPath, - associationKeyFetchPath, fetchBuilderResolver, creationStateImpl, embeddableType @@ -77,22 +75,21 @@ public ImplicitFetchBuilderEntity( } } else { - fetchBuilders = singletonMap( associationKeyFetchPath, explicitAssociationKeyFetchBuilder ); + fetchBuilders = singletonMap( associationKey, explicitAssociationKeyFetchBuilder ); } } - private static Map fetchBuilderMap( - NavigablePath fetchPath, NavigablePath associationKeyFetchPath, - Function fetchBuilderResolver, + private static Map fetchBuilderMap( + NavigablePath fetchPath, + Function fetchBuilderResolver, DomainResultCreationStateImpl creationStateImpl, EmbeddableMappingType embeddableValuedModelPart) { final int size = embeddableValuedModelPart.getNumberOfFetchables(); - final Map fetchBuilders = linkedMapOfSize( size ); + final Map fetchBuilders = linkedMapOfSize( size ); for ( int i = 0; i < size; i++ ) { final Fetchable subFetchable = embeddableValuedModelPart.getFetchable( i ); - final NavigablePath subFetchPath = associationKeyFetchPath.append( subFetchable.getFetchableName() ); - final FetchBuilder explicitFetchBuilder = fetchBuilderResolver.apply( subFetchPath.getFullPath() ); - fetchBuilders.put( subFetchPath, + final FetchBuilder explicitFetchBuilder = fetchBuilderResolver.apply( subFetchable ); + fetchBuilders.put( subFetchable, explicitFetchBuilder == null ? Builders.implicitFetchBuilder( fetchPath, subFetchable, creationStateImpl ) : explicitFetchBuilder ); @@ -108,7 +105,7 @@ private ImplicitFetchBuilderEntity(ImplicitFetchBuilderEntity original) { } else { fetchBuilders = new HashMap<>( original.fetchBuilders.size() ); - for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { + for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { fetchBuilders.put( entry.getKey(), entry.getValue().cacheKeyInstance() ); } } @@ -136,8 +133,8 @@ public Fetch buildFetch( } @Override - public void visitFetchBuilders(BiConsumer consumer) { - fetchBuilders.forEach( (k, v) -> consumer.accept( k.getLocalName(), v ) ); + public void visitFetchBuilders(BiConsumer consumer) { + fetchBuilders.forEach( (k, v) -> consumer.accept( k, v ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index 1a692df5b35e..76b3fd66bdf9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -9,6 +9,7 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -23,6 +24,12 @@ import org.hibernate.jpa.spi.NativeQueryConstructorTransformer; import org.hibernate.jpa.spi.NativeQueryListTransformer; import org.hibernate.jpa.spi.NativeQueryMapTransformer; +import org.hibernate.metamodel.mapping.EntityAssociationMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.query.QueryFlushMode; import org.hibernate.HibernateException; @@ -90,6 +97,7 @@ import org.hibernate.query.sql.spi.SelectInterpretationsKey; import org.hibernate.sql.exec.internal.CallbackImpl; import org.hibernate.sql.exec.spi.Callback; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.spi.SingleResultConsumer; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.BasicType; @@ -140,6 +148,7 @@ public class NativeQueryImpl private final Class resultType; private final ResultSetMapping resultSetMapping; private final boolean resultMappingSuppliedToCtor; + private final HashMap entityMappingTypeByTableAlias = new HashMap<>(); private final QueryOptionsImpl queryOptions = new QueryOptionsImpl(); @@ -1093,6 +1102,7 @@ public DynamicResultBuilderEntityStandard addRoot(String tableAlias, String enti final DynamicResultBuilderEntityStandard resultBuilder = Builders.entity( tableAlias, entityName, getSessionFactory() ); resultSetMapping.addResultBuilder( resultBuilder ); + entityMappingTypeByTableAlias.put( tableAlias, resultBuilder.getEntityMapping() ); return resultBuilder; } @@ -1108,13 +1118,19 @@ public NativeQueryImplementor addEntity(String entityName) { @Override public NativeQueryImplementor addEntity(String tableAlias, String entityName) { - registerBuilder( Builders.entityCalculated( tableAlias, entityName, getSessionFactory() ) ); + final DynamicResultBuilderEntityCalculated builder = Builders.entityCalculated( tableAlias, entityName, + getSessionFactory() ); + entityMappingTypeByTableAlias.put( tableAlias, builder.getEntityMapping() ); + registerBuilder( builder ); return this; } @Override public NativeQueryImplementor addEntity(String tableAlias, String entityName, LockMode lockMode) { - registerBuilder( Builders.entityCalculated( tableAlias, entityName, lockMode, getSessionFactory() ) ); + final DynamicResultBuilderEntityCalculated builder = Builders.entityCalculated( tableAlias, entityName, lockMode, + getSessionFactory() ); + entityMappingTypeByTableAlias.put( tableAlias, builder.getEntityMapping() ); + registerBuilder( builder ); return this; } @@ -1140,11 +1156,29 @@ public NativeQueryImplementor addEntity(String tableAlias, @SuppressWarnings( @Override public FetchReturn addFetch(String tableAlias, String ownerTableAlias, String joinPropertyName) { - final DynamicFetchBuilderLegacy fetchBuilder = Builders.fetch( tableAlias, ownerTableAlias, joinPropertyName ); + final ModelPart subPart = entityMappingTypeByTableAlias.get( ownerTableAlias ).findSubPart( joinPropertyName ); + addEntityMappingType( tableAlias, subPart ); + final DynamicFetchBuilderLegacy fetchBuilder = Builders.fetch( tableAlias, ownerTableAlias, (Fetchable) subPart ); resultSetMapping.addLegacyFetchBuilder( fetchBuilder ); return fetchBuilder; } + private void addEntityMappingType(String tableAlias, ModelPart part) { + if ( part instanceof PluralAttributeMapping pluralAttributeMapping ) { + final MappingType partMappingType = pluralAttributeMapping.getElementDescriptor() + .getPartMappingType(); + if ( partMappingType instanceof EntityMappingType entityMappingType ) { + entityMappingTypeByTableAlias.put( tableAlias, entityMappingType ); + } + } + else if ( part instanceof EntityAssociationMapping entityAssociationMapping ) { + entityMappingTypeByTableAlias.put( tableAlias, entityAssociationMapping.asEntityMappingType() ); + } + else if ( part instanceof EmbeddedAttributeMapping ) { + throw new UnsupportedOperationException(); + } + } + @Override public NativeQueryImplementor addJoin(String tableAlias, String path) { createFetchJoin( tableAlias, path ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java index 2305da1b083d..141aad3e7359 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java @@ -21,11 +21,12 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.loader.internal.AliasConstantsHelper; -import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.SingleTableEntityPersister; import org.hibernate.query.NativeQuery; import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.LegacyFetchBuilder; @@ -35,6 +36,7 @@ import org.hibernate.query.results.internal.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.query.results.internal.dynamic.DynamicResultBuilderEntityStandard; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.type.CollectionType; import org.hibernate.type.ComponentType; import org.hibernate.type.EntityType; @@ -133,7 +135,7 @@ else if ( resultBuilder instanceof NativeQuery.CollectionReturn collectionReturn return this; } - private void processFetchBuilder(String attributeName, FetchBuilder fetchBuilder) { + private void processFetchBuilder(Fetchable attributeName, FetchBuilder fetchBuilder) { if ( fetchBuilder instanceof LegacyFetchBuilder ) { resultSetMapping.addLegacyFetchBuilder( (LegacyFetchBuilder) fetchBuilder ); } @@ -230,7 +232,7 @@ private void applyFetchBuilder( final EntityPersister loadable = alias2Persister.get( fetchBuilder.getOwnerAlias() ); final List columnNames; final String[] columnAliases = loadable.getSubclassPropertyColumnAliases( - fetchBuilder.getFetchableName(), + fetchBuilder.getFetchable().getFetchableName(), alias2Suffix.get( fetchBuilder.getOwnerAlias() ) ); if ( columnAliases.length == 0 ) { @@ -244,7 +246,7 @@ private void applyFetchBuilder( columnNames = Arrays.asList( keyColumnAliases ); if ( collectionPersister.hasIndex() ) { resultBuilderEntity.addProperty( - CollectionPart.Nature.INDEX.getName(), + ((PluralAttributeMapping) fetchBuilder.getFetchable()).getIndexDescriptor(), collectionPersister.getIndexColumnAliases( collectionSuffix ) ); } @@ -254,11 +256,11 @@ private void applyFetchBuilder( columnNames = Arrays.asList( columnAliases ); } ownerBuilder.addFetchBuilder( - fetchBuilder.getFetchableName(), + fetchBuilder.getFetchable(), new DynamicFetchBuilderLegacy( fetchBuilder.getTableAlias(), fetchBuilder.getOwnerAlias(), - fetchBuilder.getFetchableName(), + fetchBuilder.getFetchable(), columnNames, Collections.emptyMap(), resultBuilderEntity @@ -273,11 +275,11 @@ private NavigablePath determineNavigablePath(LegacyFetchBuilder fetchBuilder) { final NativeQuery.ResultNode ownerResult = alias2Return.get( fetchBuilder.getOwnerAlias() ); if ( ownerResult instanceof NativeQuery.RootReturn ) { return ( (NativeQuery.RootReturn) ownerResult ).getNavigablePath() - .append( fetchBuilder.getFetchableName() ); + .append( fetchBuilder.getFetchable().getFetchableName() ); } else { return determineNavigablePath( ( DynamicFetchBuilderLegacy) ownerResult ) - .append( fetchBuilder.getFetchableName() ); + .append( fetchBuilder.getFetchable().getFetchableName() ); } } @@ -311,28 +313,33 @@ private DynamicResultBuilderEntityStandard createSuffixedResultBuilder( resultBuilderEntity.addIdColumnAliases( identifierAliases ); resultBuilderEntity.setDiscriminatorAlias( loadable.getDiscriminatorAlias( suffix ) ); if ( loadable.hasIdentifierProperty() ) { - resultBuilderEntity.addProperty( loadable.getIdentifierPropertyName(), identifierAliases ); + resultBuilderEntity.addProperty( loadable.getIdentifierMapping(), identifierAliases ); } - final String[] propertyNames = loadable.getPropertyNames(); - for ( int i = 0; i < propertyNames.length; i++ ) { - if ( !loadable.isPropertySelectable( i ) ) { - continue; - } - final String propertyName = propertyNames[i]; - final String[] columnAliases = loadable.getSubclassPropertyColumnAliases( propertyName, suffix ); - final Type propertyType = loadable.getPropertyType( propertyName ); - addFetchBuilder( - suffix, - loadable, - resultBuilderEntity, - tableAlias, - identifierAliases, - propertyName, - columnAliases, - propertyType - ); - } + loadable.visitFetchables( + (index, fetchable) -> { + if ( fetchable.isSelectable() ) { + final Type propertyType; + if ( loadable instanceof SingleTableEntityPersister singleTableEntityPersister ) { + propertyType = singleTableEntityPersister.getSubclassPropertyType( index ); + } + else { + propertyType = loadable.getPropertyType( fetchable.getFetchableName() ); + } + addFetchBuilder( + suffix, + loadable, + resultBuilderEntity, + tableAlias, + identifierAliases, + fetchable, + loadable.getSubclassPropertyColumnAliases( fetchable.getFetchableName(), suffix ), + propertyType + ); + } + }, + null + ); return resultBuilderEntity; } @@ -342,7 +349,7 @@ private void addFetchBuilder( DynamicFetchBuilderContainer resultBuilderEntity, String tableAlias, String[] identifierAliases, - String propertyName, + Fetchable fetchable, String[] columnAliases, Type propertyType) { if ( propertyType instanceof CollectionType collectionType ) { @@ -356,15 +363,15 @@ private void addFetchBuilder( suffix ); } - resultBuilderEntity.addProperty( propertyName, keyColumnAliases ); + resultBuilderEntity.addProperty( fetchable, keyColumnAliases ); } else if ( propertyType instanceof ComponentType componentType ) { - final Map fetchBuilderMap = new HashMap<>(); + final Map fetchBuilderMap = new HashMap<>(); final DynamicFetchBuilderLegacy fetchBuilder = new DynamicFetchBuilderLegacy( "", tableAlias, - propertyName, - null, + fetchable, + Arrays.asList( columnAliases ), fetchBuilderMap ); final String[] propertyNames = componentType.getPropertyNames(); @@ -378,28 +385,28 @@ else if ( propertyType instanceof ComponentType componentType ) { fetchBuilder, tableAlias, identifierAliases, - propertyNames[i], + fetchable, ArrayHelper.slice( columnAliases, aliasIndex, columnSpan ), propertyTypes[i] ); aliasIndex += columnSpan; } - resultBuilderEntity.addFetchBuilder( propertyName, fetchBuilder ); + resultBuilderEntity.addFetchBuilder( fetchable, fetchBuilder ); } else if ( columnAliases.length != 0 ) { if ( propertyType instanceof EntityType ) { - final ToOneAttributeMapping toOne = (ToOneAttributeMapping) loadable.findAttributeMapping( propertyName ); + final ToOneAttributeMapping toOne = (ToOneAttributeMapping) fetchable; if ( !toOne.getIdentifyingColumnsTableExpression().equals( loadable.getTableName() ) ) { // The to-one has a join-table, use the plain join column name instead of the alias assert columnAliases.length == 1; final String[] targetAliases = new String[1]; targetAliases[0] = toOne.getTargetKeyPropertyName(); - resultBuilderEntity.addProperty( propertyName, targetAliases ); + resultBuilderEntity.addProperty( fetchable, targetAliases ); return; } } - resultBuilderEntity.addProperty( propertyName, columnAliases ); + resultBuilderEntity.addProperty( fetchable, columnAliases ); } } @@ -560,10 +567,10 @@ private void processFetchReturn(NativeQuery.FetchReturn fetchReturn) { } EntityPersister ownerPersister = alias2Persister.get( ownerAlias ); - Type returnType = ownerPersister.getPropertyType( fetchReturn.getFetchableName() ); + Type returnType = ownerPersister.getPropertyType( fetchReturn.getFetchable().getFetchableName() ); if ( returnType instanceof CollectionType ) { - String role = ownerPersister.getEntityName() + '.' + fetchReturn.getFetchableName(); + String role = ownerPersister.getEntityName() + '.' + fetchReturn.getFetchable().getFetchableName(); Map propertyResultsMap = Collections.emptyMap();//fetchReturn.getPropertyResultsMap() addCollection( role, alias, propertyResultsMap ); // collectionOwnerAliases.add( ownerAlias ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java index b52df9099747..956fa98b6622 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java @@ -6,6 +6,7 @@ import java.util.function.Function; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectablePath; @@ -90,6 +91,15 @@ static ColumnReferenceKey createColumnReferenceKey(TableReference tableReference return createColumnReferenceKey( tableReference, selectable.getSelectablePath(), selectable.getJdbcMapping() ); } + /** + * Convenience form for creating a key from TableReference and EntityDiscriminatorMapping + */ + static ColumnReferenceKey createDiscriminatorColumnReferenceKey(TableReference tableReference, EntityDiscriminatorMapping discriminatorMapping) { + assert tableReference.containsAffectedTableName( discriminatorMapping.getContainingTableExpression() ) + : String.format( ROOT, "Expecting tables to match between TableReference (%s) and SelectableMapping (%s)", tableReference.getTableId(), discriminatorMapping.getContainingTableExpression() ); + return createColumnReferenceKey( tableReference, discriminatorMapping.getSelectablePath(), discriminatorMapping.getUnderlyingJdbcMapping() ); + } + default Expression resolveSqlExpression(TableReference tableReference, SelectableMapping selectableMapping) { return resolveSqlExpression( createColumnReferenceKey( tableReference, selectableMapping ), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/SingleTableNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/SingleTableNativeQueryTest.java new file mode 100644 index 000000000000..cc1f292fcb69 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/SingleTableNativeQueryTest.java @@ -0,0 +1,456 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.inheritance; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.core.Is.is; + +@JiraKey("HHH-18610") +@DomainModel( + annotatedClasses = { + SingleTableNativeQueryTest.Toy.class, + SingleTableNativeQueryTest.Color.class, + SingleTableNativeQueryTest.Family.class, + SingleTableNativeQueryTest.Person.class, + SingleTableNativeQueryTest.Child.class, + SingleTableNativeQueryTest.Man.class, + SingleTableNativeQueryTest.Woman.class + } +) +@SessionFactory +public class SingleTableNativeQueryTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Toy marioToy; + Toy fidgetSpinner; + Man john; + Woman jane; + Child susan; + Child mark; + Family family; + List children; + List familyMembers; + + marioToy = new Toy( 1L, "Super Mario retro Mushroom" ); + fidgetSpinner = new Toy( 2L, "Fidget Spinner" ); + john = new Man( "John", "Riding Roller Coasters" ); + jane = new Woman( "Jane", "Hippotherapist" ); + susan = new Child( "Susan", marioToy ); + mark = new Child( "Mark", fidgetSpinner ); + family = new Family( "McCloud" ); + children = new ArrayList<>( Arrays.asList( susan, mark ) ); + familyMembers = Arrays.asList( john, jane, susan, mark ); + + + session.persist( marioToy ); + session.persist( fidgetSpinner ); + + jane.setColor( new Color( "pink" ) ); + jane.setHusband( john ); + jane.setChildren( children ); + + john.setColor( new Color( "blue" ) ); + john.setWife( jane ); + john.setChildren( children ); + + for ( Child child : children ) { + child.setFather( john ); + child.setMother( jane ); + } + + for ( Person person : familyMembers ) { + family.add( person ); + } + + session.persist( family ); + } ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + + @Test + public void itShouldGetPersons(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List results = session.createNativeQuery( "select {p.*} from person p order by p.name", + Object.class ).addEntity( "p", Person.class ).list(); + assertThat( results.stream().map( p -> ((Person) p).getName() ).collect( Collectors.toList() ), + contains( "Jane", "John", "Mark", "Susan" ) ); + } + ); + } + + @Test + public void itShouldGetWife(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List results = session.createNativeQuery( + "select {m.*}, {w.*} from person m left join person w on m.wife_name = w.name where m.TYPE = 'MAN'", + Object[].class ) + .addEntity( "m", Person.class ) + .addEntity( "w", Person.class ) + .list(); + assertThat( results.size(), is( 1 ) ); + assertThat( results.get( 0 )[0], instanceOf( Man.class ) ); + assertThat( ((Man) results.get( 0 )[0]).getName(), is( "John" ) ); + assertThat( results.get( 0 )[1], instanceOf( Woman.class ) ); + assertThat( ((Woman) results.get( 0 )[1]).getName(), is( "Jane" ) ); + } + ); + } + + @Test + public void itShouldGetFamilyMembers(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List results = session.createNativeQuery( "select {f.*} from family f", Object.class ) + .addEntity( "f", Family.class ).list(); + Family family = (Family) results.get( 0 ); + List members = family.getMembers(); + assertThat( members.size(), is( 4 ) ); + } + ); + } + + @Embeddable + public static class Color { + @Column(name = "color") + private String attributes; + + public Color() { + } + + public Color(final String attributes) { + this.attributes = attributes; + } + + public String getAttributes() { + return attributes; + } + + public void setAttributes(final String attributes) { + this.attributes = attributes; + } + } + + @Entity(name = "Family") + @Table(name = "family") + public static class Family { + + @Id + private String name; + + @OneToMany(mappedBy = "familyName", cascade = CascadeType.ALL, orphanRemoval = true) + private List members = new ArrayList<>(); + + public Family() { + } + + public Family(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getMembers() { + return members; + } + + public void setMembers(List members) { + this.members = members; + } + + public void add(Person person) { + person.setFamilyName( this ); + members.add( person ); + } + + @Override + public String toString() { + return "Family [name=" + name + "]"; + } + } + + @Entity(name = "Person") + @Table(name = "person") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING) + public static class Person { + + @Id + private String name; + + @ManyToOne + private Family familyName; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Family getFamilyName() { + return familyName; + } + + public void setFamilyName(Family familyName) { + this.familyName = familyName; + } + + @Override + public String toString() { + return name; + } + } + + + @Entity(name = "Toy") + @Table(name = "toy") + public static class Toy { + + @Id + private Long id; + + private String name; + + @OneToMany(mappedBy = "favoriteThing", cascade = CascadeType.ALL, orphanRemoval = true) + List favorite = new ArrayList<>(); + + public Toy() { + } + + public Toy(final Long id, final String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + } + + @Entity(name = "Child") + @DiscriminatorValue("CHILD") + public static class Child extends Person { + + @ManyToOne + @JoinColumn(name = "fav_toy") + private Toy favoriteThing; + + @ManyToOne + private Woman mother; + + @ManyToOne + private Man father; + + public Child() { + } + + public Child(String name, Toy favouriteThing) { + super( name ); + this.favoriteThing = favouriteThing; + } + + public Toy getFavoriteThing() { + return favoriteThing; + } + + public void setFavoriteThing(Toy favouriteThing) { + this.favoriteThing = favouriteThing; + } + + public Man getFather() { + return father; + } + + public void setFather(Man father) { + this.father = father; + } + + public Woman getMother() { + return mother; + } + + public void setMother(Woman mother) { + this.mother = mother; + } + } + + @Entity(name = "Man") + @DiscriminatorValue("MAN") + public static class Man extends Person { + + private Color color; + + @Column(name = "fav_hobby") + private String favoriteThing; + + @OneToOne + private Woman wife; + + @OneToMany(mappedBy = "father") + private List children = new ArrayList<>(); + + public Man() { + } + + public Man(String name, String favoriteThing) { + super( name ); + this.favoriteThing = favoriteThing; + } + + public String getFavoriteThing() { + return favoriteThing; + } + + public void setFavoriteThing(String favoriteThing) { + this.favoriteThing = favoriteThing; + } + + public Woman getWife() { + return wife; + } + + public void setWife(Woman wife) { + this.wife = wife; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Color getColor() { + return color; + } + + public void setColor(final Color color) { + this.color = color; + } + } + + @Entity(name = "Woman") + @DiscriminatorValue("WOMAN") + public static class Woman extends Person { + + private Color color; + + private String job; + + @OneToOne + private Man husband; + + @OneToMany(mappedBy = "mother") + private List children = new ArrayList<>(); + + public Woman() { + } + + public Woman(String name, String job) { + super( name ); + this.job = job; + } + + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + + public Man getHusband() { + return husband; + } + + public void setHusband(Man husband) { + this.husband = husband; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Color getColor() { + return color; + } + + public void setColor(final Color color) { + this.color = color; + } + } +}