HHH-8903 bi-directional fetches in entity graphs

This commit is contained in:
Brett Meyer 2014-01-28 16:43:33 -05:00
parent fef6c5394a
commit f815f70f51
5 changed files with 49 additions and 4 deletions

View File

@ -32,4 +32,5 @@ import javax.persistence.AttributeNode;
public interface GraphNodeImplementor {
List<AttributeNodeImplementor<?>> attributeImplementorNodes();
List<AttributeNode<?>> attributeNodes();
boolean containsAttribute(String name);
}

View File

@ -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<AttributeNode<?>> 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 );
}
}

View File

@ -138,7 +138,7 @@ public abstract class AbstractLoadPlanBuildingAssociationVisitationStrategy
return last;
}
private ExpandingFetchSource currentSource() {
protected ExpandingFetchSource currentSource() {
return fetchSourceStack.peekFirst();
}

View File

@ -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<T> implements GraphNodeImplementor, HibernateEntityManagerFactoryAware{
public abstract class AbstractGraphNode<T> implements GraphNodeImplementor, HibernateEntityManagerFactoryAware {
private static final Logger log = Logger.getLogger( AbstractGraphNode.class );
private final HibernateEntityManagerFactory entityManagerFactory;
@ -200,4 +200,9 @@ public abstract class AbstractGraphNode<T> implements GraphNodeImplementor, Hibe
public <X> SubgraphImpl<X> addKeySubgraph(String attributeName, Class<X> type) {
return addAttribute( attributeName ).makeKeySubgraph( type );
}
@Override
public boolean containsAttribute(String name) {
return attributeNodeMap != null && attributeNodeMap.containsKey( name );
}
}

View File

@ -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<Foo> fooGraph = em.createEntityGraph( Foo.class );
fooGraph.addAttributeNodes("bar");
Subgraph barGraph = fooGraph.addSubgraph("bar");
fooGraph.addAttributeNodes("baz");
Subgraph<Bar> barGraph = fooGraph.addSubgraph("bar", Bar.class);
barGraph.addAttributeNodes("foos");
Map<String, Object> properties = new HashMap<String, Object>();
@ -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<Foo> foos = new HashSet<Foo>();
}
}