diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java
index b3960e521a..37dc266be6 100644
--- a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java
+++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java
@@ -15,8 +15,11 @@ import org.hibernate.LockMode;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
import org.hibernate.cache.spi.entry.CacheEntry;
+import org.hibernate.engine.profile.Fetch;
+import org.hibernate.engine.profile.FetchProfile;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
+import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionEventListenerManager;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@@ -144,9 +147,12 @@ public final class TwoPhaseLoad {
);
}
+ String entityName = persister.getEntityName();
+ String[] propertyNames = persister.getPropertyNames();
final Type[] types = persister.getPropertyTypes();
for ( int i = 0; i < hydratedState.length; i++ ) {
final Object value = hydratedState[i];
+ Boolean overridingEager = getOverridingEager( session, entityName, propertyNames[i], types[i] );
if ( value == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
// IMPLEMENTATION NOTE: This is a lazy property on a bytecode-enhanced entity.
// hydratedState[i] needs to remain LazyPropertyInitializer.UNFETCHED_PROPERTY so that
@@ -157,12 +163,12 @@ public final class TwoPhaseLoad {
// HHH-10989: We need to resolve the collection so that a CollectionReference is added to StatefulPersistentContext.
// As mentioned above, hydratedState[i] needs to remain LazyPropertyInitializer.UNFETCHED_PROPERTY
// so do not assign the resolved, unitialized PersistentCollection back to hydratedState[i].
- types[i].resolve( value, session, entity );
+ types[i].resolve( value, session, entity, overridingEager );
}
}
- else if ( value!= PropertyAccessStrategyBackRefImpl.UNKNOWN ) {
+ else if ( value != PropertyAccessStrategyBackRefImpl.UNKNOWN ) {
// we know value != LazyPropertyInitializer.UNFETCHED_PROPERTY
- hydratedState[i] = types[i].resolve( value, session, entity );
+ hydratedState[i] = types[i].resolve( value, session, entity, overridingEager );
}
}
@@ -288,7 +294,54 @@ public final class TwoPhaseLoad {
factory.getStatistics().loadEntity( persister.getEntityName() );
}
}
-
+
+ /**
+ * Check if eager of the association is overriden by anything.
+ *
+ * @param session session
+ * @param entityName entity name
+ * @param associationName association name
+ *
+ * @return null if there is no overriding, true if it is overridden to eager and false if it is overridden to lazy
+ */
+ private static Boolean getOverridingEager(
+ SharedSessionContractImplementor session,
+ String entityName,
+ String associationName,
+ Type type) {
+ if ( type.isAssociationType() || type.isCollectionType() ) {
+ Boolean overridingEager = isEagerFetchProfile( session, entityName + "." + associationName );
+
+ if ( LOG.isDebugEnabled() ) {
+ if ( overridingEager != null ) {
+ LOG.debugf(
+ "Overriding eager fetching using active fetch profile. EntityName: %s, associationName: %s, eager fetching: %s",
+ entityName,
+ associationName,
+ overridingEager
+ );
+ }
+ }
+
+ return overridingEager;
+ }
+ return null;
+ }
+
+ private static Boolean isEagerFetchProfile(SharedSessionContractImplementor session, String role) {
+ LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers();
+
+ for ( String fetchProfileName : loadQueryInfluencers.getEnabledFetchProfileNames() ) {
+ FetchProfile fp = session.getFactory().getFetchProfile( fetchProfileName );
+ Fetch fetch = fp.getFetchByRole( role );
+ if ( fetch != null && Fetch.Style.JOIN == fetch.getStyle() ) {
+ return true;
+ }
+ }
+
+ return null;
+ }
+
/**
* PostLoad cannot occur during initializeEntity, as that call occurs *before*
* the Set collections are added to the persistence context by Loader.
@@ -296,7 +349,7 @@ public final class TwoPhaseLoad {
* postLoad if it acts upon the collection.
*
* HHH-6043
- *
+ *
* @param entity The entity
* @param session The Session
* @param postLoadEvent The (re-used) post-load event
@@ -305,7 +358,7 @@ public final class TwoPhaseLoad {
final Object entity,
final SharedSessionContractImplementor session,
final PostLoadEvent postLoadEvent) {
-
+
if ( session.isEventSource() ) {
final PersistenceContext persistenceContext
= session.getPersistenceContext();
diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java
index dde24e52d4..d42380760b 100644
--- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java
+++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java
@@ -286,7 +286,7 @@ public abstract class CollectionType extends AbstractType implements Association
final Serializable key = (Serializable) getPersister(session)
.getKeyType()
.assemble( cached, session, owner);
- return resolveKey( key, session, owner );
+ return resolveKey( key, session, owner, null );
}
}
@@ -452,14 +452,19 @@ public abstract class CollectionType extends AbstractType implements Association
public Object resolve(Object value, SharedSessionContractImplementor session, Object owner)
throws HibernateException {
- return resolveKey( getKeyOfOwner( owner, session ), session, owner );
+ return resolve(value, session, owner, null);
}
- private Object resolveKey(Serializable key, SharedSessionContractImplementor session, Object owner) {
+ @Override
+ public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) throws HibernateException {
+ return resolveKey( getKeyOfOwner( owner, session ), session, owner, overridingEager );
+ }
+
+ private Object resolveKey(Serializable key, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) {
// if (key==null) throw new AssertionFailure("owner identifier unknown when re-assembling
// collection reference");
return key == null ? null : // TODO: can this case really occur??
- getCollection( key, session, owner );
+ getCollection( key, session, owner, overridingEager );
}
@Override
@@ -745,7 +750,7 @@ public abstract class CollectionType extends AbstractType implements Association
* @param owner The collection owner
* @return The collection
*/
- public Object getCollection(Serializable key, SharedSessionContractImplementor session, Object owner) {
+ public Object getCollection(Serializable key, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) {
CollectionPersister persister = getPersister( session );
final PersistenceContext persistenceContext = session.getPersistenceContext();
@@ -772,10 +777,11 @@ public abstract class CollectionType extends AbstractType implements Association
persistenceContext.addUninitializedCollection( persister, collection, key );
// some collections are not lazy:
+ boolean eager = overridingEager != null ? overridingEager : !persister.isLazy();
if ( initializeImmediately() ) {
session.initializeCollection( collection, false );
}
- else if ( !persister.isLazy() ) {
+ else if ( eager ) {
persistenceContext.addNonLazyCollection( collection );
}
diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java
index 6e42421a53..e738864822 100644
--- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java
+++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java
@@ -452,9 +452,14 @@ public abstract class EntityType extends AbstractType implements AssociationType
*/
@Override
public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return resolve(value, session, owner, null);
+ }
+
+ @Override
+ public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) throws HibernateException {
if ( value != null && !isNull( owner, session ) ) {
if ( isReferenceToPrimaryKey() ) {
- return resolveIdentifier( (Serializable) value, session );
+ return resolveIdentifier( (Serializable) value, session, overridingEager );
}
else if ( uniqueKeyPropertyName != null ) {
return loadByUniqueKey( getAssociatedEntityName(), uniqueKeyPropertyName, value, session );
@@ -664,11 +669,14 @@ public abstract class EntityType extends AbstractType implements AssociationType
*
* @throws org.hibernate.HibernateException Indicates problems performing the load.
*/
- protected final Object resolveIdentifier(Serializable id, SharedSessionContractImplementor session) throws HibernateException {
+ protected final Object resolveIdentifier(Serializable id, SharedSessionContractImplementor session, Boolean overridingEager) throws HibernateException {
+
boolean isProxyUnwrapEnabled = unwrapProxy &&
getAssociatedEntityPersister( session.getFactory() )
.isInstrumented();
+ boolean eager = overridingEager != null ? overridingEager : this.eager;
+
Object proxyOrEntity = session.internalLoad(
getAssociatedEntityName(),
id,
@@ -684,6 +692,10 @@ public abstract class EntityType extends AbstractType implements AssociationType
return proxyOrEntity;
}
+ protected final Object resolveIdentifier(Serializable id, SharedSessionContractImplementor session) throws HibernateException {
+ return resolveIdentifier( id, session, null );
+ }
+
protected boolean isNull(Object owner, SharedSessionContractImplementor session) {
return false;
}
diff --git a/hibernate-core/src/main/java/org/hibernate/type/Type.java b/hibernate-core/src/main/java/org/hibernate/type/Type.java
index 55d9585ab4..65ad6d3e1b 100644
--- a/hibernate-core/src/main/java/org/hibernate/type/Type.java
+++ b/hibernate-core/src/main/java/org/hibernate/type/Type.java
@@ -460,23 +460,33 @@ public interface Type extends Serializable {
Object hydrate(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException;
+ /**
+ * @see #resolve(Object, SharedSessionContractImplementor, Object, Boolean)
+ */
+ Object resolve(Object value, SharedSessionContractImplementor session, Object owner)
+ throws HibernateException;
+
/**
* The second phase of 2-phase loading. Only really pertinent for entities and collections. Here we resolve the
* identifier to an entity or collection instance
- *
+ *
* @param value an identifier or value returned by hydrate()
* @param owner the parent entity
* @param session the session
- *
+ * @param overridingEager can override eager from the mapping. For example because of {@link org.hibernate.engine.spi.LoadQueryInfluencers}
+ * If null, then it does not override. If true or false then it overrides the mapping value.
+ *
* @return the given value, or the value associated with the identifier
*
* @throws HibernateException An error from Hibernate
*
* @see #hydrate
*/
- Object resolve(Object value, SharedSessionContractImplementor session, Object owner)
- throws HibernateException;
-
+ default Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager)
+ throws HibernateException {
+ return resolve(value, session, owner);
+ }
+
/**
* Given a hydrated, but unresolved value, return a value that may be used to reconstruct property-ref
* associations.
diff --git a/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/CollectionLoadedInTwoPhaseLoadTest.java b/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/CollectionLoadedInTwoPhaseLoadTest.java
new file mode 100644
index 0000000000..71a67ad88e
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/CollectionLoadedInTwoPhaseLoadTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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 .
+ */
+package org.hibernate.test.fetchprofiles;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+
+import org.hibernate.LazyInitializationException;
+import org.hibernate.Session;
+import org.hibernate.annotations.FetchMode;
+import org.hibernate.annotations.FetchProfile;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+
+import org.hibernate.testing.TestForIssue;
+import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+@TestForIssue( jiraKey = "HHH-12297")
+public class CollectionLoadedInTwoPhaseLoadTest extends BaseCoreFunctionalTestCase {
+
+ // NOTE
+ // there are two fetch profiles because when I use only one the relation OrgUnit.people
+ // is missing in the fetch profile.
+ // It is missing because of logic in FetchProfile.addFetch(). Do not understand the implementation
+ // of the method now, so the workaround is to use two fetch profiles.
+ static final String FETCH_PROFILE_NAME = "fp1";
+ static final String FETCH_PROFILE_NAME_2 = "fp2";
+
+ private final String OU_1 = "ou_1";
+ private final String OU_2 = "ou_2";
+ private final String P_1 = "p_1";
+ private final String P_2 = "p_2";
+
+ public void configure(Configuration cfg) {
+ cfg.setProperty( Environment.GENERATE_STATISTICS, "true" );
+ }
+
+ @Test
+ public void testIfEverythingIsLoaded() {
+ createSampleData();
+ sessionFactory().getStatistics().clear();
+ try {
+ OrgUnit ou1 = this.loadOrgUnitWithFetchProfile( OU_1 );
+ Person p1 = ou1.findPerson( P_1 );
+ OrgUnit ou2 = p1.findOrgUnit( OU_2 );
+ Person p2 = ou2.findPerson( P_2 );
+ @SuppressWarnings( "unused" )
+ String email = p2.getEmail();
+ assertEquals( 4, sessionFactory().getStatistics().getEntityLoadCount() );
+ }
+ catch (LazyInitializationException e) {
+ fail( "Everything should be initialized" );
+ }
+ }
+
+ public OrgUnit loadOrgUnitWithFetchProfile(String groupId) {
+ return doInHibernate( this::sessionFactory, session -> {
+ session.enableFetchProfile( FETCH_PROFILE_NAME );
+ session.enableFetchProfile( FETCH_PROFILE_NAME_2 );
+ return session.get( OrgUnit.class, groupId );
+ } );
+ }
+
+ private void createSampleData() {
+ doInHibernate( this::sessionFactory, session -> {
+ OrgUnit ou1 = new OrgUnit( OU_1, "org unit one" );
+ OrgUnit ou2 = new OrgUnit( OU_2, "org unit two" );
+ Person p1 = new Person( P_1, "p1@coompany.com" );
+ Person p2 = new Person( P_2, "p2@company.com" );
+
+ ou1.addPerson( p1 );
+ ou2.addPerson( p1 );
+ ou2.addPerson( p2 );
+
+ session.persist( ou1 );
+ } );
+ }
+
+ @Override
+ protected Class[] getAnnotatedClasses() {
+ return new Class[] {
+ Person.class,
+ OrgUnit.class
+ };
+ }
+
+ @Entity(name = "OrgUnit")
+ @FetchProfile(name = FETCH_PROFILE_NAME, fetchOverrides = {
+ @FetchProfile.FetchOverride(entity = OrgUnit.class, association = "people", mode = FetchMode.JOIN)
+ })
+ public static class OrgUnit {
+
+ @Id
+ private String name;
+
+ private String description;
+
+ @ManyToMany(fetch = FetchType.LAZY, mappedBy = "orgUnits", cascade = CascadeType.PERSIST)
+ private List people = new ArrayList<>();
+
+ public OrgUnit() {
+ }
+
+ public OrgUnit(String name, String description) {
+ this.name = name;
+ this.description = description;
+ }
+
+ public Person findPerson(String personName) {
+ if (people == null) {
+ return null;
+ }
+ for ( Person person : people ) {
+ if (person.getName().equals( personName )) return person;
+ }
+ return null;
+ }
+
+ public void addPerson(Person person) {
+ if (people.contains( person )) return;
+ people.add(person);
+ person.addOrgUnit( this);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public List getPeople() {
+ return people;
+ }
+
+ public void setPeople(List people) {
+ this.people = people;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ OrgUnit group = (OrgUnit) o;
+ return Objects.equals( name, group.name );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash( name );
+ }
+
+ @Override
+ public String toString() {
+ return "OrgUnit{" +
+ "name='" + name + '\'' +
+ ", description='" + description + '\'' +
+ '}';
+ }
+
+ }
+
+ @Entity(name = "Person")
+ @FetchProfile(name = FETCH_PROFILE_NAME_2, fetchOverrides = {
+ @FetchProfile.FetchOverride(entity = Person.class, association = "orgUnits", mode = FetchMode.JOIN)
+ })
+ public static class Person {
+
+ @Id
+ private String name;
+
+ private String email;
+
+ @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
+ private List orgUnits = new ArrayList<>();
+
+ public Person() {
+ }
+
+ public Person(String name, String email) {
+ this.name = name;
+ this.email = email;
+ }
+
+ public OrgUnit findOrgUnit(String orgUnitName) {
+ if ( orgUnits == null) {
+ return null;
+ }
+ for ( OrgUnit orgUnit : orgUnits ) {
+ if (orgUnit.getName().equals( orgUnitName )) return orgUnit;
+ }
+ return null;
+ }
+
+ public void addOrgUnit(OrgUnit orgUnit) {
+ if ( orgUnits.contains( orgUnit)) return;
+ orgUnits.add( orgUnit);
+ orgUnit.addPerson(this);
+ }
+
+ public List getOrgUnits() {
+ return orgUnits;
+ }
+
+ public void setOrgUnits(List orgUnits) {
+ this.orgUnits = orgUnits;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ Person person = (Person) o;
+ return Objects.equals( name, person.name );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash( name );
+ }
+
+ @Override
+ public String toString() {
+ return "Person{" +
+ "name='" + name + '\'' +
+ ", email='" + email + '\'' +
+ '}';
+ }
+
+ }
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/EntityLoadedInTwoPhaseLoadTest.java b/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/EntityLoadedInTwoPhaseLoadTest.java
new file mode 100644
index 0000000000..f6666e3a1c
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/EntityLoadedInTwoPhaseLoadTest.java
@@ -0,0 +1,284 @@
+/*
+ * 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 .
+ */
+package org.hibernate.test.fetchprofiles;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+
+import org.hibernate.LazyInitializationException;
+import org.hibernate.annotations.FetchMode;
+import org.hibernate.annotations.FetchProfile;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+
+import org.hibernate.testing.TestForIssue;
+import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
+import org.junit.Test;
+
+import static com.sun.tools.internal.ws.wsdl.parser.Util.fail;
+import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
+import static org.junit.Assert.assertEquals;
+
+@TestForIssue( jiraKey = "HHH-12297")
+public class EntityLoadedInTwoPhaseLoadTest extends BaseCoreFunctionalTestCase {
+
+ static final String FETCH_PROFILE_NAME = "fp1";
+
+ public void configure(Configuration cfg) {
+ cfg.setProperty( Environment.GENERATE_STATISTICS, "true" );
+ }
+
+ @Test
+ public void testIfAllRelationsAreInitialized() {
+ long startId = this.createSampleData();
+ sessionFactory().getStatistics().clear();
+ try {
+ Start start = this.loadStartWithFetchProfile( startId );
+ @SuppressWarnings( "unused" )
+ String value = start.getVia2().getMid().getFinish().getValue();
+ assertEquals( 4, sessionFactory().getStatistics().getEntityLoadCount() );
+ }
+ catch (LazyInitializationException e) {
+ fail( "Everything should be initialized" );
+ }
+ }
+
+ public Start loadStartWithFetchProfile(long startId) {
+ return doInHibernate( this::sessionFactory, session -> {
+ session.enableFetchProfile( FETCH_PROFILE_NAME );
+ return session.get( Start.class, startId );
+ } );
+ }
+
+ private long createSampleData() {
+ return doInHibernate( this::sessionFactory, session -> {
+ Finish finish = new Finish( "foo" );
+ Mid mid = new Mid( finish );
+ Via2 via2 = new Via2( mid );
+ Start start = new Start( null, via2 );
+
+ session.persist( start );
+
+ return start.getId();
+ } );
+ }
+
+ @Override
+ protected Class[] getAnnotatedClasses() {
+ return new Class[] {
+ Start.class,
+ Mid.class,
+ Finish.class,
+ Via1.class,
+ Via2.class
+ };
+ }
+
+ @Entity(name = "Finish")
+ public static class Finish {
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ @Column(name = "value", nullable = false)
+ private String value;
+
+ public Finish() {
+ }
+
+ public Finish(String value) {
+ this.value = value;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ @Entity(name = "Mid")
+ @FetchProfile(name = FETCH_PROFILE_NAME, fetchOverrides = {
+ @FetchProfile.FetchOverride(entity = Mid.class, association = "finish", mode = FetchMode.JOIN)
+ })
+ public static class Mid {
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
+ private Finish finish;
+
+ public Mid() {
+ }
+
+ public Mid(Finish finish) {
+ this.finish = finish;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Finish getFinish() {
+ return finish;
+ }
+
+ public void setFinish(Finish finish) {
+ this.finish = finish;
+ }
+
+ }
+
+ @Entity(name = "Start")
+ @FetchProfile(name = FETCH_PROFILE_NAME, fetchOverrides = {
+ @FetchProfile.FetchOverride(entity = Start.class, association = "via1", mode = FetchMode.JOIN),
+ @FetchProfile.FetchOverride(entity = Start.class, association = "via2", mode = FetchMode.JOIN)
+ })
+ public static class Start {
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
+ private Via1 via1;
+
+ @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
+ private Via2 via2;
+
+ public Start() {
+ }
+
+ public Start(Via1 via1, Via2 via2) {
+ this.via1 = via1;
+ this.via2 = via2;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Via1 getVia1() {
+ return via1;
+ }
+
+ public void setVia1(Via1 via1) {
+ this.via1 = via1;
+ }
+
+ public Via2 getVia2() {
+ return via2;
+ }
+
+ public void setVia2(Via2 via2) {
+ this.via2 = via2;
+ }
+
+ }
+
+ @Entity(name = "Via1")
+ @FetchProfile(name = FETCH_PROFILE_NAME, fetchOverrides = {
+ @FetchProfile.FetchOverride(entity = Via1.class, association = "mid", mode = FetchMode.JOIN)
+ })
+ public static class Via1 {
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
+ private Mid mid;
+
+ public Via1() {
+ }
+
+ public Via1(Mid mid) {
+ this.mid = mid;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Mid getMid() {
+ return mid;
+ }
+
+ public void setMid(Mid mid) {
+ this.mid = mid;
+ }
+
+ }
+
+ @Entity(name = "Via2")
+ @FetchProfile(name = FETCH_PROFILE_NAME, fetchOverrides = {
+ @FetchProfile.FetchOverride(entity = Via2.class, association = "mid", mode = FetchMode.JOIN)
+ })
+ public static class Via2 {
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
+ private Mid mid;
+
+ public Via2() {
+ }
+
+ public Via2(Mid mid) {
+ this.mid = mid;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Mid getMid() {
+ return mid;
+ }
+
+ public void setMid(Mid mid) {
+ this.mid = mid;
+ }
+
+ }
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/join/JoinFetchProfileTest.java b/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/join/JoinFetchProfileTest.java
index 4157f469b0..bf957ecac0 100644
--- a/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/join/JoinFetchProfileTest.java
+++ b/hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/join/JoinFetchProfileTest.java
@@ -275,11 +275,6 @@ public class JoinFetchProfileTest extends BaseCoreFunctionalTestCase {
);
}
- /**
- * fetch-profiles should have no effect what-so-ever on the direct results of the HQL query.
- *
- * TODO : this is actually not strictly true. what we should have happen is to subsequently load those fetches
- */
@Test
public void testHQL() {
performWithStandardData(
@@ -292,8 +287,8 @@ public class JoinFetchProfileTest extends BaseCoreFunctionalTestCase {
List sections = session.createQuery( "from CourseOffering" ).list();
int sectionCount = sections.size();
assertEquals( "unexpected CourseOffering count", 1, sectionCount );
- assertEquals( 1, sessionFactory().getStatistics().getEntityLoadCount() );
- assertEquals( 0, sessionFactory().getStatistics().getEntityFetchCount() );
+ assertEquals( 4, sessionFactory().getStatistics().getEntityLoadCount() );
+ assertEquals( 2, sessionFactory().getStatistics().getEntityFetchCount() );
session.getTransaction().commit();
session.close();
}