diff --git a/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphNodeImplementor.java b/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphNodeImplementor.java index 708757e43c..774cc75435 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphNodeImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/spi/GraphNodeImplementor.java @@ -32,4 +32,5 @@ import javax.persistence.AttributeNode; public interface GraphNodeImplementor { List> attributeImplementorNodes(); List> attributeNodes(); + boolean containsAttribute(String name); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java index 51f5bf4c7d..ae99610819 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractEntityGraphVisitationStrategy.java @@ -45,9 +45,12 @@ import org.hibernate.graph.spi.AttributeNodeImplementor; import org.hibernate.graph.spi.GraphNodeImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.loader.plan.spi.EntityReturn; +import org.hibernate.loader.plan.spi.FetchSource; import org.hibernate.loader.plan.spi.LoadPlan; import org.hibernate.loader.plan.spi.Return; +import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; +import org.hibernate.persister.walking.spi.AssociationKey; import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.CollectionElementDefinition; import org.hibernate.persister.walking.spi.CollectionIndexDefinition; @@ -330,5 +333,30 @@ public abstract class AbstractEntityGraphVisitationStrategy public List> attributeNodes() { return Collections.emptyList(); } + + @Override + public boolean containsAttribute(String name) { + return false; + } }; + + @Override + public void foundCircularAssociation(AssociationAttributeDefinition attributeDefinition) { + final FetchStrategy fetchStrategy = determineFetchStrategy( attributeDefinition ); + if ( fetchStrategy.getStyle() != FetchStyle.JOIN ) { + return; // nothing to do + } + + // Bi-directional association & the owning side was already visited. If the current attribute node refers + // to it, fetch. + // ENTITY nature handled by super. + final GraphNodeImplementor graphNode = graphStack.peekLast(); + if ( attributeDefinition.getAssociationNature() == AssociationAttributeDefinition.AssociationNature.COLLECTION + && ! graphNode.equals( NON_EXIST_SUBGRAPH_NODE) + && graphNode.containsAttribute( attributeDefinition.getName() )) { + currentSource().buildCollectionAttributeFetch( attributeDefinition, fetchStrategy ); + } + + super.foundCircularAssociation( attributeDefinition ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java index 55cc246cef..1cb48f09fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/AbstractLoadPlanBuildingAssociationVisitationStrategy.java @@ -138,7 +138,7 @@ public abstract class AbstractLoadPlanBuildingAssociationVisitationStrategy return last; } - private ExpandingFetchSource currentSource() { + protected ExpandingFetchSource currentSource() { return fetchSourceStack.peekFirst(); } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/AbstractGraphNode.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/AbstractGraphNode.java index 5da3b05140..3a5018736a 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/AbstractGraphNode.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/AbstractGraphNode.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.persistence.AttributeNode; import javax.persistence.metamodel.Attribute; @@ -36,7 +37,6 @@ import org.hibernate.graph.spi.GraphNodeImplementor; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.jpa.HibernateEntityManagerFactory; import org.hibernate.jpa.spi.HibernateEntityManagerFactoryAware; - import org.jboss.logging.Logger; /** @@ -44,7 +44,7 @@ import org.jboss.logging.Logger; * * @author Steve Ebersole */ -public abstract class AbstractGraphNode implements GraphNodeImplementor, HibernateEntityManagerFactoryAware{ +public abstract class AbstractGraphNode implements GraphNodeImplementor, HibernateEntityManagerFactoryAware { private static final Logger log = Logger.getLogger( AbstractGraphNode.class ); private final HibernateEntityManagerFactory entityManagerFactory; @@ -200,4 +200,9 @@ public abstract class AbstractGraphNode implements GraphNodeImplementor, Hibe public SubgraphImpl addKeySubgraph(String attributeName, Class type) { return addAttribute( attributeName ).makeKeySubgraph( type ); } + + @Override + public boolean containsAttribute(String name) { + return attributeNodeMap != null && attributeNodeMap.containsKey( name ); + } } diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphTest.java index 309d41d7c2..a3161994e6 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphTest.java @@ -136,10 +136,14 @@ public class EntityGraphTest extends BaseEntityManagerFunctionalTestCase { Bar bar = new Bar(); em.persist( bar ); + Baz baz = new Baz(); + em.persist( baz ); Foo foo = new Foo(); foo.bar = bar; + foo.baz = baz; bar.foos.add(foo); + baz.foos.add(foo); em.persist( foo ); em.getTransaction().commit(); @@ -149,7 +153,8 @@ public class EntityGraphTest extends BaseEntityManagerFunctionalTestCase { EntityGraph fooGraph = em.createEntityGraph( Foo.class ); fooGraph.addAttributeNodes("bar"); - Subgraph barGraph = fooGraph.addSubgraph("bar"); + fooGraph.addAttributeNodes("baz"); + Subgraph barGraph = fooGraph.addSubgraph("bar", Bar.class); barGraph.addAttributeNodes("foos"); Map properties = new HashMap(); @@ -160,6 +165,9 @@ public class EntityGraphTest extends BaseEntityManagerFunctionalTestCase { assertTrue( Hibernate.isInitialized( result ) ); assertTrue( Hibernate.isInitialized( result.bar ) ); assertTrue( Hibernate.isInitialized( result.bar.foos) ); + assertTrue( Hibernate.isInitialized( result.baz ) ); + // sanity check -- ensure the only bi-directional fetch was the one identified by the graph + assertFalse( Hibernate.isInitialized( result.baz.foos) ); em.getTransaction().commit(); em.close(); @@ -249,6 +257,9 @@ public class EntityGraphTest extends BaseEntityManagerFunctionalTestCase { @GeneratedValue public Integer id; + @OneToMany(mappedBy = "bar") + public Set foos = new HashSet(); + } }