Skip to content

Commit f2dbe7a

Browse files
committed
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.
1 parent 2daeadd commit f2dbe7a

File tree

9 files changed

+341
-57
lines changed

9 files changed

+341
-57
lines changed

hibernate-core/src/main/java/org/hibernate/annotations/Fetch.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
*/
77
package org.hibernate.annotations;
88

9-
import java.lang.annotation.ElementType;
9+
import java.lang.annotation.Repeatable;
1010
import java.lang.annotation.Retention;
11-
import java.lang.annotation.RetentionPolicy;
1211
import java.lang.annotation.Target;
1312

13+
import static java.lang.annotation.ElementType.FIELD;
14+
import static java.lang.annotation.ElementType.METHOD;
15+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
16+
1417
/**
15-
* Specifies the default fetching strategy for the annotated association.
18+
* Specifies the default fetching strategy for the annotated association,
19+
* or, if {@link #profile} is specified, the fetching strategy for the
20+
* annotated association in the named {@linkplain FetchProfile fetch profile}.
1621
* <p>
1722
* When this annotation is <em>not</em> explicitly specified, then:
1823
* <ul>
@@ -25,17 +30,29 @@
2530
* <p>
2631
* The default fetching strategy specified by this annotation may be
2732
* overridden in a given {@linkplain FetchProfile fetch profile}.
33+
* <p>
34+
* If {@link #profile} is specified, then the given profile name must
35+
* match the name of an existing fetch profile declared using the
36+
* {@link FetchProfile#name @FetchProfile} annotation.
2837
*
2938
* @author Emmanuel Bernard
3039
*
3140
* @see FetchMode
3241
* @see FetchProfile
3342
*/
34-
@Target({ElementType.METHOD, ElementType.FIELD})
35-
@Retention(RetentionPolicy.RUNTIME)
43+
@Target({METHOD, FIELD})
44+
@Retention(RUNTIME)
45+
@Repeatable(Fetches.class)
3646
public @interface Fetch {
3747
/**
3848
* The method that should be used to fetch the association.
3949
*/
4050
FetchMode value();
51+
52+
/**
53+
* The name of the {@link FetchProfile fetch profile} in
54+
* which this fetch mode should be applied. By default,
55+
* it is applied the default fetch profile.
56+
*/
57+
String profile() default "";
4158
}

hibernate-core/src/main/java/org/hibernate/annotations/FetchProfile.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
/**
1818
* Defines a fetch profile, by specifying its {@link #name}, together
1919
* with a list of {@linkplain #fetchOverrides fetch strategy overrides}.
20+
* The definition of a single named fetch profile may be split over
21+
* multiple {@link FetchProfile @FetchProfile} annotations which share
22+
* the same {@link #name}.
23+
* <p>
24+
* Additional fetch strategy overrides may be added to a named fetch
25+
* profile by annotating the fetched associations themselves with the
26+
* {@link Fetch @Fetch} annotation, specifying the
27+
* {@linkplain Fetch#profile() name of the fetch profile}.
2028
* <p>
2129
* A named fetch profile must be explicitly enabled in a given session
2230
* by calling {@link org.hibernate.Session#enableFetchProfile(String)}
@@ -74,8 +82,12 @@
7482

7583
/**
7684
* The list of association fetching strategy overrides.
85+
* <p>
86+
* Additional overrides may be specified by marking the
87+
* fetched associations themselves with the {@link Fetch @Fetch}
88+
* annotation.
7789
*/
78-
FetchOverride[] fetchOverrides();
90+
FetchOverride[] fetchOverrides() default {};
7991

8092
/**
8193
* Overrides the fetching strategy pf a particular association
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.annotations;
8+
9+
import java.lang.annotation.Retention;
10+
import java.lang.annotation.Target;
11+
12+
import static java.lang.annotation.ElementType.FIELD;
13+
import static java.lang.annotation.ElementType.METHOD;
14+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
15+
16+
/**
17+
* @author Gavin King
18+
*/
19+
@Target({METHOD, FIELD})
20+
@Retention(RUNTIME)
21+
public @interface Fetches {
22+
Fetch[] value();
23+
}

hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import org.hibernate.annotations.ConverterRegistrations;
2323
import org.hibernate.annotations.EmbeddableInstantiatorRegistration;
2424
import org.hibernate.annotations.EmbeddableInstantiatorRegistrations;
25+
import org.hibernate.annotations.FetchMode;
2526
import org.hibernate.annotations.FetchProfile;
27+
import org.hibernate.annotations.FetchProfile.FetchOverride;
2628
import org.hibernate.annotations.FetchProfiles;
2729
import org.hibernate.annotations.FilterDef;
2830
import org.hibernate.annotations.FilterDefs;
@@ -93,6 +95,7 @@
9395
import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity;
9496
import static org.hibernate.boot.model.internal.InheritanceState.getSuperclassInheritanceState;
9597
import static org.hibernate.internal.CoreLogging.messageLogger;
98+
import static org.hibernate.mapping.MetadataSource.ANNOTATIONS;
9699

97100
/**
98101
* Reads annotations from Java classes and produces the Hibernate configuration-time metamodel,
@@ -834,27 +837,45 @@ public static void bindFetchProfilesForPackage(ClassLoaderService cls, String pa
834837
}
835838

836839
private static void bindFetchProfiles(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
837-
final FetchProfile fetchProfileAnnotation = annotatedElement.getAnnotation( FetchProfile.class );
838-
final FetchProfiles fetchProfileAnnotations = annotatedElement.getAnnotation( FetchProfiles.class );
839-
if ( fetchProfileAnnotation != null ) {
840-
bindFetchProfile( fetchProfileAnnotation, context );
840+
final FetchProfile fetchProfile = annotatedElement.getAnnotation( FetchProfile.class );
841+
final FetchProfiles fetchProfiles = annotatedElement.getAnnotation( FetchProfiles.class );
842+
if ( fetchProfile != null ) {
843+
bindFetchProfile( fetchProfile, context );
841844
}
842-
if ( fetchProfileAnnotations != null ) {
843-
for ( FetchProfile profile : fetchProfileAnnotations.value() ) {
845+
if ( fetchProfiles != null ) {
846+
for ( FetchProfile profile : fetchProfiles.value() ) {
844847
bindFetchProfile( profile, context );
845848
}
846849
}
847850
}
848851

849-
private static void bindFetchProfile(FetchProfile fetchProfileAnnotation, MetadataBuildingContext context) {
850-
for ( FetchProfile.FetchOverride fetch : fetchProfileAnnotation.fetchOverrides() ) {
851-
org.hibernate.annotations.FetchMode mode = fetch.mode();
852-
if ( !mode.equals( org.hibernate.annotations.FetchMode.JOIN ) ) {
853-
throw new MappingException( "Only FetchMode.JOIN is currently supported" );
852+
private static void bindFetchProfile(FetchProfile fetchProfile, MetadataBuildingContext context) {
853+
final String name = fetchProfile.name();
854+
if ( reuseOrCreateFetchProfile( context, name ) ) {
855+
for ( FetchOverride fetch : fetchProfile.fetchOverrides() ) {
856+
if ( fetch.mode() != FetchMode.JOIN ) {
857+
throw new MappingException( "Only 'FetchMode.JOIN' is currently supported" );
858+
}
859+
context.getMetadataCollector()
860+
.addSecondPass( new FetchOverrideSecondPass( name, fetch, context ) );
854861
}
855-
context.getMetadataCollector().addSecondPass(
856-
new VerifyFetchProfileReferenceSecondPass( fetchProfileAnnotation.name(), fetch, context )
857-
);
862+
}
863+
// otherwise, it's a fetch profile defined in XML, and it overrides
864+
// the annotations, so we simply ignore this annotation completely
865+
}
866+
867+
private static boolean reuseOrCreateFetchProfile(MetadataBuildingContext context, String name) {
868+
// We tolerate multiple @FetchProfile annotations for same named profile
869+
org.hibernate.mapping.FetchProfile existing = context.getMetadataCollector().getFetchProfile( name );
870+
if ( existing == null ) {
871+
// no existing profile, so create a new one
872+
org.hibernate.mapping.FetchProfile profile =
873+
new org.hibernate.mapping.FetchProfile( name, ANNOTATIONS );
874+
context.getMetadataCollector().addFetchProfile( profile );
875+
return true;
876+
}
877+
else {
878+
return existing.getSource() == ANNOTATIONS;
858879
}
859880
}
860881

hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.hibernate.annotations.Columns;
3434
import org.hibernate.annotations.CompositeType;
3535
import org.hibernate.annotations.Fetch;
36+
import org.hibernate.annotations.Fetches;
3637
import org.hibernate.annotations.Filter;
3738
import org.hibernate.annotations.FilterJoinTable;
3839
import org.hibernate.annotations.FilterJoinTables;
@@ -64,11 +65,11 @@
6465
import org.hibernate.annotations.SQLDelete;
6566
import org.hibernate.annotations.SQLDeleteAll;
6667
import org.hibernate.annotations.SQLInsert;
67-
import org.hibernate.annotations.SQLSelect;
68-
import org.hibernate.annotations.SQLUpdate;
69-
import org.hibernate.annotations.SQLRestriction;
7068
import org.hibernate.annotations.SQLJoinTableRestriction;
7169
import org.hibernate.annotations.SQLOrder;
70+
import org.hibernate.annotations.SQLRestriction;
71+
import org.hibernate.annotations.SQLSelect;
72+
import org.hibernate.annotations.SQLUpdate;
7273
import org.hibernate.annotations.SortComparator;
7374
import org.hibernate.annotations.SortNatural;
7475
import org.hibernate.annotations.Synchronize;
@@ -1454,17 +1455,47 @@ private void defineFetchingStrategy() {
14541455
}
14551456

14561457
private void handleFetch() {
1457-
if ( property.isAnnotationPresent( Fetch.class ) ) {
1458+
if ( !handleHibernateFetchMode() ) {
14581459
// Hibernate @Fetch annotation takes precedence
1459-
handleHibernateFetchMode();
1460+
collection.setFetchMode( getFetchMode( getJpaFetchType() ) );
1461+
}
1462+
}
1463+
1464+
private boolean handleHibernateFetchMode() {
1465+
if ( property.isAnnotationPresent( Fetch.class ) ) {
1466+
final Fetch fetch = property.getAnnotation( Fetch.class );
1467+
if ( fetch.profile().isEmpty() ) {
1468+
setHibernateFetchMode( fetch.value() );
1469+
return true;
1470+
}
1471+
else {
1472+
buildingContext.getMetadataCollector()
1473+
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );
1474+
return false;
1475+
}
1476+
}
1477+
else if ( property.isAnnotationPresent( Fetches.class ) ) {
1478+
boolean result = false;
1479+
for ( Fetch fetch: property.getAnnotation( Fetches.class ).value() ) {
1480+
if ( fetch.profile().isEmpty() ) {
1481+
if ( result ) {
1482+
throw new AnnotationException( "Collection '" + safeCollectionRole()
1483+
+ "' had multiple '@Fetch' annotations which did not specify a named fetch 'profile'"
1484+
+ " (only one annotation may be specified for the default profile)" );
1485+
}
1486+
setHibernateFetchMode( fetch.value() );
1487+
result = true;
1488+
}
1489+
}
1490+
return result;
14601491
}
14611492
else {
1462-
collection.setFetchMode( getFetchMode( getJpaFetchType() ) );
1493+
return false;
14631494
}
14641495
}
14651496

1466-
private void handleHibernateFetchMode() {
1467-
switch ( property.getAnnotation( Fetch.class ).value() ) {
1497+
private void setHibernateFetchMode(org.hibernate.annotations.FetchMode fetchMode) {
1498+
switch ( fetchMode ) {
14681499
case JOIN:
14691500
collection.setFetchMode( FetchMode.JOIN );
14701501
collection.setLazy( false );
Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,28 @@
55
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
66
*/
77
package org.hibernate.boot.model.internal;
8-
import java.util.Locale;
9-
import java.util.Map;
108

