HHH-16666 introduce @FetchProfileOverride instead of reusing @Fetch

There are differences in the implied timing, so this is more consistent
This commit is contained in:
Gavin 2023-05-23 10:43:03 +02:00 committed by Gavin King
parent c636c83d7e
commit ba0221da36
8 changed files with 144 additions and 115 deletions

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.annotations;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@ -15,9 +14,7 @@ import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Specifies the default fetching strategy for the annotated association,
* or, if {@link #profile} is specified, the fetching strategy for the
* annotated association in the named {@linkplain FetchProfile fetch profile}.
* Specifies the default fetching method for the annotated association.
* <p>
* When this annotation is <em>not</em> explicitly specified, then:
* <ul>
@ -28,12 +25,11 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* {@linkplain jakarta.persistence.FetchType#EAGER eager} fetching.
* </ul>
* <p>
* The default fetching strategy specified by this annotation may be
* The default fetching method specified by this annotation may be
* overridden in a given {@linkplain FetchProfile fetch profile}.
* <p>
* If {@link #profile} is specified, then the given profile name must
* match the name of an existing fetch profile declared using the
* {@link FetchProfile#name @FetchProfile} annotation.
* Note that join fetching is incompatible with lazy fetching, and so
* {@code @Fetch(JOIN)} implies {@code fetch=EAGER}.
*
* @author Emmanuel Bernard
*
@ -42,17 +38,9 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Repeatable(Fetches.class)
public @interface Fetch {
/**
* The method that should be used to fetch the association.
*/
FetchMode value();
/**
* The name of the {@link FetchProfile fetch profile} in
* which this fetch mode should be applied. By default,
* it is applied the default fetch profile.
*/
String profile() default "";
}

View File

@ -27,8 +27,8 @@ import static org.hibernate.annotations.FetchMode.JOIN;
* <p>
* Additional fetch strategy overrides may be added to a named fetch
* profile by annotating the fetched associations themselves with the
* {@link Fetch @Fetch} annotation, specifying the
* {@linkplain Fetch#profile() name of the fetch profile}.
* {@link FetchProfileOverride @Fetch} annotation, specifying the
* {@linkplain FetchProfileOverride#profile name of the fetch profile}.
* <p>
* A named fetch profile must be explicitly enabled in a given session
* by calling {@link org.hibernate.Session#enableFetchProfile(String)}
@ -65,11 +65,12 @@ import static org.hibernate.annotations.FetchMode.JOIN;
* the determination of the fetch graph.
* <p>
* A JPA {@link jakarta.persistence.EntityGraph} may be constructed in
* Java code at runtime. But this amounts to separate, albeit extremely
* Java code at runtime. But this amounts to a separate, albeit extremely
* limited, query facility that competes with JPA's own {@linkplain
* jakarta.persistence.criteria.CriteriaBuilder criteria queries}.
* There's no such capability for fetch profiles.
*
* @see FetchProfileOverride
* @see org.hibernate.Session#enableFetchProfile(String)
* @see org.hibernate.SessionFactory#containsFetchProfileDefinition(String)
*
@ -94,11 +95,17 @@ public @interface FetchProfile {
FetchOverride[] fetchOverrides() default {};
/**
* 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.
* Overrides the fetching strategy for a particular association in
* the named fetch profile being defined. A "strategy" is a fetching
* {@linkplain #mode method}, together with the {@linkplain #fetch
* timing}. If {@link #mode} and {@link #fetch} are both unspecified,
* the strategy defaults to {@linkplain FetchType#EAGER eager}
* {@linkplain FetchMode#JOIN join} fetching.
* <p>
* Additional fetch strategy overrides may be specified using the
* {@link FetchProfileOverride @FetchProfileOverride} annotation.
*
* @see FetchProfileOverride
*/
@Target({ TYPE, PACKAGE })
@Retention(RUNTIME)

View File

@ -0,0 +1,61 @@
/*
* 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.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.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.hibernate.annotations.FetchMode.JOIN;
/**
* Overrides the fetching strategy for the annotated association
* in a certain named {@linkplain FetchProfile fetch profile}. A
* "strategy" is a fetching {@linkplain #mode method}, together
* with the {@linkplain #fetch timing}. If {@link #mode} and
* {@link #fetch} are both unspecified, the strategy defaults to
* {@linkplain FetchType#EAGER eager} {@linkplain FetchMode#JOIN join}
* fetching.
* <p>
* The specified {@linkplain #profile profile name} must match the
* name of an existing fetch profile declared using the
* {@link FetchProfile#name @FetchProfile} annotation.
*
* @author Gavin King
*
* @see FetchMode
* @see FetchProfile
* @see FetchProfile.FetchOverride
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Repeatable(FetchProfileOverrides.class)
public @interface FetchProfileOverride {
/**
* The method that should be used to fetch the association
* in the named fetch profile.
*/
FetchMode mode() default JOIN;
/**
* The timing of association fetching in the named fetch
* profile.
*/
FetchType fetch() default EAGER;
/**
* The name of the {@link FetchProfile fetch profile} in
* which this fetch mode should be applied.
*/
String profile();
}

View File

@ -14,10 +14,12 @@ import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A group of {@link FetchProfileOverride}s.
*
* @author Gavin King
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Fetches {
Fetch[] value();
public @interface FetchProfileOverrides {
FetchProfileOverride[] value();
}

View File

@ -33,7 +33,8 @@ import org.hibernate.annotations.CollectionType;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.CompositeType;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.Fetches;
import org.hibernate.annotations.FetchProfileOverride;
import org.hibernate.annotations.FetchProfileOverrides;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterJoinTable;
import org.hibernate.annotations.FilterJoinTables;
@ -1452,49 +1453,31 @@ public abstract class CollectionBinder {
private void defineFetchingStrategy() {
handleLazy();
handleFetch();
handleFetchProfileOverrides();
}
private void handleFetchProfileOverrides() {
if ( property.isAnnotationPresent( FetchProfileOverride.class ) ) {
final FetchProfileOverride fetch = property.getAnnotation( FetchProfileOverride.class );
buildingContext.getMetadataCollector()
.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 ) );
}
}
}
private void handleFetch() {
if ( !handleHibernateFetchMode() ) {
// Hibernate @Fetch annotation takes precedence
collection.setFetchMode( getFetchMode( getJpaFetchType() ) );
}
}
private boolean handleHibernateFetchMode() {
if ( property.isAnnotationPresent( Fetch.class ) ) {
final Fetch fetch = property.getAnnotation( Fetch.class );
if ( fetch.profile().isEmpty() ) {
setHibernateFetchMode( fetch.value() );
return true;
}
else {
buildingContext.getMetadataCollector()
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );
return false;
}
}
else if ( property.isAnnotationPresent( Fetches.class ) ) {
boolean result = false;
for ( Fetch fetch: property.getAnnotation( Fetches.class ).value() ) {
if ( fetch.profile().isEmpty() ) {
if ( result ) {
throw new AnnotationException( "Collection '" + safeCollectionRole()
+ "' had multiple '@Fetch' annotations which did not specify a named fetch 'profile'"
+ " (only one annotation may be specified for the default profile)" );
}
setHibernateFetchMode( fetch.value() );
result = true;
}
else {
buildingContext.getMetadataCollector()
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );
}
}
return result;
// Hibernate @Fetch annotation takes precedence
setHibernateFetchMode( property.getAnnotation( Fetch.class ).value() );
}
else {
return false;
collection.setFetchMode( getFetchMode( getJpaFetchType() ) );
}
}

View File

@ -6,12 +6,11 @@
*/
package org.hibernate.boot.model.internal;
import java.util.Locale;
import java.util.Map;
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchProfileOverride;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.SecondPass;
import org.hibernate.mapping.FetchProfile;
@ -24,12 +23,16 @@ import static org.hibernate.mapping.MetadataSource.ANNOTATIONS;
* @author Gavin King
*/
public class FetchSecondPass implements SecondPass {
private final Fetch fetch;
private final FetchProfileOverride fetch;
private final PropertyHolder propertyHolder;
private final String propertyName;
private final MetadataBuildingContext buildingContext;
public FetchSecondPass(Fetch fetch, PropertyHolder propertyHolder, String propertyName, MetadataBuildingContext buildingContext) {
public FetchSecondPass(
FetchProfileOverride fetch,
PropertyHolder propertyHolder,
String propertyName,
MetadataBuildingContext buildingContext) {
this.fetch = fetch;
this.propertyHolder = propertyHolder;
this.propertyName = propertyName;
@ -53,9 +56,12 @@ public class FetchSecondPass implements SecondPass {
}
else if ( profile.getSource() == ANNOTATIONS ) {
profile.addFetch(
propertyHolder.getEntityName(),
propertyName,
fetch.value().toString().toLowerCase(Locale.ROOT)
new FetchProfile.Fetch(
propertyHolder.getEntityName(),
propertyName,
fetch.mode(),
fetch.fetch()
)
);
}
// otherwise, it's a fetch profile defined in XML, and it overrides

View File

@ -15,7 +15,8 @@ import org.hibernate.FetchMode;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.Fetches;
import org.hibernate.annotations.FetchProfileOverride;
import org.hibernate.annotations.FetchProfileOverrides;
import org.hibernate.annotations.LazyToOne;
import org.hibernate.annotations.LazyToOneOption;
import org.hibernate.annotations.NotFound;
@ -308,14 +309,8 @@ public class ToOneBinder {
PropertyData inferredData,
PropertyHolder propertyHolder) {
handleLazy( toOne, property, inferredData, propertyHolder );
handleFetch( toOne, property, propertyHolder, inferredData );
}
private static void handleFetch(ToOne toOne, XProperty property, PropertyHolder propertyHolder, PropertyData inferredData) {
if ( !handleHibernateFetchMode( toOne, property, propertyHolder, inferredData ) ) {
// Hibernate @Fetch annotation takes precedence
toOne.setFetchMode( getFetchMode( getJpaFetchType( property ) ) );
}
handleFetch( toOne, property );
handleFetchProfileOverrides( toOne, property, propertyHolder, inferredData );
}
private static void handleLazy(ToOne toOne, XProperty property, PropertyData inferredData, PropertyHolder propertyHolder) {
@ -331,46 +326,33 @@ public class ToOneBinder {
}
}
private static boolean handleHibernateFetchMode(
private static void handleFetchProfileOverrides(
ToOne toOne,
XProperty property,
PropertyHolder propertyHolder,
PropertyData inferredData) {
if ( property.isAnnotationPresent( Fetch.class ) ) {
final Fetch fetch = property.getAnnotation( Fetch.class );
if ( fetch.profile().isEmpty() ) {
setHibernateFetchMode( toOne, property, fetch.value() );
return true;
}
else {
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 ) );
}
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 ) );
return false;
}
}
else if ( property.isAnnotationPresent( Fetches.class ) ) {
boolean result = false;
for ( Fetch fetch: property.getAnnotation( Fetches.class ).value() ) {
if ( fetch.profile().isEmpty() ) {
if ( result ) {
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
+ "' had multiple '@Fetch' annotations which did not specify a named fetch 'profile'"
+ " (only one annotation may be specified for the default profile)" );
}
setHibernateFetchMode( toOne, property, fetch.value() );
result = true;
}
else {
final MetadataBuildingContext context = toOne.getBuildingContext();
context.getMetadataCollector()
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, inferredData.getPropertyName(), context ) );
}
}
return result;
}
private static void handleFetch(ToOne toOne, XProperty property) {
if ( property.isAnnotationPresent( Fetch.class ) ) {
// Hibernate @Fetch annotation takes precedence
setHibernateFetchMode( toOne, property, property.getAnnotation( Fetch.class ).value() );
}
else {
return false;
toOne.setFetchMode( getFetchMode( getJpaFetchType( property ) ) );
}
}

View File

@ -5,8 +5,8 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchProfile;
import org.hibernate.annotations.FetchProfileOverride;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@ -172,13 +172,13 @@ public class NewFetchTest {
@Id @GeneratedValue
Long id;
@ManyToOne(fetch = LAZY)
@Fetch(value = JOIN, profile = NEW_PROFILE)
@FetchProfileOverride(mode = JOIN, profile = NEW_PROFILE)
G g;
@OneToMany(mappedBy = "f")
@Fetch(value = JOIN, profile = NEW_PROFILE)
@Fetch(value = SUBSELECT, profile = SUBSELECT_PROFILE)
@Fetch(value = SELECT, profile = SELECT_PROFILE)
@Fetch(value = JOIN, profile = JOIN_PROFILE)
@FetchProfileOverride(mode = JOIN, profile = NEW_PROFILE)
@FetchProfileOverride(mode = SUBSELECT, profile = SUBSELECT_PROFILE)
@FetchProfileOverride(mode = SELECT, profile = SELECT_PROFILE)
@FetchProfileOverride(mode = JOIN, profile = JOIN_PROFILE)
Set<E> es;
}
@Entity(name = "G")