HHH-16651 cleaner separation of "fetch method" vs "fetch timing"

This commit is contained in:
Gavin 2023-05-23 09:12:59 +02:00 committed by Gavin King
parent 7ed0241dc5
commit c636c83d7e
16 changed files with 228 additions and 67 deletions

View File

@ -7,7 +7,7 @@
package org.hibernate.annotations;
/**
* Enumerates strategies for fetching an association from the database.
* Enumerates methods for fetching an association from the database.
* <p>
* The JPA-defined {@link jakarta.persistence.FetchType} enumerates the
* possibilities for <em>when</em> an association might be fetched. This
@ -46,7 +46,7 @@ public enum FetchMode {
* when it is almost certain that the associated data will be
* available in the second-level cache.
*/
SELECT( org.hibernate.FetchMode.SELECT ),
SELECT,
/**
* Use an outer join to load all instances of the related entity
@ -62,7 +62,7 @@ public enum FetchMode {
* since the associated data is retrieved as part of the initial
* query.
*/
JOIN( org.hibernate.FetchMode.JOIN ),
JOIN,
/**
* Use a secondary select with a subselect that re-executes an
@ -84,15 +84,11 @@ public enum FetchMode {
* re-execution of the initial query within a SQL subselect.
* </ul>
*/
SUBSELECT( org.hibernate.FetchMode.SELECT );
private final org.hibernate.FetchMode hibernateFetchMode;
FetchMode(org.hibernate.FetchMode hibernateFetchMode) {
this.hibernateFetchMode = hibernateFetchMode;
}
SUBSELECT;
public org.hibernate.FetchMode getHibernateFetchMode() {
return hibernateFetchMode;
return this == JOIN
? org.hibernate.FetchMode.JOIN
: org.hibernate.FetchMode.SELECT;
}
}

View File

@ -6,10 +6,13 @@
*/
package org.hibernate.annotations;
import jakarta.persistence.FetchType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static jakarta.persistence.FetchType.EAGER;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -91,8 +94,11 @@ public @interface FetchProfile {
FetchOverride[] fetchOverrides() default {};
/**
* Overrides the fetching strategy pf a particular association
* in the named fetch profile being defined.
* Overrides the fetching strategy of a particular association in
* the named fetch profile being defined. If {@link #mode} and
* {@link #fetch} are both unspecified, the strategy defaults to
* {@linkplain FetchType#EAGER eager} {@linkplain FetchMode#JOIN join}
* fetching.
*/
@Target({ TYPE, PACKAGE })
@Retention(RUNTIME)
@ -110,9 +116,15 @@ public @interface FetchProfile {
String association();
/**
* The {@linkplain FetchMode fetching strategy} to apply to
* the association in the fetch profile being defined.
* The {@linkplain FetchMode method} used for fetching the
* association in the fetch profile being defined.
*/
FetchMode mode() default JOIN;
/**
* The {@link FetchType timing} of association fetching in
* the fetch profile being defined.
*/
FetchType fetch() default EAGER;
}
}

View File

