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:
parent
b3e27788fa
commit
176abffdd5
|
@ -1463,7 +1463,6 @@ public abstract class CollectionBinder {
|
||||||
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );
|
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );
|
||||||
}
|
}
|
||||||
else if ( property.isAnnotationPresent( FetchProfileOverrides.class ) ) {
|
else if ( property.isAnnotationPresent( FetchProfileOverrides.class ) ) {
|
||||||
boolean result = false;
|
|
||||||
for ( FetchProfileOverride fetch: property.getAnnotation( FetchProfileOverrides.class ).value() ) {
|
for ( FetchProfileOverride fetch: property.getAnnotation( FetchProfileOverrides.class ).value() ) {
|
||||||
buildingContext.getMetadataCollector()
|
buildingContext.getMetadataCollector()
|
||||||
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );
|
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import org.hibernate.mapping.FetchProfile;
|
||||||
import org.hibernate.mapping.PersistentClass;
|
import org.hibernate.mapping.PersistentClass;
|
||||||
|
|
||||||
import static org.hibernate.internal.util.StringHelper.qualify;
|
import static org.hibernate.internal.util.StringHelper.qualify;
|
||||||
|
import static org.hibernate.mapping.FetchProfile.HIBERNATE_DEFAULT_PROFILE;
|
||||||
import static org.hibernate.mapping.MetadataSource.ANNOTATIONS;
|
import static org.hibernate.mapping.MetadataSource.ANNOTATIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,19 +43,18 @@ public class FetchSecondPass implements SecondPass {
|
||||||
@Override
|
@Override
|
||||||
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
|
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() );
|
FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetch.profile() );
|
||||||
if ( profile == null ) {
|
if ( profile == null ) {
|
||||||
throw new AnnotationException( "Property '" + qualify( propertyHolder.getPath(), propertyName )
|
if ( fetch.profile().equals( HIBERNATE_DEFAULT_PROFILE ) ) {
|
||||||
+ "' refers to an unknown fetch profile named '" + fetch.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(
|
profile.addFetch(
|
||||||
new FetchProfile.Fetch(
|
new FetchProfile.Fetch(
|
||||||
propertyHolder.getEntityName(),
|
propertyHolder.getEntityName(),
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.hibernate.annotations.OnDeleteAction;
|
||||||
import org.hibernate.annotations.common.reflection.XClass;
|
import org.hibernate.annotations.common.reflection.XClass;
|
||||||
import org.hibernate.annotations.common.reflection.XProperty;
|
import org.hibernate.annotations.common.reflection.XProperty;
|
||||||
import org.hibernate.boot.spi.AccessType;
|
import org.hibernate.boot.spi.AccessType;
|
||||||
|
import org.hibernate.boot.spi.InFlightMetadataCollector;
|
||||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||||
import org.hibernate.boot.spi.PropertyData;
|
import org.hibernate.boot.spi.PropertyData;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
|
@ -331,19 +332,34 @@ public class ToOneBinder {
|
||||||
XProperty property,
|
XProperty property,
|
||||||
PropertyHolder propertyHolder,
|
PropertyHolder propertyHolder,
|
||||||
PropertyData inferredData) {
|
PropertyData inferredData) {
|
||||||
|
final MetadataBuildingContext context = toOne.getBuildingContext();
|
||||||
|
final InFlightMetadataCollector collector = context.getMetadataCollector();
|
||||||
if ( property.isAnnotationPresent( FetchProfileOverride.class ) ) {
|
if ( property.isAnnotationPresent( FetchProfileOverride.class ) ) {
|
||||||
final FetchProfileOverride fetch = property.getAnnotation( FetchProfileOverride.class );
|
final FetchProfileOverride fetch = property.getAnnotation( FetchProfileOverride.class );
|
||||||
final MetadataBuildingContext context = toOne.getBuildingContext();
|
collector.addSecondPass( new FetchSecondPass( fetch, propertyHolder, inferredData.getPropertyName(), context ) );
|
||||||
context.getMetadataCollector()
|
|
||||||
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, inferredData.getPropertyName(), context ) );
|
|
||||||
}
|
}
|
||||||
else if ( property.isAnnotationPresent( FetchProfileOverrides.class ) ) {
|
else if ( property.isAnnotationPresent( FetchProfileOverrides.class ) ) {
|
||||||
for ( FetchProfileOverride fetch: property.getAnnotation( FetchProfileOverrides.class ).value() ) {
|
for ( FetchProfileOverride fetch: property.getAnnotation( FetchProfileOverrides.class ).value() ) {
|
||||||
final MetadataBuildingContext context = toOne.getBuildingContext();
|
collector.addSecondPass( new FetchSecondPass( fetch, propertyHolder, inferredData.getPropertyName(), context ) );
|
||||||
context.getMetadataCollector()
|
|
||||||
.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) {
|
private static void handleFetch(ToOne toOne, XProperty property) {
|
||||||
|
|
|
@ -22,6 +22,11 @@ import static jakarta.persistence.FetchType.EAGER;
|
||||||
* @see org.hibernate.engine.profile.FetchProfile
|
* @see org.hibernate.engine.profile.FetchProfile
|
||||||
*/
|
*/
|
||||||
public class 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 String name;
|
||||||
private final MetadataSource source;
|
private final MetadataSource source;
|
||||||
private final LinkedHashSet<Fetch> fetches = new LinkedHashSet<>();
|
private final LinkedHashSet<Fetch> fetches = new LinkedHashSet<>();
|
||||||
|
|
|
@ -7636,7 +7636,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
}
|
}
|
||||||
else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) {
|
else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) {
|
||||||
// There is no point in checking the fetch profile if it can't affect this fetchable
|
// 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();
|
final String fetchableRole = fetchable.getNavigableRole().getFullPath();
|
||||||
|
|
||||||
for ( String enabledFetchProfileName : getLoadQueryInfluencers()
|
for ( String enabledFetchProfileName : getLoadQueryInfluencers()
|
||||||
|
@ -7666,7 +7666,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
@SessionFactory
|
@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.NEW_PROFILE)
|
||||||
@FetchProfile(name = NewFetchTest.OLD_PROFILE,
|
@FetchProfile(name = NewFetchTest.OLD_PROFILE,
|
||||||
fetchOverrides = @FetchProfile.FetchOverride(entity = NewFetchTest.E.class, association = "f"))
|
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")
|
@Entity(name = "E")
|
||||||
static class E {
|
static class E {
|
||||||
@Id @GeneratedValue
|
@Id @GeneratedValue
|
||||||
|
@ -186,4 +227,13 @@ public class NewFetchTest {
|
||||||
@Id @GeneratedValue
|
@Id @GeneratedValue
|
||||||
Long id;
|
Long id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FetchProfile(name = "test")
|
||||||
|
@Entity(name = "H")
|
||||||
|
static class H {
|
||||||
|
@Id @GeneratedValue
|
||||||
|
Long id;
|
||||||
|
@FetchProfileOverride(profile = "test", mode = JOIN)
|
||||||
|
@ManyToOne G g;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue