From 2b27d98a89ae69be9d9a7b8e35f7c42684e6262e Mon Sep 17 00:00:00 2001 From: Gavin Date: Wed, 24 May 2023 16:53:57 +0200 Subject: [PATCH] HHH-16654 much more efficient implementation of default fetch profile --- .../internal/DefaultFetchProfileOverride.java | 43 ------------ .../boot/model/internal/FetchSecondPass.java | 13 +--- .../boot/model/internal/ToOneBinder.java | 17 ----- .../engine/profile/DefaultFetchProfile.java | 70 +++++++++++++++++++ .../engine/profile/FetchProfile.java | 12 ++++ .../engine/spi/LoadQueryInfluencers.java | 9 +-- .../internal/FetchProfileHelper.java | 9 ++- .../internal/SessionFactoryImpl.java | 2 +- .../org/hibernate/mapping/FetchProfile.java | 4 -- .../org/hibernate/query/SelectionQuery.java | 3 +- .../fetchprofile/NewFetchTest.java | 3 +- 11 files changed, 100 insertions(+), 85 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/boot/model/internal/DefaultFetchProfileOverride.java create mode 100644 hibernate-core/src/main/java/org/hibernate/engine/profile/DefaultFetchProfile.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DefaultFetchProfileOverride.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DefaultFetchProfileOverride.java deleted file mode 100644 index ab8df364a6..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/DefaultFetchProfileOverride.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.boot.model.internal; - -import jakarta.persistence.FetchType; -import org.hibernate.annotations.FetchProfileOverride; -import org.hibernate.mapping.FetchProfile; - -import java.lang.annotation.Annotation; - -import static jakarta.persistence.FetchType.EAGER; - -/** - * @author Gavin King - */ -class DefaultFetchProfileOverride implements FetchProfileOverride { - - static final FetchProfileOverride INSTANCE = new DefaultFetchProfileOverride(); - - @Override - public org.hibernate.annotations.FetchMode mode() { - return org.hibernate.annotations.FetchMode.JOIN; - } - - @Override - public FetchType fetch() { - return EAGER; - } - - @Override - public String profile() { - return FetchProfile.HIBERNATE_DEFAULT_PROFILE; - } - - @Override - public Class annotationType() { - return FetchProfileOverride.class; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FetchSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FetchSecondPass.java index a3b49a2f3f..1c1d84c4ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FetchSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FetchSecondPass.java @@ -17,7 +17,6 @@ import org.hibernate.mapping.FetchProfile; import org.hibernate.mapping.PersistentClass; import static org.hibernate.internal.util.StringHelper.qualify; -import static org.hibernate.mapping.FetchProfile.HIBERNATE_DEFAULT_PROFILE; import static org.hibernate.mapping.MetadataSource.ANNOTATIONS; /** @@ -43,16 +42,10 @@ public class FetchSecondPass implements SecondPass { @Override public void doSecondPass(Map persistentClasses) throws MappingException { - FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetch.profile() ); + final FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetch.profile() ); if ( profile == null ) { - if ( fetch.profile().equals( HIBERNATE_DEFAULT_PROFILE ) ) { - profile = new FetchProfile( HIBERNATE_DEFAULT_PROFILE, ANNOTATIONS ); - buildingContext.getMetadataCollector().addFetchProfile( profile ); - } - else { - throw new AnnotationException( "Property '" + qualify( propertyHolder.getPath(), propertyName ) - + "' refers to an unknown fetch profile named '" + fetch.profile() + "'" ); - } + throw new AnnotationException( "Property '" + qualify( propertyHolder.getPath(), propertyName ) + + "' refers to an unknown fetch profile named '" + fetch.profile() + "'" ); } if ( profile.getSource() == ANNOTATIONS ) { profile.addFetch( diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java index d2694acef4..757ddf9565 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java @@ -343,23 +343,6 @@ public class ToOneBinder { collector.addSecondPass( new FetchSecondPass( fetch, propertyHolder, inferredData.getPropertyName(), context ) ); } } - if ( !toOne.isLazy() - && !propertyHolder.isOrWithinEmbeddedId() - && !propertyHolder.isWithinElementCollection() - && !propertyHolder.isInIdClass() - // this is a bit of a problem: embeddable classes don't - // come with the entity name attached, so we can't - // create a Fetch that refers to their fields - && !propertyHolder.isComponent() - // not sure exactly what the story is here: - && !collector.isInSecondPass() ) { - collector.addSecondPass( new FetchSecondPass( - DefaultFetchProfileOverride.INSTANCE, - propertyHolder, - inferredData.getPropertyName(), - context - ) ); - } } private static void handleFetch(ToOne toOne, XProperty property) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/profile/DefaultFetchProfile.java b/hibernate-core/src/main/java/org/hibernate/engine/profile/DefaultFetchProfile.java new file mode 100644 index 0000000000..23a96ff431 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/profile/DefaultFetchProfile.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.engine.profile; + +import org.hibernate.metamodel.RuntimeMetamodels; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.sql.results.graph.FetchOptions; +import org.hibernate.tuple.NonIdentifierAttribute; + +import java.util.Map; + +import static org.hibernate.engine.FetchStyle.SUBSELECT; +import static org.hibernate.engine.FetchTiming.IMMEDIATE; +import static org.hibernate.engine.FetchStyle.JOIN; + +/** + * @author Gavin King + */ +public class DefaultFetchProfile extends FetchProfile { + /** + * The name of an implicit fetch profile which includes all eager to-one associations. + */ + public static final String HIBERNATE_DEFAULT_PROFILE = "org.hibernate.defaultProfile"; + private final RuntimeMetamodels metamodels; + + public DefaultFetchProfile(RuntimeMetamodels metamodels) { + super(HIBERNATE_DEFAULT_PROFILE); + this.metamodels = metamodels; + } + + @Override + public Fetch getFetchByRole(String role) { + final int last = role.lastIndexOf('.'); + final String entityName = role.substring( 0, last ); + final String property = role.substring( last + 1 ); + final EntityMappingType entity = metamodels.getEntityMappingType( entityName ); + if ( entity != null ) { + final AttributeMapping attributeMapping = entity.findAttributeMapping( property ); + if ( attributeMapping != null && !attributeMapping.isPluralAttributeMapping() ) { + final FetchOptions fetchOptions = attributeMapping.getMappedFetchOptions(); + if ( fetchOptions.getStyle() == JOIN && fetchOptions.getTiming() == IMMEDIATE ) { + return new Fetch( new Association( entity.getEntityPersister(), role ), JOIN, IMMEDIATE ); + } + } + } + return super.getFetchByRole( role ); + } + + @Override + public boolean hasSubselectLoadableCollectionsEnabled(EntityPersister persister) { + final EntityMappingType entity = metamodels.getEntityMappingType( persister.getEntityName() ); + for ( AttributeMapping attributeMapping : entity.getAttributeMappings() ) { + if ( attributeMapping.getMappedFetchOptions().getStyle() == SUBSELECT ) { + return true; + } + } + return false; + } + + @Override + public Map getFetches() { + throw new UnsupportedOperationException( "DefaultFetchProfile has implicit fetches" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/profile/FetchProfile.java b/hibernate-core/src/main/java/org/hibernate/engine/profile/FetchProfile.java index e10860d51e..8e0f059692 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/profile/FetchProfile.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/profile/FetchProfile.java @@ -12,10 +12,12 @@ import java.util.Map; import org.hibernate.Internal; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.BagType; import org.hibernate.type.Type; import static org.hibernate.engine.FetchStyle.JOIN; +import static org.hibernate.engine.FetchStyle.SUBSELECT; /** * The runtime representation of a Hibernate @@ -181,4 +183,14 @@ public class FetchProfile { public String toString() { return "FetchProfile[" + name + "]"; } + + public boolean hasSubselectLoadableCollectionsEnabled(EntityPersister persister) { + for ( Fetch fetch : getFetches().values() ) { + if ( fetch.getMethod() == SUBSELECT + && fetch.getAssociation().getOwner() == persister ) { + return true; + } + } + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java index 679ba4c234..d1d70401e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java @@ -344,12 +344,9 @@ public class LoadQueryInfluencers implements Serializable { private boolean hasSubselectLoadableCollectionsEnabledInProfile(EntityPersister persister) { if ( hasEnabledFetchProfiles() ) { for ( String profile : getEnabledFetchProfileNames() ) { - final FetchProfile fetchProfile = sessionFactory.getFetchProfile( profile ); - for ( Fetch fetch : fetchProfile.getFetches().values() ) { - // TODO: check that it's relevant to this persister?? - if ( fetch.getMethod() == SUBSELECT ) { - return true; - } + if ( sessionFactory.getFetchProfile( profile ) + .hasSubselectLoadableCollectionsEnabled( persister ) ) { + return true; } } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FetchProfileHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/FetchProfileHelper.java index a5d4cba794..79935d7e8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FetchProfileHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FetchProfileHelper.java @@ -11,10 +11,12 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.profile.Association; +import org.hibernate.engine.profile.DefaultFetchProfile; import org.hibernate.engine.profile.Fetch; import org.hibernate.engine.profile.FetchProfile; import org.hibernate.engine.profile.internal.FetchProfileAffectee; import org.hibernate.metamodel.MappingMetamodel; +import org.hibernate.metamodel.RuntimeMetamodels; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; @@ -23,6 +25,8 @@ import org.hibernate.persister.entity.EntityPersister; import java.util.HashMap; import java.util.Map; +import static org.hibernate.engine.profile.DefaultFetchProfile.HIBERNATE_DEFAULT_PROFILE; + /** * Create {@link FetchProfile} references from {@link org.hibernate.mapping.FetchProfile} references * @@ -32,12 +36,13 @@ public class FetchProfileHelper { public static Map getFetchProfiles( MetadataImplementor bootMetamodel, - MappingMetamodel mappingMetamodel) { + RuntimeMetamodels runtimeMetamodels) { final Map fetchProfiles = new HashMap<>(); for ( org.hibernate.mapping.FetchProfile mappingProfile : bootMetamodel.getFetchProfiles() ) { - final FetchProfile fetchProfile = createFetchProfile( mappingMetamodel, mappingProfile ); + final FetchProfile fetchProfile = createFetchProfile( runtimeMetamodels.getMappingMetamodel(), mappingProfile ); fetchProfiles.put( fetchProfile.getName(), fetchProfile ); } + fetchProfiles.put( HIBERNATE_DEFAULT_PROFILE, new DefaultFetchProfile( runtimeMetamodels ) ); return fetchProfiles; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index a19f8d424f..831842def9 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -272,7 +272,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im // this needs to happen after the mapping metamodel is // completely built, since we need to use the persisters - fetchProfiles = getFetchProfiles( bootMetamodel, runtimeMetamodels.getMappingMetamodel() ); + fetchProfiles = getFetchProfiles( bootMetamodel, runtimeMetamodels ); defaultSessionOpenOptions = createDefaultSessionOpenOptionsIfPossible(); temporarySessionOpenOptions = defaultSessionOpenOptions == null ? null : buildTemporarySessionOpenOptions(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/FetchProfile.java b/hibernate-core/src/main/java/org/hibernate/mapping/FetchProfile.java index 9870b1d352..05718996fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/FetchProfile.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/FetchProfile.java @@ -22,10 +22,6 @@ import static jakarta.persistence.FetchType.EAGER; * @see org.hibernate.engine.profile.FetchProfile */ public class FetchProfile { - /** - * The name of an implicit fetch profile which includes all eager to-one associations. - */ - public static final String HIBERNATE_DEFAULT_PROFILE = "org.hibernate.defaultProfile"; private final String name; private final MetadataSource source; diff --git a/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java index 60d505a986..0ecc3ed227 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java @@ -35,6 +35,7 @@ import jakarta.persistence.FlushModeType; import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; +import org.hibernate.engine.profile.DefaultFetchProfile; import org.hibernate.graph.GraphSemantic; /** @@ -78,7 +79,7 @@ import org.hibernate.graph.GraphSemantic; * *

* The special built-in fetch profile named - * {@value org.hibernate.mapping.FetchProfile#HIBERNATE_DEFAULT_PROFILE} adds + * {@value DefaultFetchProfile#HIBERNATE_DEFAULT_PROFILE} adds * a fetch join for every {@link jakarta.persistence.FetchType#EAGER eager} * {@code @ManyToOne} or {@code @OneToOne} association belonging to an entity * returned by the query. diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/fetchprofile/NewFetchTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/fetchprofile/NewFetchTest.java index a0a4e0d8cf..5c3ebac714 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/fetchprofile/NewFetchTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/fetchprofile/NewFetchTest.java @@ -21,6 +21,7 @@ import static org.hibernate.Hibernate.isInitialized; import static org.hibernate.annotations.FetchMode.JOIN; import static org.hibernate.annotations.FetchMode.SELECT; import static org.hibernate.annotations.FetchMode.SUBSELECT; +import static org.hibernate.engine.profile.DefaultFetchProfile.HIBERNATE_DEFAULT_PROFILE; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -210,7 +211,7 @@ public class NewFetchTest { scope.getCollectingStatementInspector().clear(); List hs2 = scope.fromSession( s -> { - s.enableFetchProfile( org.hibernate.mapping.FetchProfile.HIBERNATE_DEFAULT_PROFILE ); + s.enableFetchProfile( HIBERNATE_DEFAULT_PROFILE ); return s.createSelectionQuery("from H", H.class).getResultList(); }); assertTrue( isInitialized( hs2.get(0).g ) );