HHH-16666 allow fetch profiles to be defined using the @Fetch annotation
1. You may now declare an empty named @FetchProfile, and 2. add associations to it using @Fetch. Note that @Fetch becomes a repeatable annotation.
This commit is contained in:
parent
2daeadd449
commit
f2dbe7a9cb
|
@ -6,13 +6,18 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.annotations;
|
package org.hibernate.annotations;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.Repeatable;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the default fetching strategy for the annotated association.
|
* 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}.
|
||||||
* <p>
|
* <p>
|
||||||
* When this annotation is <em>not</em> explicitly specified, then:
|
* When this annotation is <em>not</em> explicitly specified, then:
|
||||||
* <ul>
|
* <ul>
|
||||||
|
@ -25,17 +30,29 @@ import java.lang.annotation.Target;
|
||||||
* <p>
|
* <p>
|
||||||
* The default fetching strategy specified by this annotation may be
|
* The default fetching strategy specified by this annotation may be
|
||||||
* overridden in a given {@linkplain FetchProfile fetch profile}.
|
* 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.
|
||||||
*
|
*
|
||||||
* @author Emmanuel Bernard
|
* @author Emmanuel Bernard
|
||||||
*
|
*
|
||||||
* @see FetchMode
|
* @see FetchMode
|
||||||
* @see FetchProfile
|
* @see FetchProfile
|
||||||
*/
|
*/
|
||||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
@Target({METHOD, FIELD})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RUNTIME)
|
||||||
|
@Repeatable(Fetches.class)
|
||||||
public @interface Fetch {
|
public @interface Fetch {
|
||||||
/**
|
/**
|
||||||
* The method that should be used to fetch the association.
|
* The method that should be used to fetch the association.
|
||||||
*/
|
*/
|
||||||
FetchMode value();
|
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 "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,14 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
/**
|
/**
|
||||||
* Defines a fetch profile, by specifying its {@link #name}, together
|
* Defines a fetch profile, by specifying its {@link #name}, together
|
||||||
* with a list of {@linkplain #fetchOverrides fetch strategy overrides}.
|
* with a list of {@linkplain #fetchOverrides fetch strategy overrides}.
|
||||||
|
* The definition of a single named fetch profile may be split over
|
||||||
|
* multiple {@link FetchProfile @FetchProfile} annotations which share
|
||||||
|
* the same {@link #name}.
|
||||||
|
* <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}.
|
||||||
* <p>
|
* <p>
|
||||||
* A named fetch profile must be explicitly enabled in a given session
|
* A named fetch profile must be explicitly enabled in a given session
|
||||||
* by calling {@link org.hibernate.Session#enableFetchProfile(String)}
|
* by calling {@link org.hibernate.Session#enableFetchProfile(String)}
|
||||||
|
@ -74,8 +82,12 @@ public @interface FetchProfile {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of association fetching strategy overrides.
|
* The list of association fetching strategy overrides.
|
||||||
|
* <p>
|
||||||
|
* Additional overrides may be specified by marking the
|
||||||
|
* fetched associations themselves with the {@link Fetch @Fetch}
|
||||||
|
* annotation.
|
||||||
*/
|
*/
|
||||||
FetchOverride[] fetchOverrides();
|
FetchOverride[] fetchOverrides() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides the fetching strategy pf a particular association
|
* Overrides the fetching strategy pf a particular association
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* 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 java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Gavin King
|
||||||
|
*/
|
||||||
|
@Target({METHOD, FIELD})
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface Fetches {
|
||||||
|
Fetch[] value();
|
||||||
|
}
|
|
@ -22,7 +22,9 @@ import org.hibernate.annotations.ConverterRegistration;
|
||||||
import org.hibernate.annotations.ConverterRegistrations;
|
import org.hibernate.annotations.ConverterRegistrations;
|
||||||
import org.hibernate.annotations.EmbeddableInstantiatorRegistration;
|
import org.hibernate.annotations.EmbeddableInstantiatorRegistration;
|
||||||
import org.hibernate.annotations.EmbeddableInstantiatorRegistrations;
|
import org.hibernate.annotations.EmbeddableInstantiatorRegistrations;
|
||||||
|
import org.hibernate.annotations.FetchMode;
|
||||||
import org.hibernate.annotations.FetchProfile;
|
import org.hibernate.annotations.FetchProfile;
|
||||||
|
import org.hibernate.annotations.FetchProfile.FetchOverride;
|
||||||
import org.hibernate.annotations.FetchProfiles;
|
import org.hibernate.annotations.FetchProfiles;
|
||||||
import org.hibernate.annotations.FilterDef;
|
import org.hibernate.annotations.FilterDef;
|
||||||
import org.hibernate.annotations.FilterDefs;
|
import org.hibernate.annotations.FilterDefs;
|
||||||
|
@ -93,6 +95,7 @@ import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
|
||||||
import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity;
|
import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity;
|
||||||
import static org.hibernate.boot.model.internal.InheritanceState.getSuperclassInheritanceState;
|
import static org.hibernate.boot.model.internal.InheritanceState.getSuperclassInheritanceState;
|
||||||
import static org.hibernate.internal.CoreLogging.messageLogger;
|
import static org.hibernate.internal.CoreLogging.messageLogger;
|
||||||
|
import static org.hibernate.mapping.MetadataSource.ANNOTATIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads annotations from Java classes and produces the Hibernate configuration-time metamodel,
|
* Reads annotations from Java classes and produces the Hibernate configuration-time metamodel,
|
||||||
|
@ -834,27 +837,45 @@ public final class AnnotationBinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void bindFetchProfiles(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
|
private static void bindFetchProfiles(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
|
||||||
final FetchProfile fetchProfileAnnotation = annotatedElement.getAnnotation( FetchProfile.class );
|
final FetchProfile fetchProfile = annotatedElement.getAnnotation( FetchProfile.class );
|
||||||
final FetchProfiles fetchProfileAnnotations = annotatedElement.getAnnotation( FetchProfiles.class );
|
final FetchProfiles fetchProfiles = annotatedElement.getAnnotation( FetchProfiles.class );
|
||||||
if ( fetchProfileAnnotation != null ) {
|
if ( fetchProfile != null ) {
|
||||||
bindFetchProfile( fetchProfileAnnotation, context );
|
bindFetchProfile( fetchProfile, context );
|
||||||
}
|
}
|
||||||
if ( fetchProfileAnnotations != null ) {
|
if ( fetchProfiles != null ) {
|
||||||
for ( FetchProfile profile : fetchProfileAnnotations.value() ) {
|
for ( FetchProfile profile : fetchProfiles.value() ) {
|
||||||
bindFetchProfile( profile, context );
|
bindFetchProfile( profile, context );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void bindFetchProfile(FetchProfile fetchProfileAnnotation, MetadataBuildingContext context) {
|
private static void bindFetchProfile(FetchProfile fetchProfile, MetadataBuildingContext context) {
|
||||||
for ( FetchProfile.FetchOverride fetch : fetchProfileAnnotation.fetchOverrides() ) {
|
final String name = fetchProfile.name();
|
||||||
org.hibernate.annotations.FetchMode mode = fetch.mode();
|
if ( reuseOrCreateFetchProfile( context, name ) ) {
|
||||||
if ( !mode.equals( org.hibernate.annotations.FetchMode.JOIN ) ) {
|
for ( FetchOverride fetch : fetchProfile.fetchOverrides() ) {
|
||||||
throw new MappingException( "Only FetchMode.JOIN is currently supported" );
|
if ( fetch.mode() != FetchMode.JOIN ) {
|
||||||
|
throw new MappingException( "Only 'FetchMode.JOIN' is currently supported" );
|
||||||
|
}
|
||||||
|
context.getMetadataCollector()
|
||||||
|
.addSecondPass( new FetchOverrideSecondPass( name, fetch, context ) );
|
||||||
}
|
}
|
||||||
context.getMetadataCollector().addSecondPass(
|
}
|
||||||
new VerifyFetchProfileReferenceSecondPass( fetchProfileAnnotation.name(), fetch, context )
|
// otherwise, it's a fetch profile defined in XML, and it overrides
|
||||||
);
|
// the annotations, so we simply ignore this annotation completely
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean reuseOrCreateFetchProfile(MetadataBuildingContext context, String name) {
|
||||||
|
// We tolerate multiple @FetchProfile annotations for same named profile
|
||||||
|
org.hibernate.mapping.FetchProfile existing = context.getMetadataCollector().getFetchProfile( name );
|
||||||
|
if ( existing == null ) {
|
||||||
|
// no existing profile, so create a new one
|
||||||
|
org.hibernate.mapping.FetchProfile profile =
|
||||||
|
new org.hibernate.mapping.FetchProfile( name, ANNOTATIONS );
|
||||||
|
context.getMetadataCollector().addFetchProfile( profile );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return existing.getSource() == ANNOTATIONS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.hibernate.annotations.CollectionType;
|
||||||
import org.hibernate.annotations.Columns;
|
import org.hibernate.annotations.Columns;
|
||||||
import org.hibernate.annotations.CompositeType;
|
import org.hibernate.annotations.CompositeType;
|
||||||
import org.hibernate.annotations.Fetch;
|
import org.hibernate.annotations.Fetch;
|
||||||
|
import org.hibernate.annotations.Fetches;
|
||||||
import org.hibernate.annotations.Filter;
|
import org.hibernate.annotations.Filter;
|
||||||
import org.hibernate.annotations.FilterJoinTable;
|
import org.hibernate.annotations.FilterJoinTable;
|
||||||
import org.hibernate.annotations.FilterJoinTables;
|
import org.hibernate.annotations.FilterJoinTables;
|
||||||
|
@ -64,11 +65,11 @@ import org.hibernate.annotations.Persister;
|
||||||
import org.hibernate.annotations.SQLDelete;
|
import org.hibernate.annotations.SQLDelete;
|
||||||
import org.hibernate.annotations.SQLDeleteAll;
|
import org.hibernate.annotations.SQLDeleteAll;
|
||||||
import org.hibernate.annotations.SQLInsert;
|
import org.hibernate.annotations.SQLInsert;
|
||||||
import org.hibernate.annotations.SQLSelect;
|
|
||||||
import org.hibernate.annotations.SQLUpdate;
|
|
||||||
import org.hibernate.annotations.SQLRestriction;
|
|
||||||
import org.hibernate.annotations.SQLJoinTableRestriction;
|
import org.hibernate.annotations.SQLJoinTableRestriction;
|
||||||
import org.hibernate.annotations.SQLOrder;
|
import org.hibernate.annotations.SQLOrder;
|
||||||
|
import org.hibernate.annotations.SQLRestriction;
|
||||||
|
import org.hibernate.annotations.SQLSelect;
|
||||||
|
import org.hibernate.annotations.SQLUpdate;
|
||||||
import org.hibernate.annotations.SortComparator;
|
import org.hibernate.annotations.SortComparator;
|
||||||
import org.hibernate.annotations.SortNatural;
|
import org.hibernate.annotations.SortNatural;
|
||||||
import org.hibernate.annotations.Synchronize;
|
import org.hibernate.annotations.Synchronize;
|
||||||
|
@ -1454,17 +1455,47 @@ public abstract class CollectionBinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFetch() {
|
private void handleFetch() {
|
||||||
if ( property.isAnnotationPresent( Fetch.class ) ) {
|
if ( !handleHibernateFetchMode() ) {
|
||||||
// Hibernate @Fetch annotation takes precedence
|
// Hibernate @Fetch annotation takes precedence
|
||||||
handleHibernateFetchMode();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
collection.setFetchMode( getFetchMode( getJpaFetchType() ) );
|
collection.setFetchMode( getFetchMode( getJpaFetchType() ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleHibernateFetchMode() {
|
private boolean handleHibernateFetchMode() {
|
||||||
switch ( property.getAnnotation( Fetch.class ).value() ) {
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHibernateFetchMode(org.hibernate.annotations.FetchMode fetchMode) {
|
||||||
|
switch ( fetchMode ) {
|
||||||
case JOIN:
|
case JOIN:
|
||||||
collection.setFetchMode( FetchMode.JOIN );
|
collection.setFetchMode( FetchMode.JOIN );
|
||||||
collection.setLazy( false );
|
collection.setLazy( false );
|
||||||
|
|
|
@ -5,27 +5,28 @@
|
||||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
* 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;
|
package org.hibernate.boot.model.internal;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.annotations.FetchProfile;
|
import org.hibernate.annotations.FetchProfile.FetchOverride;
|
||||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||||
import org.hibernate.boot.spi.SecondPass;
|
import org.hibernate.boot.spi.SecondPass;
|
||||||
import org.hibernate.mapping.MetadataSource;
|
import org.hibernate.mapping.FetchProfile;
|
||||||
import org.hibernate.mapping.PersistentClass;
|
import org.hibernate.mapping.PersistentClass;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Hardy Ferentschik
|
* @author Hardy Ferentschik
|
||||||
*/
|
*/
|
||||||
public class VerifyFetchProfileReferenceSecondPass implements SecondPass {
|
public class FetchOverrideSecondPass implements SecondPass {
|
||||||
private final String fetchProfileName;
|
private final String fetchProfileName;
|
||||||
private final FetchProfile.FetchOverride fetch;
|
private final FetchOverride fetch;
|
||||||
private final MetadataBuildingContext buildingContext;
|
private final MetadataBuildingContext buildingContext;
|
||||||
|
|
||||||
public VerifyFetchProfileReferenceSecondPass(
|
public FetchOverrideSecondPass(
|
||||||
String fetchProfileName,
|
String fetchProfileName,
|
||||||
FetchProfile.FetchOverride fetch,
|
FetchOverride fetch,
|
||||||
MetadataBuildingContext buildingContext) {
|
MetadataBuildingContext buildingContext) {
|
||||||
this.fetchProfileName = fetchProfileName;
|
this.fetchProfileName = fetchProfileName;
|
||||||
this.fetch = fetch;
|
this.fetch = fetch;
|
||||||
|
@ -34,21 +35,13 @@ public class VerifyFetchProfileReferenceSecondPass implements SecondPass {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
|
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
|
||||||
org.hibernate.mapping.FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetchProfileName );
|
|
||||||
if ( profile != null ) {
|
|
||||||
if ( profile.getSource() != MetadataSource.ANNOTATIONS ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
profile = new org.hibernate.mapping.FetchProfile( fetchProfileName, MetadataSource.ANNOTATIONS );
|
|
||||||
buildingContext.getMetadataCollector().addFetchProfile( profile );
|
|
||||||
}
|
|
||||||
|
|
||||||
PersistentClass clazz = buildingContext.getMetadataCollector().getEntityBinding( fetch.entity().getName() );
|
|
||||||
// throws MappingException in case the property does not exist
|
// throws MappingException in case the property does not exist
|
||||||
clazz.getProperty( fetch.association() );
|
buildingContext.getMetadataCollector()
|
||||||
|
.getEntityBinding( fetch.entity().getName() )
|
||||||
|
.getProperty( fetch.association() );
|
||||||
|
|
||||||
|
final FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetchProfileName );
|
||||||
|
// we already know that the FetchProfile exists and is good to use
|
||||||
profile.addFetch(
|
profile.addFetch(
|
||||||
fetch.entity().getName(),
|
fetch.entity().getName(),
|
||||||
fetch.association(),
|
fetch.association(),
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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 java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hibernate.AnnotationException;
|
||||||
|
import org.hibernate.MappingException;
|
||||||
|
import org.hibernate.annotations.Fetch;
|
||||||
|
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||||
|
import org.hibernate.boot.spi.SecondPass;
|
||||||
|
import org.hibernate.mapping.FetchProfile;
|
||||||
|
import org.hibernate.mapping.PersistentClass;
|
||||||
|
|
||||||
|
import static org.hibernate.internal.util.StringHelper.qualify;
|
||||||
|
import static org.hibernate.mapping.MetadataSource.ANNOTATIONS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Gavin King
|
||||||
|
*/
|
||||||
|
public class FetchSecondPass implements SecondPass {
|
||||||
|
private final Fetch fetch;
|
||||||
|
private final PropertyHolder propertyHolder;
|
||||||
|
private final String propertyName;
|
||||||
|
private final MetadataBuildingContext buildingContext;
|
||||||
|
|
||||||
|
public FetchSecondPass(Fetch fetch, PropertyHolder propertyHolder, String propertyName, MetadataBuildingContext buildingContext) {
|
||||||
|
this.fetch = fetch;
|
||||||
|
this.propertyHolder = propertyHolder;
|
||||||
|
this.propertyName = propertyName;
|
||||||
|
this.buildingContext = buildingContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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() + "'" );
|
||||||
|
}
|
||||||
|
else if ( profile.getSource() == ANNOTATIONS ) {
|
||||||
|
profile.addFetch(
|
||||||
|
propertyHolder.getEntityName(),
|
||||||
|
propertyName,
|
||||||
|
fetch.value().toString().toLowerCase(Locale.ROOT)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// otherwise, it's a fetch profile defined in XML, and it overrides
|
||||||
|
// the annotations, so we simply ignore this annotation completely
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import org.hibernate.FetchMode;
|
||||||
import org.hibernate.annotations.Cascade;
|
import org.hibernate.annotations.Cascade;
|
||||||
import org.hibernate.annotations.Columns;
|
import org.hibernate.annotations.Columns;
|
||||||
import org.hibernate.annotations.Fetch;
|
import org.hibernate.annotations.Fetch;
|
||||||
|
import org.hibernate.annotations.Fetches;
|
||||||
import org.hibernate.annotations.LazyToOne;
|
import org.hibernate.annotations.LazyToOne;
|
||||||
import org.hibernate.annotations.LazyToOneOption;
|
import org.hibernate.annotations.LazyToOneOption;
|
||||||
import org.hibernate.annotations.NotFound;
|
import org.hibernate.annotations.NotFound;
|
||||||
|
@ -307,15 +308,12 @@ public class ToOneBinder {
|
||||||
PropertyData inferredData,
|
PropertyData inferredData,
|
||||||
PropertyHolder propertyHolder) {
|
PropertyHolder propertyHolder) {
|
||||||
handleLazy( toOne, property, inferredData, propertyHolder );
|
handleLazy( toOne, property, inferredData, propertyHolder );
|
||||||
handleFetch( toOne, property );
|
handleFetch( toOne, property, propertyHolder, inferredData );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void handleFetch(ToOne toOne, XProperty property) {
|
private static void handleFetch(ToOne toOne, XProperty property, PropertyHolder propertyHolder, PropertyData inferredData) {
|
||||||
if ( property.isAnnotationPresent( Fetch.class ) ) {
|
if ( !handleHibernateFetchMode( toOne, property, propertyHolder, inferredData ) ) {
|
||||||
// Hibernate @Fetch annotation takes precedence
|
// Hibernate @Fetch annotation takes precedence
|
||||||
handleHibernateFetchMode( toOne, property );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
toOne.setFetchMode( getFetchMode( getJpaFetchType( property ) ) );
|
toOne.setFetchMode( getFetchMode( getJpaFetchType( property ) ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,8 +331,46 @@ public class ToOneBinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void handleHibernateFetchMode(ToOne toOne, XProperty property) {
|
private static boolean handleHibernateFetchMode(
|
||||||
switch ( property.getAnnotation( Fetch.class ).value() ) {
|
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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setHibernateFetchMode(ToOne toOne, XProperty property, org.hibernate.annotations.FetchMode fetchMode) {
|
||||||
|
switch ( fetchMode ) {
|
||||||
case JOIN:
|
case JOIN:
|
||||||
toOne.setFetchMode( FetchMode.JOIN );
|
toOne.setFetchMode( FetchMode.JOIN );
|
||||||
toOne.setLazy( false );
|
toOne.setLazy( false );
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package org.hibernate.orm.test.annotations.fetchprofile;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.annotations.Fetch;
|
||||||
|
import org.hibernate.annotations.FetchProfile;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static jakarta.persistence.FetchType.LAZY;
|
||||||
|
import static org.hibernate.annotations.FetchMode.JOIN;
|
||||||
|
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})
|
||||||
|
@FetchProfile(name = NewFetchTest.NEW_PROFILE)
|
||||||
|
@FetchProfile(name = NewFetchTest.OLD_PROFILE,
|
||||||
|
fetchOverrides = @FetchProfile.FetchOverride(entity = NewFetchTest.E.class, association = "f", mode = JOIN))
|
||||||
|
public class NewFetchTest {
|
||||||
|
|
||||||
|
public static final String NEW_PROFILE = "new-profile";
|
||||||
|
public static final String OLD_PROFILE = "old-profile";
|
||||||
|
|
||||||
|
@Test void test(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( s-> {
|
||||||
|
G g = new G();
|
||||||
|
F f = new F();
|
||||||
|
E e = new E();
|
||||||
|
f.g = g;
|
||||||
|
e.f = f;
|
||||||
|
s.persist(g);
|
||||||
|
s.persist(f);
|
||||||
|
s.persist(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
F f = scope.fromSession( s -> s.find(F.class, 1));
|
||||||
|
assertFalse( Hibernate.isInitialized( f.g ) );
|
||||||
|
assertFalse( Hibernate.isInitialized( f.es ) );
|
||||||
|
F ff = scope.fromSession( s -> {
|
||||||
|
s.enableFetchProfile(NEW_PROFILE);
|
||||||
|
return s.find(F.class, 1);
|
||||||
|
} );
|
||||||
|
assertTrue( Hibernate.isInitialized( ff.g ) );
|
||||||
|
assertTrue( Hibernate.isInitialized( ff.es ) );
|
||||||
|
|
||||||
|
E e = scope.fromSession( s -> s.find(E.class, 1));
|
||||||
|
assertFalse( Hibernate.isInitialized( e.f ) );
|
||||||
|
E ee = scope.fromSession( s -> {
|
||||||
|
s.enableFetchProfile(OLD_PROFILE);
|
||||||
|
return s.find(E.class, 1);
|
||||||
|
} );
|
||||||
|
assertTrue( Hibernate.isInitialized( ee.f ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "E")
|
||||||
|
static class E {
|
||||||
|
@Id @GeneratedValue
|
||||||
|
Long id;
|
||||||
|
@ManyToOne(fetch = LAZY)
|
||||||
|
F f;
|
||||||
|
}
|
||||||
|
@Entity(name = "F")
|
||||||
|
static class F {
|
||||||
|
@Id @GeneratedValue
|
||||||
|
Long id;
|
||||||
|
@ManyToOne(fetch = LAZY)
|
||||||
|
@Fetch(value = JOIN, profile = NEW_PROFILE)
|
||||||
|
G g;
|
||||||
|
@OneToMany(mappedBy = "f")
|
||||||
|
@Fetch(value = JOIN, profile = NEW_PROFILE)
|
||||||
|
Set<E> es;
|
||||||
|
}
|
||||||
|
@Entity(name = "G")
|
||||||
|
static class G {
|
||||||
|
@Id @GeneratedValue
|
||||||
|
Long id;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue