From 93c475f7e2bb23f42f148b0a4364c728609e43f5 Mon Sep 17 00:00:00 2001 From: Ladislav Kulhanek Date: Fri, 16 Feb 2018 09:08:20 +0100 Subject: [PATCH] HHH-12297 - Relations are not loaded when using Fetch Profiles --- .../engine/internal/TwoPhaseLoad.java | 65 +++- .../org/hibernate/type/CollectionType.java | 18 +- .../java/org/hibernate/type/EntityType.java | 16 +- .../main/java/org/hibernate/type/Type.java | 20 +- .../CollectionLoadedInTwoPhaseLoadTest.java | 280 +++++++++++++++++ .../EntityLoadedInTwoPhaseLoadTest.java | 284 ++++++++++++++++++ .../join/JoinFetchProfileTest.java | 9 +- 7 files changed, 666 insertions(+), 26 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/CollectionLoadedInTwoPhaseLoadTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/fetchprofiles/EntityLoadedInTwoPhaseLoadTest.java 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(); }