@ -12,6 +12,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import jakarta.persistence.FetchType;
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.annotations.CollectionTypeRegistration;
@ -22,6 +23,7 @@ import org.hibernate.annotations.ConverterRegistration;
import org.hibernate.annotations.ConverterRegistrations;
import org.hibernate.annotations.EmbeddableInstantiatorRegistration;
import org.hibernate.annotations.EmbeddableInstantiatorRegistrations;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.FetchProfile;
import org.hibernate.annotations.FetchProfile.FetchOverride;
import org.hibernate.annotations.FetchProfiles;
@ -852,7 +854,11 @@ public final class AnnotationBinder {
final String name = fetchProfile.name();
if ( reuseOrCreateFetchProfile( context, name ) ) {
for ( FetchOverride fetch : fetchProfile.fetchOverrides() ) {
// TODO: validate which modes are valid where
if ( fetch.fetch() == FetchType.LAZY && fetch.mode() == FetchMode.JOIN ) {
throw new AnnotationException( "Fetch profile '" + name
+ "' has a '@FetchOverride' with 'fetch=LAZY' and 'mode=JOIN'"
+ " (join fetching is eager by nature)");
}
context.getMetadataCollector()
.addSecondPass( new FetchOverrideSecondPass( name, fetch, context ) );
}

View File

@ -13,7 +13,6 @@ import org.hibernate.boot.spi.SecondPass;
import org.hibernate.mapping.FetchProfile;
import org.hibernate.mapping.PersistentClass;
import java.util.Locale;
import java.util.Map;
/**
@ -43,9 +42,12 @@ public class FetchOverrideSecondPass implements SecondPass {
final FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetchProfileName );
// we already know that the FetchProfile exists and is good to use
profile.addFetch(
fetch.entity().getName(),
fetch.association(),
fetch.mode().toString().toLowerCase(Locale.ROOT)
new FetchProfile.Fetch(
fetch.entity().getName(),
fetch.association(),
fetch.mode(),
fetch.fetch()
)
);
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.engine;
import jakarta.persistence.FetchType;
/**
* Enumeration of values describing <em>when</em> fetching should occur.
*
@ -20,5 +22,16 @@ public enum FetchTiming {
/**
* Performing fetching later, when needed. Also called lazy fetching.
*/
DELAYED
DELAYED;
public static FetchTiming forType(FetchType type) {
switch ( type ) {
case EAGER:
return IMMEDIATE;
case LAZY:
return DELAYED;
default:
throw new IllegalArgumentException( "Unknown FetchType" );
}
}
}

View File

@ -6,8 +6,15 @@
*/
package org.hibernate.engine.profile;
import org.hibernate.AssertionFailure;
import org.hibernate.annotations.FetchMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import java.util.Locale;
import static org.hibernate.engine.FetchTiming.IMMEDIATE;
/**
* Models an individual fetch override within a {@link FetchProfile}.
*
@ -15,17 +22,34 @@ import java.util.Locale;
*/
public class Fetch {
private final Association association;
private final Style style;
private final FetchStyle method;
private final FetchTiming timing;
/**
* Constructs a {@link Fetch}.
*
* @param association The association to be fetched
* @param style How to fetch it
*
* @deprecated use {@link #Fetch(Association,FetchStyle,FetchTiming)}
*/
@Deprecated(forRemoval = true)
public Fetch(Association association, Style style) {
this.association = association;
this.style = style;
this.method = style.toFetchStyle();
this.timing = IMMEDIATE;
}
/**
* Constructs a {@link Fetch}.
*
* @param association The association to be fetched
* @param method How to fetch it
*/
public Fetch(Association association, FetchStyle method, FetchTiming timing) {
this.association = association;
this.method = method;
this.timing = timing;
}
/**
@ -37,14 +61,34 @@ public class Fetch {
/**
* The fetch style applied to the association.
*
* @deprecated use {@link #getMethod()}
*/
@Deprecated(forRemoval = true)
public Style getStyle() {
return style;
return Style.fromFetchStyle( method );
}
/**
* The fetch method to be applied to the association.
*/
public FetchStyle getMethod() {
return method;
}
/**
* The fetch timing to be applied to the association.
*/
public FetchTiming getTiming() {
return timing;
}
/**
* The type or style of fetch.
*
* @deprecated Use {@link FetchStyle}
*/
@Deprecated(forRemoval = true)
public enum Style {
/**
* Fetch via a join
@ -59,6 +103,32 @@ public class Fetch {
*/
SUBSELECT;
public FetchStyle toFetchStyle() {
switch (this) {
case SELECT:
return FetchStyle.SELECT;
case SUBSELECT:
return FetchStyle.SUBSELECT;
case JOIN:
return FetchStyle.JOIN;
default:
throw new AssertionFailure("Unknown Fetch.Style");
}
}
static Style fromFetchStyle(FetchStyle fetchStyle) {
switch (fetchStyle) {
case SELECT:
return SELECT;
case SUBSELECT:
return SUBSELECT;
case JOIN:
return JOIN;
default:
throw new IllegalArgumentException("Unhandled FetchStyle");
}
}
@Override
public String toString() {
return name().toLowerCase(Locale.ROOT);
@ -79,10 +149,23 @@ public class Fetch {
}
return JOIN;
}
public static Style forMethod(FetchMode fetchMode) {
switch ( fetchMode ) {
case JOIN:
return JOIN;
case SELECT:
return SELECT;
case SUBSELECT:
return SUBSELECT;
default:
throw new IllegalArgumentException( "Unknown FetchMode" );
}
}
}
@Override
public String toString() {
return "Fetch[" + style + "{" + association.getRole() + "}]";
return "Fetch[" + method + "{" + association.getRole() + "}]";
}
}

View File

@ -15,6 +15,8 @@ import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.type.BagType;
import org.hibernate.type.Type;
import static org.hibernate.engine.FetchStyle.JOIN;
/**
* The runtime representation of a Hibernate
* {@linkplain org.hibernate.annotations.FetchProfile fetch profile}
@ -97,7 +99,7 @@ public class FetchProfile {
// couple of things for which to account in the case of collection
// join fetches
if ( Fetch.Style.JOIN == fetch.getStyle() ) {
if ( fetch.getMethod() == JOIN ) {
// first, if this is a bag we need to ignore it if we previously
// processed collection join fetches
if ( associationType instanceof BagType ) {

View File

@ -6,8 +6,6 @@
*/
package org.hibernate.engine.profile.internal;
import org.hibernate.engine.profile.Fetch;
/**
* Commonality between entities and collections as something that can be affected by fetch profiles.
*
@ -17,5 +15,5 @@ public interface FetchProfileAffectee {
/**
* Register the profile name with the entity/collection
*/
void registerAffectingFetchProfile(String fetchProfileName, Fetch.Style fetchStyle);
void registerAffectingFetchProfile(String fetchProfileName);
}

View File

@ -26,7 +26,7 @@ import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import static java.util.Collections.emptySet;
import static org.hibernate.engine.profile.Fetch.Style.SUBSELECT;
import static org.hibernate.engine.FetchStyle.SUBSELECT;
/**
* Centralize all options which can influence the SQL query needed to load an
@ -318,7 +318,7 @@ public class LoadQueryInfluencers implements Serializable {
final FetchProfile fetchProfile = sessionFactory.getFetchProfile( profile );
if ( fetchProfile != null ) {
final Fetch fetch = fetchProfile.getFetchByRole( persister.getRole() );
if ( fetch != null && fetch.getStyle() == SUBSELECT ) {
if ( fetch != null && fetch.getMethod() == SUBSELECT) {
return true;
}
}
@ -347,7 +347,7 @@ public class LoadQueryInfluencers implements Serializable {
final FetchProfile fetchProfile = sessionFactory.getFetchProfile( profile );
for ( Fetch fetch : fetchProfile.getFetches().values() ) {
// TODO: check that it's relevant to this persister??
if ( fetch.getStyle() == SUBSELECT ) {
if ( fetch.getMethod() == SUBSELECT ) {
return true;
}
}

View File

@ -8,6 +8,8 @@ package org.hibernate.internal;
import org.hibernate.HibernateException;
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.Fetch;
import org.hibernate.engine.profile.FetchProfile;
@ -44,24 +46,24 @@ public class FetchProfileHelper {
org.hibernate.mapping.FetchProfile mappingProfile) {
final String profileName = mappingProfile.getName();
final FetchProfile fetchProfile = new FetchProfile( profileName );
for ( org.hibernate.mapping.FetchProfile.Fetch mappingFetch : mappingProfile.getFetches() ) {
// resolve the persister owning the fetch
final EntityPersister owner = getEntityPersister( mappingMetamodel, fetchProfile, mappingFetch );
( (FetchProfileAffectee) owner ).registerAffectingFetchProfile( profileName, null );
( (FetchProfileAffectee) owner ).registerAffectingFetchProfile( profileName);
final Association association = new Association( owner, mappingFetch.getAssociation() );
final Fetch.Style fetchStyle = Fetch.Style.parse( mappingFetch.getStyle() );
final FetchStyle fetchStyle = Fetch.Style.forMethod( mappingFetch.getMethod() ).toFetchStyle();
final FetchTiming fetchTiming = FetchTiming.forType( mappingFetch.getType() );
// validate the specified association fetch
final ModelPart fetchablePart = owner.findByPath( association.getAssociationPath() );
validateFetchablePart( fetchablePart, profileName, association );
if ( fetchablePart instanceof FetchProfileAffectee ) {
( (FetchProfileAffectee) fetchablePart ).registerAffectingFetchProfile( profileName, fetchStyle );
( (FetchProfileAffectee) fetchablePart ).registerAffectingFetchProfile( profileName );
}
// then register the association with the FetchProfile
fetchProfile.addFetch( new Fetch( association, fetchStyle ) );
fetchProfile.addFetch( new Fetch( association, fetchStyle, fetchTiming ) );
}
return fetchProfile;
}
@ -86,7 +88,7 @@ public class FetchProfileHelper {
private static boolean isAssociation(ModelPart fetchablePart) {
return fetchablePart instanceof EntityValuedModelPart
|| fetchablePart instanceof PluralAttributeMapping;
|| fetchablePart instanceof PluralAttributeMapping;
}
private static EntityPersister getEntityPersister(
@ -95,7 +97,7 @@ public class FetchProfileHelper {
org.hibernate.mapping.FetchProfile.Fetch mappingFetch) {
final String entityName = mappingMetamodel.getImportedName( mappingFetch.getEntity() );
if ( entityName != null ) {
EntityPersister persister = mappingMetamodel.getEntityDescriptor( entityName );
final EntityPersister persister = mappingMetamodel.getEntityDescriptor( entityName );
if ( persister != null ) {
return persister;
}

View File

@ -88,7 +88,6 @@ import org.hibernate.sql.results.internal.StandardEntityGraphTraversalStateImpl;
import org.jboss.logging.Logger;
import static java.util.Collections.singletonList;
import static org.hibernate.engine.profile.Fetch.Style;
import static org.hibernate.query.results.ResultsHelper.attributeName;
/**
@ -878,8 +877,8 @@ public class LoaderSelectBuilder {
final org.hibernate.engine.profile.Fetch profileFetch =
enabledFetchProfile.getFetchByRole( fetchableRole );
if ( profileFetch != null ) {
fetchTiming = FetchTiming.IMMEDIATE;
joined = joined || profileFetch.getStyle() == Style.JOIN;
fetchTiming = profileFetch.getTiming();
joined = joined || profileFetch.getMethod() == FetchStyle.JOIN;
explicitFetch = shouldExplicitFetch( maximumFetchDepth, fetchable, creationState );
}
}

View File

@ -5,7 +5,14 @@
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.mapping;
import jakarta.persistence.FetchType;
import org.hibernate.annotations.FetchMode;
import java.util.LinkedHashSet;
import java.util.Locale;
import static jakarta.persistence.FetchType.EAGER;
/**
* A mapping model object representing a {@link org.hibernate.annotations.FetchProfile}.
@ -63,9 +70,19 @@ public class FetchProfile {
* @param entity The entity which contains the association to be fetched
* @param association The association to fetch
* @param style The style of fetch to apply
*
* @deprecated use {@link #addFetch(Fetch)}
*/
@Deprecated(forRemoval = true)
public void addFetch(String entity, String association, String style) {
fetches.add( new Fetch( entity, association, style ) );
addFetch( new Fetch( entity, association, style ) );
}
/**
* Adds a fetch to this profile.
*/
public void addFetch(Fetch fetch) {
fetches.add( fetch );
}
@Override
@ -89,17 +106,39 @@ public class FetchProfile {
/**
* Defines an individual association fetch within the given profile.
* An individual association fetch within the given profile.
*/
public static class Fetch {
private final String entity;
private final String association;
private final String style;
private final FetchMode method;
private final FetchType type;
public Fetch(String entity, String association, FetchMode method, FetchType type) {
this.entity = entity;
this.association = association;
this.method = method;
this.type = type;
}
/**
* @deprecated use {@link FetchProfile.Fetch#Fetch(String,String,FetchMode,FetchType)}
*/
@Deprecated(forRemoval = true)
public Fetch(String entity, String association, String style) {
this.entity = entity;
this.association = association;
this.style = style;
this.method = fetchMode( style );
this.type = EAGER;
}
private FetchMode fetchMode(String style) {
for ( FetchMode mode: FetchMode.values() ) {
if ( mode.name().equalsIgnoreCase( style ) ) {
return mode;
}
}
throw new IllegalArgumentException( "Unknown FetchMode: " + style );
}
public String getEntity() {
@ -110,8 +149,20 @@ public class FetchProfile {
return association;
}
/**
* @deprecated use {@link #getMethod()}
*/
@Deprecated(forRemoval = true)
public String getStyle() {
return style;
return method.toString().toLowerCase(Locale.ROOT);
}
public FetchMode getMethod() {
return method;
}
public FetchType getType() {
return type;
}
}
}

View File

@ -901,9 +901,9 @@ public class PluralAttributeMappingImpl
}
@Override
public void registerAffectingFetchProfile(String fetchProfileName, org.hibernate.engine.profile.Fetch.Style fetchStyle) {
public void registerAffectingFetchProfile(String fetchProfileName) {
if ( collectionDescriptor instanceof FetchProfileAffectee ) {
( (FetchProfileAffectee) collectionDescriptor ).registerAffectingFetchProfile( fetchProfileName, fetchStyle );
( (FetchProfileAffectee) collectionDescriptor ).registerAffectingFetchProfile( fetchProfileName);
}
}

View File

@ -30,7 +30,6 @@ import org.hibernate.engine.jdbc.mutation.ParameterUsage;
import org.hibernate.engine.jdbc.mutation.internal.MutationQueryOptions;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.engine.profile.Fetch;
import org.hibernate.engine.profile.internal.FetchProfileAffectee;
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
import org.hibernate.engine.spi.LoadQueryInfluencers;
@ -127,6 +126,7 @@ import java.sql.SQLException;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@ -232,7 +232,7 @@ public abstract class AbstractCollectionPersister
private CollectionElementLoaderByIndex collectionElementLoaderByIndex;
private PluralAttributeMapping attributeMapping;
private volatile Map<String, Fetch.Style> affectingFetchProfiles;
private volatile Set<String> affectingFetchProfiles;
@Deprecated(since = "6.0")
@ -1556,17 +1556,17 @@ public abstract class AbstractCollectionPersister
}
@Override
public void registerAffectingFetchProfile(String fetchProfileName, Fetch.Style fetchStyle) {
public void registerAffectingFetchProfile(String fetchProfileName) {
if ( affectingFetchProfiles == null ) {
affectingFetchProfiles = new HashMap<>();
affectingFetchProfiles = new HashSet<>();
}
affectingFetchProfiles.put( fetchProfileName, fetchStyle );
affectingFetchProfiles.add( fetchProfileName );
}
@Override
public boolean isAffectedByEnabledFetchProfiles(LoadQueryInfluencers influencers) {
if ( affectingFetchProfiles != null && influencers.hasEnabledFetchProfiles() ) {
for ( String profileName : affectingFetchProfiles.keySet() ) {
for ( String profileName : affectingFetchProfiles ) {
if ( influencers.isFetchProfileEnabled( profileName ) ) {
return true;
}

View File

@ -3518,11 +3518,6 @@ public abstract class AbstractEntityPersister
affectingFetchProfileNames.add( fetchProfileName );
}
@Override
public void registerAffectingFetchProfile(String fetchProfileName, org.hibernate.engine.profile.Fetch.Style fetchStyle) {
registerAffectingFetchProfile( fetchProfileName );
}
@Override
public boolean isAffectedByEntityGraph(LoadQueryInfluencers loadQueryInfluencers) {
final RootGraphImplementor<?> graph = loadQueryInfluencers.getEffectiveEntityGraph().getGraph();

View File

@ -38,6 +38,7 @@ import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.TimestampaddFunction;
import org.hibernate.dialect.function.TimestampdiffFunction;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.profile.FetchProfile;
import org.hibernate.engine.spi.LoadQueryInfluencers;
@ -7643,20 +7644,21 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final FetchProfile enabledFetchProfile = getCreationContext()
.getSessionFactory()
.getFetchProfile( enabledFetchProfileName );
final org.hibernate.engine.profile.Fetch profileFetch = enabledFetchProfile.getFetchByRole(
fetchableRole );
final org.hibernate.engine.profile.Fetch profileFetch =
enabledFetchProfile.getFetchByRole( fetchableRole );
if ( profileFetch != null ) {
fetchTiming = FetchTiming.IMMEDIATE;
joined = joined || profileFetch.getStyle() == org.hibernate.engine.profile.Fetch.Style.JOIN;
fetchTiming = profileFetch.getTiming();
joined = joined || profileFetch.getMethod() == FetchStyle.JOIN;
if ( shouldExplicitFetch( maxDepth, fetchable ) ) {
explicitFetch = true;
}
if ( currentBagRole != null && fetchable instanceof PluralAttributeMapping ) {
final CollectionClassification collectionClassification = ( (PluralAttributeMapping) fetchable ).getMappedType()
.getCollectionSemantics()
.getCollectionClassification();
final CollectionClassification collectionClassification =
( (PluralAttributeMapping) fetchable ).getMappedType()
.getCollectionSemantics()
.getCollectionClassification();
if ( collectionClassification == CollectionClassification.BAG ) {
// To avoid a MultipleBagFetchException due to fetch profiles in a circular model,
// we skip join fetching in case we encounter an existing bag role