HHH-16654 much more efficient implementation of default fetch profile

This commit is contained in:
Gavin 2023-05-24 16:53:57 +02:00 committed by Gavin King
parent 35da6000fd
commit 2b27d98a89
11 changed files with 100 additions and 85 deletions

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<? extends Annotation> annotationType() {
return FetchProfileOverride.class;
}
}

View File

@ -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<String, PersistentClass> 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(

View File

@ -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) {

View File

@ -0,0 +1,70 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.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<String, Fetch> getFetches() {
throw new UnsupportedOperationException( "DefaultFetchProfile has implicit fetches" );
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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<String, FetchProfile> getFetchProfiles(
MetadataImplementor bootMetamodel,
MappingMetamodel mappingMetamodel) {
RuntimeMetamodels runtimeMetamodels) {
final Map<String, FetchProfile> 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;
}

View File

@ -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();

View File

@ -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;

View File

@ -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;
* </ul>
* <p>
* 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.

View File

@ -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<H> 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 ) );