119
import org.hibernate.MappingException;
12-
import org.hibernate.annotations.FetchProfile;
10+
import org.hibernate.annotations.FetchProfile.FetchOverride;
1311
import org.hibernate.boot.spi.MetadataBuildingContext;
1412
import org.hibernate.boot.spi.SecondPass;
15-
import org.hibernate.mapping.MetadataSource;
13+
import org.hibernate.mapping.FetchProfile;
1614
import org.hibernate.mapping.PersistentClass;
1715

16+
import java.util.Locale;
17+
import java.util.Map;
18+
1819
/**
1920
* @author Hardy Ferentschik
2021
*/
21-
public class VerifyFetchProfileReferenceSecondPass implements SecondPass {
22+
public class FetchOverrideSecondPass implements SecondPass {
2223
private final String fetchProfileName;
23-
private final FetchProfile.FetchOverride fetch;
24+
private final FetchOverride fetch;
2425
private final MetadataBuildingContext buildingContext;
2526

26-
public VerifyFetchProfileReferenceSecondPass(
27+
public FetchOverrideSecondPass(
2728
String fetchProfileName,
28-
FetchProfile.FetchOverride fetch,
29+
FetchOverride fetch,
2930
MetadataBuildingContext buildingContext) {
3031
this.fetchProfileName = fetchProfileName;
3132
this.fetch = fetch;
@@ -34,21 +35,13 @@ public VerifyFetchProfileReferenceSecondPass(
3435

3536
@Override
3637
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
37-
org.hibernate.mapping.FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetchProfileName );
38-
if ( profile != null ) {
39-
if ( profile.getSource() != MetadataSource.ANNOTATIONS ) {
40-
return;
41-
}
42-
}
43-
else {
44-
profile = new org.hibernate.mapping.FetchProfile( fetchProfileName, MetadataSource.ANNOTATIONS );
45-
buildingContext.getMetadataCollector().addFetchProfile( profile );
46-
}
47-
48-
PersistentClass clazz = buildingContext.getMetadataCollector().getEntityBinding( fetch.entity().getName() );
4938
// throws MappingException in case the property does not exist
50-
clazz.getProperty( fetch.association() );
39+
buildingContext.getMetadataCollector()
40+
.getEntityBinding( fetch.entity().getName() )
41+
.getProperty( fetch.association() );
5142

43+
final FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetchProfileName );
44+
// we already know that the FetchProfile exists and is good to use
5245
profile.addFetch(
5346
fetch.entity().getName(),
5447
fetch.association(),
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
6+
*/
7+
package org.hibernate.boot.model.internal;
8+
9+
import java.util.Locale;
10+
import java.util.Map;
11+
12+
import org.hibernate.AnnotationException;
13+
import org.hibernate.MappingException;
14+
import org.hibernate.annotations.Fetch;
15+
import org.hibernate.boot.spi.MetadataBuildingContext;
16+
import org.hibernate.boot.spi.SecondPass;
17+
import org.hibernate.mapping.FetchProfile;
18+
import org.hibernate.mapping.PersistentClass;
19+
20+
import static org.hibernate.internal.util.StringHelper.qualify;
21+
import static org.hibernate.mapping.MetadataSource.ANNOTATIONS;
22+
23+
/**
24+
* @author Gavin King
25+
*/
26+
public class FetchSecondPass implements SecondPass {
27+
private final Fetch fetch;
28+
private final PropertyHolder propertyHolder;
29+
private final String propertyName;
30+
private final MetadataBuildingContext buildingContext;
31+
32+
public FetchSecondPass(Fetch fetch, PropertyHolder propertyHolder, String propertyName, MetadataBuildingContext buildingContext) {
33+
this.fetch = fetch;
34+
this.propertyHolder = propertyHolder;
35+
this.propertyName = propertyName;
36+
this.buildingContext = buildingContext;
37+
}
38+
39+
@Override
40+
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
41+
42+
//TODO: handle propertyHolder.getPath() !!!!
43+
44+
// throws MappingException in case the property does not exist
45+
buildingContext.getMetadataCollector()
46+
.getEntityBinding( propertyHolder.getEntityName() )
47+
.getProperty( propertyName );
48+
49+
FetchProfile profile = buildingContext.getMetadataCollector().getFetchProfile( fetch.profile() );
50+
if ( profile == null ) {
51+
throw new AnnotationException( "Property '" + qualify( propertyHolder.getPath(), propertyName )
52+
+ "' refers to an unknown fetch profile named '" + fetch.profile() + "'" );
53+
}
54+
else if ( profile.getSource() == ANNOTATIONS ) {
55+
profile.addFetch(
56+
propertyHolder.getEntityName(),
57+
propertyName,
58+
fetch.value().toString().toLowerCase(Locale.ROOT)
59+
);
60+
}
61+
// otherwise, it's a fetch profile defined in XML, and it overrides
62+
// the annotations, so we simply ignore this annotation completely
63+
}
64+
}

0 commit comments

Comments
 (0)