HHH-16654 introduce a "default" fetch profile with eager to-ones in it

also, make the query translator always respect the fetch type specified
in the fetch profile (previously it would ignore it for statically-EAGER
many-to-ones, which was inconsistent and made little sense)
This commit is contained in:
Gavin 2023-05-23 12:54:01 +02:00 committed by Gavin King
parent b3e27788fa
commit 176abffdd5
7 changed files with 133 additions and 20 deletions

View File

@ -1463,7 +1463,6 @@ public abstract class CollectionBinder {
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );
}
else if ( property.isAnnotationPresent( FetchProfileOverrides.class ) ) {
boolean result = false;
for ( FetchProfileOverride fetch: property.getAnnotation( FetchProfileOverrides.class ).value() ) {
buildingContext.getMetadataCollector()
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );

View File

@ -0,0 +1,43 @@
/*
* 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,6 +17,7 @@ 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;
/**
@ -42,19 +43,18 @@ public class FetchSecondPass implements SecondPass {
@Override
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
//TODO: handle propertyHolder.getPath() !!!!
// throws MappingException in case the property does not exist
buildingContext.getMetadataCollector()
.getEntityBinding( propertyHolder.getEntityName() )
.getProperty( propertyName );
FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetch.profile() );
if ( profile == null ) {
throw new AnnotationException( "Property '" + qualify( propertyHolder.getPath(), propertyName )
+ "' refers to an unknown fetch profile named '" + fetch.profile() + "'" );
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() + "'" );
}
}
else if ( profile.getSource() == ANNOTATIONS ) {
if ( profile.getSource() == ANNOTATIONS ) {
profile.addFetch(
new FetchProfile.Fetch(
propertyHolder.getEntityName(),

View File

@ -26,6 +26,7 @@ import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.internal.CoreMessageLogger;
@ -331,19 +332,34 @@ public class ToOneBinder {
XProperty property,
PropertyHolder propertyHolder,
PropertyData inferredData) {
final MetadataBuildingContext context = toOne.getBuildingContext();
final InFlightMetadataCollector collector = context.getMetadataCollector();
if ( property.isAnnotationPresent( FetchProfileOverride.class ) ) {
final FetchProfileOverride fetch = property.getAnnotation( FetchProfileOverride.class );
final MetadataBuildingContext context = toOne.getBuildingContext();
context.getMetadataCollector()
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, inferredData.getPropertyName(), context ) );
collector.addSecondPass( new FetchSecondPass( fetch, propertyHolder, inferredData.getPropertyName(), context ) );
}
else if ( property.isAnnotationPresent( FetchProfileOverrides.class ) ) {
for ( FetchProfileOverride fetch: property.getAnnotation( FetchProfileOverrides.class ).value() ) {
final MetadataBuildingContext context = toOne.getBuildingContext();
context.getMetadataCollector()
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, inferredData.getPropertyName(), context ) );
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

@ -22,6 +22,11 @@ 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;
private final LinkedHashSet<Fetch> fetches = new LinkedHashSet<>();

View File

@ -7636,7 +7636,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) {
// There is no point in checking the fetch profile if it can't affect this fetchable
if ( fetchTiming != FetchTiming.IMMEDIATE || fetchable.incrementFetchDepth() ) {
// if ( fetchTiming != FetchTiming.IMMEDIATE || fetchable.incrementFetchDepth() ) {
final String fetchableRole = fetchable.getNavigableRole().getFullPath();
for ( String enabledFetchProfileName : getLoadQueryInfluencers()
@ -7666,7 +7666,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
}
}
}
// }
}
}
}

View File

@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SessionFactory
@DomainModel(annotatedClasses = {NewFetchTest.class,NewFetchTest.E.class, NewFetchTest.F.class, NewFetchTest.G.class})
@DomainModel(annotatedClasses = {NewFetchTest.class,NewFetchTest.E.class, NewFetchTest.F.class, NewFetchTest.G.class, NewFetchTest.H.class})
@FetchProfile(name = NewFetchTest.NEW_PROFILE)
@FetchProfile(name = NewFetchTest.OLD_PROFILE,
fetchOverrides = @FetchProfile.FetchOverride(entity = NewFetchTest.E.class, association = "f"))
@ -160,6 +160,47 @@ public class NewFetchTest {
});
}
@Test void testDefaultProfile(SessionFactoryScope scope) {
scope.inTransaction( s-> {
G g = new G();
H h1 = new H();
h1.g = g;
H h2 = new H();
h2.g = g;
s.persist(g);
s.persist(h1);
s.persist(h2);
});
scope.getCollectingStatementInspector().assertExecutedCount(6);
scope.getCollectingStatementInspector().clear();
List<H> hs1 = scope.fromSession( s -> {
return s.createSelectionQuery("from H", H.class).getResultList();
});
assertTrue( isInitialized( hs1.get(0).g ) );
scope.getCollectingStatementInspector().assertExecutedCount(2);
scope.getCollectingStatementInspector().assertNumberOfJoins(0, 0);
scope.getCollectingStatementInspector().assertNumberOfJoins(1, 0);
scope.getCollectingStatementInspector().clear();
List<H> hs2 = scope.fromSession( s -> {
s.enableFetchProfile( org.hibernate.mapping.FetchProfile.HIBERNATE_DEFAULT_PROFILE );
return s.createSelectionQuery("from H", H.class).getResultList();
});
assertTrue( isInitialized( hs2.get(0).g ) );
scope.getCollectingStatementInspector().assertExecutedCount(1);
scope.getCollectingStatementInspector().assertNumberOfJoins(0,1);
scope.getCollectingStatementInspector().clear();
List<H> hs3 = scope.fromSession( s -> {
s.enableFetchProfile("test");
return s.createSelectionQuery("from H", H.class).getResultList();
});
assertTrue( isInitialized( hs3.get(0).g ) );
scope.getCollectingStatementInspector().assertExecutedCount(1);
scope.getCollectingStatementInspector().assertNumberOfJoins(0,1);
}
@Entity(name = "E")
static class E {
@Id @GeneratedValue
@ -186,4 +227,13 @@ public class NewFetchTest {
@Id @GeneratedValue
Long id;
}
@FetchProfile(name = "test")
@Entity(name = "H")
static class H {
@Id @GeneratedValue
Long id;
@FetchProfileOverride(profile = "test", mode = JOIN)
@ManyToOne G g;
}
}