HHH-7841 - Redesign Loader

This commit is contained in:
Steve Ebersole 2013-04-15 20:46:18 -05:00
parent cbfa233ea1
commit fafce001e7
7 changed files with 455 additions and 113 deletions

View File

@ -26,6 +26,7 @@ package org.hibernate.loader.plan.internal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.hibernate.loader.plan.spi.EntityReturn;
import org.hibernate.loader.plan.spi.LoadPlan; import org.hibernate.loader.plan.spi.LoadPlan;
import org.hibernate.loader.plan.spi.Return; import org.hibernate.loader.plan.spi.Return;
@ -47,6 +48,10 @@ public class LoadPlanImpl implements LoadPlan {
this( hasScalars, Collections.singletonList( rootReturn ) ); this( hasScalars, Collections.singletonList( rootReturn ) );
} }
public LoadPlanImpl(EntityReturn entityReturn) {
this( false, entityReturn );
}
@Override @Override
public boolean hasAnyScalarReturns() { public boolean hasAnyScalarReturns() {
return hasScalars; return hasScalars;

View File

@ -36,6 +36,7 @@ import org.jboss.logging.Logger;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.jpa.HibernateEntityManagerFactory; import org.hibernate.jpa.HibernateEntityManagerFactory;
import org.hibernate.jpa.graph.spi.AttributeNodeImplementor;
import org.hibernate.jpa.graph.spi.GraphNodeImplementor; import org.hibernate.jpa.graph.spi.GraphNodeImplementor;
/** /**
@ -49,7 +50,7 @@ public abstract class AbstractGraphNode<T> implements GraphNodeImplementor {
private final HibernateEntityManagerFactory entityManagerFactory; private final HibernateEntityManagerFactory entityManagerFactory;
private final boolean mutable; private final boolean mutable;
private Map<String, AttributeNode<?>> attributeNodeMap; private Map<String, AttributeNodeImplementor<?>> attributeNodeMap;
protected AbstractGraphNode(HibernateEntityManagerFactory entityManagerFactory, boolean mutable) { protected AbstractGraphNode(HibernateEntityManagerFactory entityManagerFactory, boolean mutable) {
this.entityManagerFactory = entityManagerFactory; this.entityManagerFactory = entityManagerFactory;
@ -62,14 +63,14 @@ public abstract class AbstractGraphNode<T> implements GraphNodeImplementor {
this.attributeNodeMap = makeSafeMapCopy( original.attributeNodeMap ); this.attributeNodeMap = makeSafeMapCopy( original.attributeNodeMap );
} }
private static Map<String, AttributeNode<?>> makeSafeMapCopy(Map<String, AttributeNode<?>> attributeNodeMap) { private static Map<String, AttributeNodeImplementor<?>> makeSafeMapCopy(Map<String, AttributeNodeImplementor<?>> attributeNodeMap) {
if ( attributeNodeMap == null ) { if ( attributeNodeMap == null ) {
return null; return null;
} }
final int properSize = CollectionHelper.determineProperSizing( attributeNodeMap ); final int properSize = CollectionHelper.determineProperSizing( attributeNodeMap );
final HashMap<String,AttributeNode<?>> copy = new HashMap<String,AttributeNode<?>>( properSize ); final HashMap<String,AttributeNodeImplementor<?>> copy = new HashMap<String,AttributeNodeImplementor<?>>( properSize );
for ( Map.Entry<String,AttributeNode<?>> attributeNodeEntry : attributeNodeMap.entrySet() ) { for ( Map.Entry<String,AttributeNodeImplementor<?>> attributeNodeEntry : attributeNodeMap.entrySet() ) {
copy.put( copy.put(
attributeNodeEntry.getKey(), attributeNodeEntry.getKey(),
( ( AttributeNodeImpl ) attributeNodeEntry.getValue() ).makeImmutableCopy() ( ( AttributeNodeImpl ) attributeNodeEntry.getValue() ).makeImmutableCopy()
@ -83,6 +84,16 @@ public abstract class AbstractGraphNode<T> implements GraphNodeImplementor {
return entityManagerFactory; return entityManagerFactory;
} }
@Override
public List<AttributeNodeImplementor<?>> attributeImplementorNodes() {
if ( attributeNodeMap == null ) {
return Collections.emptyList();
}
else {
return new ArrayList<AttributeNodeImplementor<?>>( attributeNodeMap.values() );
}
}
@Override @Override
public List<AttributeNode<?>> attributeNodes() { public List<AttributeNode<?>> attributeNodes() {
if ( attributeNodeMap == null ) { if ( attributeNodeMap == null ) {
@ -120,7 +131,7 @@ public abstract class AbstractGraphNode<T> implements GraphNodeImplementor {
} }
if ( attributeNodeMap == null ) { if ( attributeNodeMap == null ) {
attributeNodeMap = new HashMap<String, AttributeNode<?>>(); attributeNodeMap = new HashMap<String, AttributeNodeImplementor<?>>();
} }
else { else {
final AttributeNode old = attributeNodeMap.get( attributeNode.getRegistrationName() ); final AttributeNode old = attributeNodeMap.get( attributeNode.getRegistrationName() );

View File

@ -0,0 +1,293 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.jpa.graph.internal.advisor;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.logging.Logger;
import org.hibernate.jpa.graph.internal.EntityGraphImpl;
import org.hibernate.jpa.graph.spi.AttributeNodeImplementor;
import org.hibernate.loader.plan.internal.LoadPlanImpl;
import org.hibernate.loader.plan.spi.CollectionFetch;
import org.hibernate.loader.plan.spi.CompositeFetch;
import org.hibernate.loader.plan.spi.CopyContext;
import org.hibernate.loader.plan.spi.EntityFetch;
import org.hibernate.loader.plan.spi.EntityReturn;
import org.hibernate.loader.plan.spi.FetchOwner;
import org.hibernate.loader.plan.spi.LoadPlan;
import org.hibernate.loader.plan.spi.Return;
import org.hibernate.loader.plan.spi.visit.ReturnGraphVisitationStrategy;
import org.hibernate.loader.plan.spi.visit.ReturnGraphVisitationStrategyAdapter;
import org.hibernate.loader.spi.LoadPlanAdvisor;
/**
* @author Steve Ebersole
*/
public class EntityGraphBasedLoadPlanAdvisor implements LoadPlanAdvisor {
private static final Logger log = Logger.getLogger( EntityGraphBasedLoadPlanAdvisor.class );
private final EntityGraphImpl root;
private final AdviceStyle adviceStyle;
public EntityGraphBasedLoadPlanAdvisor(EntityGraphImpl root, AdviceStyle adviceStyle) {
if ( root == null ) {
throw new IllegalArgumentException( "EntityGraph cannot be null" );
}
this.root = root;
this.adviceStyle = adviceStyle;
}
public LoadPlan advise(LoadPlan loadPlan) {
if ( root == null ) {
log.debug( "Skipping load plan advising: no entity graph was specified" );
}
else {
// for now, lets assume that the graph and the load-plan returns have to match up
EntityReturn entityReturn = findRootEntityReturn( loadPlan );
if ( entityReturn == null ) {
log.debug( "Skipping load plan advising: not able to find appropriate root entity return in load plan" );
}
else {
final String entityName = entityReturn.getEntityPersister().getEntityName();
if ( ! root.appliesTo( entityName ) ) {
log.debugf(
"Skipping load plan advising: entity types did not match : [%s] and [%s]",
root.getEntityType().getName(),
entityName
);
}
else {
// ok to apply the advice
return applyAdvice( entityReturn );
}
}
}
// return the original load-plan
return loadPlan;
}
private LoadPlan applyAdvice(final EntityReturn entityReturn) {
final EntityReturn copy = entityReturn.makeCopy( new CopyContextImpl( entityReturn ) );
return new LoadPlanImpl( copy );
}
private EntityReturn findRootEntityReturn(LoadPlan loadPlan) {
EntityReturn rootEntityReturn = null;
for ( Return rtn : loadPlan.getReturns() ) {
if ( ! EntityReturn.class.isInstance( rtn ) ) {
continue;
}
if ( rootEntityReturn != null ) {
log.debug( "Multiple EntityReturns were found" );
return null;
}
rootEntityReturn = (EntityReturn) rtn;
}
if ( rootEntityReturn == null ) {
log.debug( "Unable to find root entity return in load plan" );
}
return rootEntityReturn;
}
public static enum AdviceStyle {
FETCH,
LOAD
}
public class CopyContextImpl implements CopyContext {
private final ReturnGraphVisitationStrategyImpl strategy;
public CopyContextImpl(EntityReturn entityReturn) {
strategy = new ReturnGraphVisitationStrategyImpl( entityReturn );
}
@Override
public ReturnGraphVisitationStrategy getReturnGraphVisitationStrategy() {
return strategy;
}
}
public class ReturnGraphVisitationStrategyImpl extends ReturnGraphVisitationStrategyAdapter {
private ArrayDeque<NodeDescriptor> nodeStack = new ArrayDeque<NodeDescriptor>();
public ReturnGraphVisitationStrategyImpl(EntityReturn entityReturn) {
nodeStack.addFirst( new EntityReferenceDescriptor( entityReturn, new RootEntityGraphNode( root ) ) );
}
@Override
public void finishingRootReturn(Return rootReturn) {
nodeStack.removeFirst();
super.finishingRootReturn( rootReturn );
}
@Override
public void finishingFetches(FetchOwner fetchOwner) {
nodeStack.peekFirst().applyMissingFetches();
super.finishingFetches( fetchOwner );
}
@Override
public void startingEntityFetch(EntityFetch entityFetch) {
super.startingEntityFetch( entityFetch );
final NodeDescriptor currentNode = nodeStack.peekFirst();
final String attributeName = entityFetch.getOwnerPropertyName();
final JpaGraphReference fetchedGraphReference = currentNode.attributeProcessed( attributeName );
nodeStack.addFirst( new EntityReferenceDescriptor( entityFetch, fetchedGraphReference ) );
}
@Override
public void finishingEntityFetch(EntityFetch entityFetch) {
nodeStack.removeFirst();
super.finishingEntityFetch( entityFetch );
}
@Override
public void startingCollectionFetch(CollectionFetch collectionFetch) {
super.startingCollectionFetch( collectionFetch ); //To change body of overridden methods use File | Settings | File Templates.
}
@Override
public void finishingCollectionFetch(CollectionFetch collectionFetch) {
super.finishingCollectionFetch( collectionFetch ); //To change body of overridden methods use File | Settings | File Templates.
}
@Override
public void startingCompositeFetch(CompositeFetch fetch) {
super.startingCompositeFetch( fetch ); //To change body of overridden methods use File | Settings | File Templates.
}
@Override
public void finishingCompositeFetch(CompositeFetch fetch) {
super.finishingCompositeFetch( fetch ); //To change body of overridden methods use File | Settings | File Templates.
}
}
private static interface NodeDescriptor {
public JpaGraphReference attributeProcessed(String attributeName);
public void applyMissingFetches();
}
private static abstract class AbstractNodeDescriptor implements NodeDescriptor {
private final FetchOwner fetchOwner;
private final JpaGraphReference jpaGraphReference;
protected AbstractNodeDescriptor(FetchOwner fetchOwner, JpaGraphReference jpaGraphReference) {
this.fetchOwner = fetchOwner;
this.jpaGraphReference = jpaGraphReference;
}
@Override
public JpaGraphReference attributeProcessed(String attributeName) {
if ( jpaGraphReference != null ) {
return jpaGraphReference.attributeProcessed( attributeName );
}
else {
return null;
}
}
@Override
public void applyMissingFetches() {
if ( jpaGraphReference != null ) {
jpaGraphReference.applyMissingFetches( fetchOwner );
}
}
}
private static class EntityReferenceDescriptor extends AbstractNodeDescriptor {
private EntityReferenceDescriptor(EntityReturn entityReturn, JpaGraphReference correspondingJpaGraphNode) {
super( entityReturn, correspondingJpaGraphNode );
}
@SuppressWarnings("unchecked")
public EntityReferenceDescriptor(EntityFetch entityFetch, JpaGraphReference jpaGraphReference) {
super( entityFetch, jpaGraphReference );
}
}
private static interface JpaGraphReference {
public JpaGraphReference attributeProcessed(String attributeName);
public void applyMissingFetches(FetchOwner fetchOwner);
}
private static class RootEntityGraphNode implements JpaGraphReference {
private final Map<String,AttributeNodeImplementor> graphAttributeMap;
private RootEntityGraphNode(EntityGraphImpl entityGraph) {
graphAttributeMap = new HashMap<String, AttributeNodeImplementor>();
final List<AttributeNodeImplementor<?>> explicitAttributeNodes = entityGraph.attributeImplementorNodes();
if ( explicitAttributeNodes != null ) {
for ( AttributeNodeImplementor node : explicitAttributeNodes ) {
graphAttributeMap.put( node.getAttributeName(), node );
}
}
}
@Override
public JpaGraphReference attributeProcessed(String attributeName) {
final AttributeNodeImplementor attributeNode = graphAttributeMap.remove( attributeName );
if ( attributeNode == null ) {
return null;
}
return new SubGraphNode( attributeNode );
}
@Override
public void applyMissingFetches(FetchOwner fetchOwner) {
for ( AttributeNodeImplementor attributeNode : graphAttributeMap.values() ) {
System.out.println( "Found unprocessed attribute node : " + attributeNode.getAttributeName() );
}
}
}
private static class SubGraphNode implements JpaGraphReference {
protected SubGraphNode(AttributeNodeImplementor attributeNode) {
}
@Override
public JpaGraphReference attributeProcessed(String attributeName) {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void applyMissingFetches(FetchOwner fetchOwner) {
//To change body of implemented methods use File | Settings | File Templates.
}
}
}

View File

@ -23,6 +23,7 @@
*/ */
package org.hibernate.jpa.graph.spi; package org.hibernate.jpa.graph.spi;
import javax.persistence.AttributeNode;
import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.Attribute;
import org.hibernate.jpa.HibernateEntityManagerFactory; import org.hibernate.jpa.HibernateEntityManagerFactory;
@ -30,7 +31,7 @@ import org.hibernate.jpa.HibernateEntityManagerFactory;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface AttributeNodeImplementor<T> { public interface AttributeNodeImplementor<T> extends AttributeNode<T> {
public HibernateEntityManagerFactory entityManagerFactory(); public HibernateEntityManagerFactory entityManagerFactory();
public Attribute<?,T> getAttribute(); public Attribute<?,T> getAttribute();

View File

@ -1,107 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.jpa.graph.spi;
import java.util.List;
import org.jboss.logging.Logger;
import org.hibernate.jpa.graph.internal.EntityGraphImpl;
import org.hibernate.loader.plan.spi.EntityReturn;
import org.hibernate.loader.plan.spi.LoadPlan;
import org.hibernate.loader.plan.spi.Return;
import org.hibernate.loader.spi.LoadPlanAdvisor;
/**
* @author Steve Ebersole
*/
public class EntityGraphBasedLoadPlanAdvisor implements LoadPlanAdvisor {
private static final Logger log = Logger.getLogger( EntityGraphBasedLoadPlanAdvisor.class );
private final EntityGraphImpl root;
public EntityGraphBasedLoadPlanAdvisor(EntityGraphImpl root) {
if ( root == null ) {
throw new IllegalArgumentException( "EntityGraph cannot be null" );
}
this.root = root;
}
public LoadPlan advise(LoadPlan loadPlan) {
if ( root == null ) {
log.debug( "Skipping load plan advising: no entity graph was specified" );
}
else {
// for now, lets assume that the graph and the load-plan returns have to match up
EntityReturn entityReturn = findRootEntityReturn( loadPlan );
if ( entityReturn == null ) {
log.debug( "Skipping load plan advising: not able to find appropriate root entity return in load plan" );
}
else {
final String entityName = entityReturn.getEntityPersister().getEntityName();
if ( ! root.appliesTo( entityName ) ) {
log.debugf(
"Skipping load plan advising: entity types did not match : [%s] and [%s]",
root.getEntityType().getName(),
entityName
);
}
else {
// ok to apply the advice
return applyAdvice( entityReturn );
}
}
}
// return the original load-plan
return loadPlan;
}
private LoadPlan applyAdvice(final EntityReturn entityReturn) {
// final EntityReturn copy = entityReturn.makeCopy( )
return null;
}
private EntityReturn findRootEntityReturn(LoadPlan loadPlan) {
EntityReturn rootEntityReturn = null;
for ( Return rtn : loadPlan.getReturns() ) {
if ( ! EntityReturn.class.isInstance( rtn ) ) {
continue;
}
if ( rootEntityReturn != null ) {
log.debug( "Multiple EntityReturns were found" );
return null;
}
rootEntityReturn = (EntityReturn) rtn;
}
if ( rootEntityReturn == null ) {
log.debug( "Unable to find root entity return in load plan" );
}
return rootEntityReturn;
}
}

View File

@ -35,5 +35,6 @@ import org.hibernate.jpa.HibernateEntityManagerFactory;
*/ */
public interface GraphNodeImplementor { public interface GraphNodeImplementor {
public HibernateEntityManagerFactory entityManagerFactory(); public HibernateEntityManagerFactory entityManagerFactory();
public List<AttributeNodeImplementor<?>> attributeImplementorNodes();
public List<AttributeNode<?>> attributeNodes(); public List<AttributeNode<?>> attributeNodes();
} }

View File

@ -0,0 +1,138 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.jpa.test.graphs;
import javax.persistence.Entity;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Subgraph;
import java.util.Set;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jpa.graph.internal.EntityGraphImpl;
import org.hibernate.jpa.graph.internal.advisor.EntityGraphBasedLoadPlanAdvisor;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.loader.DefaultEntityAliases;
import org.hibernate.loader.plan.internal.LoadPlanImpl;
import org.hibernate.loader.plan.spi.EntityReturn;
import org.hibernate.loader.plan.spi.LoadPlan;
import org.hibernate.loader.spi.LoadPlanAdvisor;
import org.hibernate.persister.entity.Loadable;
import org.junit.Test;
import static org.hibernate.jpa.graph.internal.advisor.EntityGraphBasedLoadPlanAdvisor.AdviceStyle;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* @author Steve Ebersole
*/
public class BasicGraphLoadPlanAdviceTests extends BaseEntityManagerFunctionalTestCase {
private static final String ENTIYT_NAME = "org.hibernate.jpa.test.graphs.BasicGraphLoadPlanAdviceTests$Entity1";
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { Entity1.class };
}
public void testNoAdvice() {
EntityManager em = getOrCreateEntityManager();
}
private LoadPlan buildBasicLoadPlan() {
return new LoadPlanImpl(
new EntityReturn(
sfi(),
"abc",
LockMode.NONE,
ENTIYT_NAME,
"a1",
new DefaultEntityAliases( (Loadable) sfi().getEntityPersister( ENTIYT_NAME ), "1" )
)
);
}
private SessionFactoryImplementor sfi() {
return entityManagerFactory().unwrap( SessionFactoryImplementor.class );
}
@Test
public void testBasicGraphBuilding() {
EntityManager em = getOrCreateEntityManager();
EntityGraph<Entity1> graphRoot = em.createEntityGraph( Entity1.class );
assertNull( graphRoot.getName() );
assertEquals( 0, graphRoot.getAttributeNodes().size() );
LoadPlan loadPlan = buildBasicLoadPlan();
LoadPlan advised = buildAdvisor( graphRoot, AdviceStyle.FETCH ).advise( loadPlan );
assertNotSame( advised, loadPlan );
}
private LoadPlanAdvisor buildAdvisor(EntityGraph<Entity1> graphRoot, AdviceStyle adviceStyle) {
return new EntityGraphBasedLoadPlanAdvisor( (EntityGraphImpl) graphRoot, adviceStyle );
}
@Test
public void testBasicSubgraphBuilding() {
EntityManager em = getOrCreateEntityManager();
EntityGraph<Entity1> graphRoot = em.createEntityGraph( Entity1.class );
Subgraph<Entity1> parentGraph = graphRoot.addSubgraph( "parent" );
Subgraph<Entity1> childGraph = graphRoot.addSubgraph( "children" );
assertNull( graphRoot.getName() );
assertEquals( 2, graphRoot.getAttributeNodes().size() );
assertTrue(
graphRoot.getAttributeNodes().get( 0 ).getSubgraphs().containsValue( parentGraph )
|| graphRoot.getAttributeNodes().get( 0 ).getSubgraphs().containsValue( childGraph )
);
assertTrue(
graphRoot.getAttributeNodes().get( 1 ).getSubgraphs().containsValue( parentGraph )
|| graphRoot.getAttributeNodes().get( 1 ).getSubgraphs().containsValue( childGraph )
);
LoadPlan loadPlan = buildBasicLoadPlan();
LoadPlan advised = buildAdvisor( graphRoot, AdviceStyle.FETCH ).advise( loadPlan );
assertNotSame( advised, loadPlan );
}
@Entity( name = "Entity1" )
public static class Entity1 {
@Id
public Integer id;
public String name;
@ManyToOne
public Entity1 parent;
@OneToMany( mappedBy = "parent" )
public Set<Entity1> children;
}
}