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 321f3d97e1d4..35975605b948 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 @@ -662,10 +662,6 @@ public FetchSource registeredFetchSource(AssociationKey associationKey) { @Override public void foundCircularAssociation(AssociationAttributeDefinition attributeDefinition) { final FetchStrategy fetchStrategy = determineFetchStrategy( attributeDefinition ); - if ( fetchStrategy.getStyle() != FetchStyle.JOIN ) { - return; // nothing to do - } - final AssociationKey associationKey = attributeDefinition.getAssociationKey(); // go ahead and build the bidirectional fetch diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java index fa6ab39bd046..3ca5bf195d6b 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java @@ -8,9 +8,9 @@ import java.util.Iterator; +import org.hibernate.FetchMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; -import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyles; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -122,6 +122,7 @@ public AttributeDefinition next() { final String name = compositeType.getPropertyNames()[subAttributeNumber]; final Type type = compositeType.getSubtypes()[subAttributeNumber]; + final FetchMode fetchMode = compositeType.getFetchMode( subAttributeNumber ); final int columnPosition = currentColumnPosition; final int columnSpan = type.getColumnSpan( ownerEntityPersister.getFactory() ); @@ -181,7 +182,19 @@ public CollectionDefinition toCollectionDefinition() { @Override public FetchStrategy determineFetchPlan(LoadQueryInfluencers loadQueryInfluencers, PropertyPath propertyPath) { - return new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.JOIN ); + final FetchStyle style = FetchStrategyHelper.determineFetchStyleByMetadata( + fetchMode, + (AssociationType) type, + ownerEntityPersister.getFactory() + ); + return new FetchStrategy( + FetchStrategyHelper.determineFetchTiming( + style, + getType(), + ownerEntityPersister.getFactory() + ), + style + ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/EmbeddableCollectionElementWithLazyManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/EmbeddableCollectionElementWithLazyManyToOneTest.java new file mode 100644 index 000000000000..ea094b0d13db --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/EmbeddableCollectionElementWithLazyManyToOneTest.java @@ -0,0 +1,165 @@ +/* + * 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.annotations.collectionelement; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class EmbeddableCollectionElementWithLazyManyToOneTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Test + @TestForIssue( jiraKey = "???") + public void testLazyManyToOneInEmbeddable() { + Parent p = new Parent(); + p.containedChild = new ContainedChild( new Child() ); + + doInHibernate( + this::sessionFactory, session -> { + session.persist( p ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Parent pRead = session.get( Parent.class, p.id ); + assertFalse( Hibernate.isInitialized( pRead.containedChild.child ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( p ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "???") + public void testLazyManyToOneInCollectionElementEmbeddable() { + Parent p = new Parent(); + p.containedChildren.add( new ContainedChild( new Child() ) ); + + doInHibernate( + this::sessionFactory, session -> { + session.persist( p ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Parent pRead = session.get( Parent.class, p.id ); + assertFalse( Hibernate.isInitialized( pRead.containedChildren ) ); + assertEquals( 1, pRead.containedChildren.size() ); + assertTrue( Hibernate.isInitialized( pRead.containedChildren ) ); + assertFalse( Hibernate.isInitialized( pRead.containedChildren.iterator().next().child ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( p ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "???") + public void testLazyBoth() { + Parent p = new Parent(); + ContainedChild containedChild = new ContainedChild( new Child() ); + p.containedChild = containedChild; + p.containedChildren.add( containedChild ); + + doInHibernate( + this::sessionFactory, session -> { + session.persist( p ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + Parent pRead = session.get( Parent.class, p.id ); + assertFalse( Hibernate.isInitialized( pRead.containedChild.child ) ); + assertFalse( Hibernate.isInitialized( pRead.containedChildren ) ); + assertEquals( 1, pRead.containedChildren.size() ); + assertTrue( Hibernate.isInitialized( pRead.containedChildren ) ); + assertFalse( Hibernate.isInitialized( pRead.containedChildren.iterator().next().child ) ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + session.delete( p ); + } + ); + } + + @Entity(name = "Parent") + public static class Parent { + @Id + @GeneratedValue + private int id; + + private ContainedChild containedChild; + + @ElementCollection + private Set containedChildren = new HashSet(); + } + + @Entity(name = "Child") + public static class Child { + @Id + @GeneratedValue + private int id; + + } + + @Embeddable + public static class ContainedChild { + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private Child child; + + ContainedChild() { + } + + ContainedChild(Child child) { + this.child = child; + } + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/keymanytoone/bidir/component/EagerCollectionLazyKeyManyToOneMapping.hbm.xml b/hibernate-core/src/test/java/org/hibernate/test/keymanytoone/bidir/component/EagerCollectionLazyKeyManyToOneMapping.hbm.xml new file mode 100644 index 000000000000..575505a0e76c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/keymanytoone/bidir/component/EagerCollectionLazyKeyManyToOneMapping.hbm.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/test/keymanytoone/bidir/component/EagerCollectionLazyKeyManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/keymanytoone/bidir/component/EagerCollectionLazyKeyManyToOneTest.java new file mode 100644 index 000000000000..8a9bd7aba391 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/keymanytoone/bidir/component/EagerCollectionLazyKeyManyToOneTest.java @@ -0,0 +1,110 @@ +/* + * 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.keymanytoone.bidir.component; + +import java.util.List; + +import org.hibernate.Session; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Steve Ebersole + */ +public class EagerCollectionLazyKeyManyToOneTest extends BaseCoreFunctionalTestCase { + @Override + public String[] getMappings() { + return new String[] { "keymanytoone/bidir/component/EagerCollectionLazyKeyManyToOneMapping.hbm.xml" }; + } + + @Override + public void configure(Configuration cfg) { + super.configure( cfg ); + cfg.setProperty( Environment.GENERATE_STATISTICS, "true" ); + } + + @Test + public void testQueryingOnMany2One() { + Session s = openSession(); + s.beginTransaction(); + Customer cust = new Customer( "Acme, Inc." ); + Order order = new Order( new Order.Id( cust, 1 ) ); + cust.getOrders().add( order ); + s.save( cust ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + List results = s.createQuery( "from Order o where o.id.customer.name = :name" ) + .setParameter( "name", cust.getName() ) + .list(); + assertEquals( 1, results.size() ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + s.delete( cust ); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testSaveCascadedToKeyManyToOne() { + // test cascading a save to an association with a key-many-to-one which refers to a + // just saved entity + Session s = openSession(); + s.beginTransaction(); + Customer cust = new Customer( "Acme, Inc." ); + Order order = new Order( new Order.Id( cust, 1 ) ); + cust.getOrders().add( order ); + sessionFactory().getStatistics().clear(); + s.save( cust ); + s.flush(); + assertEquals( 2, sessionFactory().getStatistics().getEntityInsertCount() ); + s.delete( cust ); + s.getTransaction().commit(); + s.close(); + } + + @Test + public void testLoadingStrategies() { + Session s = openSession(); + s.beginTransaction(); + Customer cust = new Customer( "Acme, Inc." ); + Order order = new Order( new Order.Id( cust, 1 ) ); + cust.getOrders().add( order ); + s.save( cust ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + + cust = ( Customer ) s.get( Customer.class, cust.getId() ); + assertEquals( 1, cust.getOrders().size() ); + s.clear(); + + cust = ( Customer ) s.createQuery( "from Customer" ).uniqueResult(); + assertEquals( 1, cust.getOrders().size() ); + s.clear(); + + cust = ( Customer ) s.createQuery( "from Customer c join fetch c.orders" ).uniqueResult(); + assertEquals( 1, cust.getOrders().size() ); + s.clear(); + + s.delete( cust ); + s.getTransaction().commit(); + s.close(); + } +}