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:
parent
c636c83d7e
commit
ba0221da36
|
@ -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 "";
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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() ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue