From dfe33ffa1a6f17c973783e464fcd6437a9e629e7 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 25 Apr 2013 12:07:52 -0700 Subject: [PATCH] HHH-7841 - Redesign Loader --- .../loader/plan/spi/CompositeFetch.java | 3 +- .../entity/AbstractEntityPersister.java | 57 ++- .../AbstractCompositionDefinition.java | 36 +- ...positeAttributeResultSetProcessorTest.java | 338 ++++++++++++++++++ ...LazyCollectionResultSetProcessorTest.java} | 2 +- ...zyOneToManyListResultSetProcessorTest.java | 215 +++++++++++ ...zyOneToManySetResultSetProcessorTest.java} | 2 +- 7 files changed, 629 insertions(+), 24 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeAttributeResultSetProcessorTest.java rename hibernate-core/src/test/java/org/hibernate/loader/{EntityWithCollectionResultSetProcessorTest.java => EntityWithNonLazyCollectionResultSetProcessorTest.java} (98%) create mode 100644 hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManyListResultSetProcessorTest.java rename hibernate-core/src/test/java/org/hibernate/loader/{EntityWithOneToManyResultSetProcessorTest.java => EntityWithNonLazyOneToManySetResultSetProcessorTest.java} (98%) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java index 0d1a9fddd0..68e0c19291 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java @@ -31,6 +31,7 @@ import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.plan.internal.LoadPlanBuildingHelper; import org.hibernate.loader.plan.spi.build.LoadPlanBuildingContext; import org.hibernate.loader.spi.ResultSetProcessingContext; import org.hibernate.persister.entity.EntityPersister; @@ -78,7 +79,7 @@ public class CompositeFetch extends AbstractSingularAttributeFetch { @Override public CompositeFetch buildCompositeFetch( CompositionDefinition attributeDefinition, LoadPlanBuildingContext loadPlanBuildingContext) { - return null; //To change body of implemented methods use File | Settings | File Templates. + return LoadPlanBuildingHelper.buildStandardCompositeFetch( this, attributeDefinition, loadPlanBuildingContext ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 249c141061..e2cb4a1108 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -111,6 +111,7 @@ import org.hibernate.metamodel.relational.DerivedValue; import org.hibernate.metamodel.relational.Value; import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.AttributeSource; +import org.hibernate.persister.walking.spi.CompositionDefinition; import org.hibernate.persister.walking.spi.EncapsulatedEntityIdentifierDefinition; import org.hibernate.persister.walking.spi.EntityDefinition; import org.hibernate.persister.walking.spi.EntityIdentifierDefinition; @@ -130,6 +131,7 @@ import org.hibernate.sql.Update; import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.tuple.entity.EntityTuplizer; import org.hibernate.type.AssociationType; +import org.hibernate.type.ComponentType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -5120,13 +5122,13 @@ public abstract class AbstractEntityPersister final Type idType = getIdentifierType(); if ( !idType.isComponentType() ) { - entityIdentifierDefinition = buildEncapsulatedIdentifierDefinition(); + entityIdentifierDefinition = buildSimpleEncapsulatedIdentifierDefinition(); return; } final CompositeType cidType = (CompositeType) idType; if ( !cidType.isEmbedded() ) { - entityIdentifierDefinition = buildEncapsulatedIdentifierDefinition(); + entityIdentifierDefinition = buildEncapsulatedCompositeIdentifierDefinition(); return; } @@ -5155,7 +5157,7 @@ public abstract class AbstractEntityPersister }; } - private EntityIdentifierDefinition buildEncapsulatedIdentifierDefinition() { + private EntityIdentifierDefinition buildSimpleEncapsulatedIdentifierDefinition() { final AttributeDefinition simpleIdentifierAttributeAdapter = new AttributeDefinition() { @Override public String getName() { @@ -5196,6 +5198,55 @@ public abstract class AbstractEntityPersister }; } + private EntityIdentifierDefinition buildEncapsulatedCompositeIdentifierDefinition() { + final CompositionDefinition compositeIdentifierAttributeAdapter = new CompositionDefinition() { + @Override + public String getName() { + return entityMetamodel.getIdentifierProperty().getName(); + } + + @Override + public Type getType() { + return entityMetamodel.getIdentifierProperty().getType(); + } + + @Override + public AttributeSource getSource() { + return AbstractEntityPersister.this; + } + + @Override + public String toString() { + return ""; + } + + @Override + public Iterable getAttributes() { + ComponentType componentType = (ComponentType) getType(); + //for ( Type type : componentType.getSubtypes() ) { + throw new NotYetImplementedException( "cannot create sub-attribute definitions for a ComponentType yet." ); + //} + } + }; + + return new EncapsulatedEntityIdentifierDefinition() { + @Override + public AttributeDefinition getAttributeDefinition() { + return compositeIdentifierAttributeAdapter; + } + + @Override + public boolean isEncapsulated() { + return true; + } + + @Override + public EntityDefinition getEntityDefinition() { + return AbstractEntityPersister.this; + } + }; + } + private void collectAttributeDefinitions() { // todo : leverage the attribute definitions housed on EntityMetamodel // for that to work, we'd have to be able to walk our super entity persister(s) diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionDefinition.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionDefinition.java index 0584f843ff..3f4dcf216e 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionDefinition.java @@ -72,21 +72,21 @@ public abstract class AbstractCompositionDefinition extends AbstractNonIdentifie public Iterator iterator() { return new Iterator() { private final int numberOfAttributes = getType().getSubtypes().length; - private int currentAttributeNumber = 0; + private int currentSubAttributeNumber = 0; private int currentColumnPosition = 0; @Override public boolean hasNext() { - return currentAttributeNumber < numberOfAttributes; + return currentSubAttributeNumber < numberOfAttributes; } @Override public AttributeDefinition next() { - final int attributeNumber = currentAttributeNumber; - currentAttributeNumber++; + final int subAttributeNumber = currentSubAttributeNumber; + currentSubAttributeNumber++; - final String name = getType().getPropertyNames()[attributeNumber]; - final Type type = getType().getSubtypes()[attributeNumber]; + final String name = getType().getPropertyNames()[subAttributeNumber]; + final Type type = getType().getSubtypes()[subAttributeNumber]; int columnPosition = currentColumnPosition; currentColumnPosition += type.getColumnSpan( sessionFactory() ); @@ -122,7 +122,7 @@ public abstract class AbstractCompositionDefinition extends AbstractNonIdentifie return new CompositeBasedAssociationAttribute( AbstractCompositionDefinition.this, sessionFactory(), - currentAttributeNumber, + subAttributeNumber, name, (AssociationType) type, new BaselineAttributeInformation.Builder() @@ -130,11 +130,11 @@ public abstract class AbstractCompositionDefinition extends AbstractNonIdentifie .setUpdateable( AbstractCompositionDefinition.this.isUpdateable() ) .setInsertGenerated( AbstractCompositionDefinition.this.isInsertGenerated() ) .setUpdateGenerated( AbstractCompositionDefinition.this.isUpdateGenerated() ) - .setNullable( getType().getPropertyNullability()[currentAttributeNumber] ) + .setNullable( getType().getPropertyNullability()[subAttributeNumber] ) .setDirtyCheckable( true ) .setVersionable( AbstractCompositionDefinition.this.isVersionable() ) - .setCascadeStyle( getType().getCascadeStyle( currentAttributeNumber ) ) - .setFetchMode( getType().getFetchMode( currentAttributeNumber ) ) + .setCascadeStyle( getType().getCascadeStyle( subAttributeNumber ) ) + .setFetchMode( getType().getFetchMode( subAttributeNumber ) ) .createInformation(), AbstractCompositionDefinition.this.attributeNumber(), associationKey @@ -144,7 +144,7 @@ public abstract class AbstractCompositionDefinition extends AbstractNonIdentifie return new CompositionBasedCompositionAttribute( AbstractCompositionDefinition.this, sessionFactory(), - currentAttributeNumber, + subAttributeNumber, name, (CompositeType) type, new BaselineAttributeInformation.Builder() @@ -152,11 +152,11 @@ public abstract class AbstractCompositionDefinition extends AbstractNonIdentifie .setUpdateable( AbstractCompositionDefinition.this.isUpdateable() ) .setInsertGenerated( AbstractCompositionDefinition.this.isInsertGenerated() ) .setUpdateGenerated( AbstractCompositionDefinition.this.isUpdateGenerated() ) - .setNullable( getType().getPropertyNullability()[currentAttributeNumber] ) + .setNullable( getType().getPropertyNullability()[subAttributeNumber] ) .setDirtyCheckable( true ) .setVersionable( AbstractCompositionDefinition.this.isVersionable() ) - .setCascadeStyle( getType().getCascadeStyle( currentAttributeNumber ) ) - .setFetchMode( getType().getFetchMode( currentAttributeNumber ) ) + .setCascadeStyle( getType().getCascadeStyle( subAttributeNumber ) ) + .setFetchMode( getType().getFetchMode( subAttributeNumber ) ) .createInformation() ); } @@ -164,7 +164,7 @@ public abstract class AbstractCompositionDefinition extends AbstractNonIdentifie return new CompositeBasedBasicAttribute( AbstractCompositionDefinition.this, sessionFactory(), - currentAttributeNumber, + subAttributeNumber, name, type, new BaselineAttributeInformation.Builder() @@ -172,11 +172,11 @@ public abstract class AbstractCompositionDefinition extends AbstractNonIdentifie .setUpdateable( AbstractCompositionDefinition.this.isUpdateable() ) .setInsertGenerated( AbstractCompositionDefinition.this.isInsertGenerated() ) .setUpdateGenerated( AbstractCompositionDefinition.this.isUpdateGenerated() ) - .setNullable( getType().getPropertyNullability()[currentAttributeNumber] ) + .setNullable( getType().getPropertyNullability()[subAttributeNumber] ) .setDirtyCheckable( true ) .setVersionable( AbstractCompositionDefinition.this.isVersionable() ) - .setCascadeStyle( getType().getCascadeStyle( currentAttributeNumber ) ) - .setFetchMode( getType().getFetchMode( currentAttributeNumber ) ) + .setCascadeStyle( getType().getCascadeStyle( subAttributeNumber ) ) + .setFetchMode( getType().getFetchMode( subAttributeNumber ) ) .createInformation() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeAttributeResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeAttributeResultSetProcessorTest.java new file mode 100644 index 0000000000..3f76e0607b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeAttributeResultSetProcessorTest.java @@ -0,0 +1,338 @@ +/* + * 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.loader; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.Id; + +import org.junit.Test; + +import org.hibernate.Session; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.jdbc.Work; +import org.hibernate.loader.internal.EntityLoadQueryBuilderImpl; +import org.hibernate.loader.internal.LoadQueryAliasResolutionContextImpl; +import org.hibernate.loader.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.internal.SingleRootReturnLoadPlanBuilderStrategy; +import org.hibernate.loader.plan.spi.LoadPlan; +import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; +import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; +import org.hibernate.loader.spi.NamedParameterContext; +import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.ExtraAssertions; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +/** + * @author Gail Badner + */ +public class EncapsulatedCompositeAttributeResultSetProcessorTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Customer.class }; + } + + @Test + public void testSimpleNestedCompositeAttributeProcessing() throws Exception { + // create some test data + Session session = openSession(); + session.beginTransaction(); + Person person = new Person(); + person.id = 1; + person.name = "Joe Blow"; + person.address = new Address(); + person.address.address1 = "1313 Mockingbird Lane"; + person.address.city = "Pleasantville"; + person.address.country = "USA"; + AddressType addressType = new AddressType(); + addressType.typeName = "snail mail"; + person.address.type = addressType; + session.save( person ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + Person personGotten = (Person) session.get( Person.class, person.id ); + assertEquals( person.id, personGotten.id ); + assertEquals( person.address.address1, personGotten.address.address1 ); + assertEquals( person.address.city, personGotten.address.city ); + assertEquals( person.address.country, personGotten.address.country ); + assertEquals( person.address.type.typeName, personGotten.address.type.typeName ); + session.getTransaction().commit(); + session.close(); + + List results = getResults( sessionFactory().getEntityPersister( Person.class.getName() ) ); + assertEquals( 1, results.size() ); + Object result = results.get( 0 ); + assertNotNull( result ); + + Person personWork = ExtraAssertions.assertTyping( Person.class, result ); + assertEquals( person.id, personWork.id ); + assertEquals( person.address.address1, personWork.address.address1 ); + assertEquals( person.address.city, personWork.address.city ); + assertEquals( person.address.country, personWork.address.country ); + assertEquals( person.address.type.typeName, personGotten.address.type.typeName ); + + // clean up test data + session = openSession(); + session.beginTransaction(); + session.createQuery( "delete Person" ).executeUpdate(); + session.getTransaction().commit(); + session.close(); + } + + /* + @Test + public void testNestedCompositeElementCollectionProcessing() throws Exception { + // create some test data + Session session = openSession(); + session.beginTransaction(); + Customer customer = new Customer(); + customer.id = 1L; + Investment investment1 = new Investment(); + investment1.description = "stock"; + investment1.date = new Date(); + investment1.monetaryAmount = new MonetaryAmount(); + investment1.monetaryAmount.currency = MonetaryAmount.CurrencyCode.USD; + investment1.monetaryAmount.amount = BigDecimal.valueOf( 1234, 2 ); + Investment investment2 = new Investment(); + investment2.description = "bond"; + investment2.date = new Date(); + investment2.monetaryAmount = new MonetaryAmount(); + investment2.monetaryAmount.currency = MonetaryAmount.CurrencyCode.EUR; + investment2.monetaryAmount.amount = BigDecimal.valueOf( 98176, 1 ); + customer.investments.add( investment1 ); + customer.investments.add( investment2 ); + session.save( customer ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + Customer customerGotten = (Customer) session.get( Customer.class, customer.id ); + assertEquals( customer.id, customerGotten.id ); + session.getTransaction().commit(); + session.close(); + + List results = getResults( sessionFactory().getEntityPersister( Customer.class.getName() ) ); + + assertEquals( 2, results.size() ); + assertSame( results.get( 0 ), results.get( 1 ) ); + Object result = results.get( 0 ); + assertNotNull( result ); + + Customer customerWork = ExtraAssertions.assertTyping( Customer.class, result ); + + // clean up test data + session = openSession(); + session.beginTransaction(); + session.createQuery( "delete Customer" ).executeUpdate(); + session.getTransaction().commit(); + session.close(); + } + */ + + private List getResults(EntityPersister entityPersister ) { + final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( + sessionFactory(), + LoadQueryInfluencers.NONE + ); + final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); + final LoadQueryAliasResolutionContext aliasResolutionContext = + new LoadQueryAliasResolutionContextImpl( + sessionFactory(), + 0, + Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) + ); + final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( + LoadQueryInfluencers.NONE, + plan + ); + final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); + final List results = new ArrayList(); + + final Session workSession = openSession(); + workSession.beginTransaction(); + workSession.doWork( + new Work() { + @Override + public void execute(Connection connection) throws SQLException { + PreparedStatement ps = connection.prepareStatement( sql ); + ps.setInt( 1, 1 ); + ResultSet resultSet = ps.executeQuery(); + results.addAll( + resultSetProcessor.extractResults( + NoOpLoadPlanAdvisor.INSTANCE, + resultSet, + (SessionImplementor) workSession, + new QueryParameters(), + new NamedParameterContext() { + @Override + public int[] getNamedParameterLocations(String name) { + return new int[0]; + } + }, + aliasResolutionContext, + true, + false, + null, + null + ) + ); + resultSet.close(); + ps.close(); + } + } + ); + workSession.getTransaction().commit(); + workSession.close(); + return results; + } + + @Entity( name = "Person" ) + public static class Person implements Serializable { + @Id + Integer id; + String name; + + @Embedded + Address address; + } + + @Embeddable + public static class Address implements Serializable { + String address1; + String city; + String country; + AddressType type; + } + + @Embeddable + public static class AddressType { + String typeName; + } + + @Entity( name = "Customer" ) + public static class Customer { + private Long id; + private List investments = new ArrayList(); + + @Id + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + + @ElementCollection(fetch = FetchType.EAGER) + public List getInvestments() { + return investments; + } + public void setInvestments(List investments) { + this.investments = investments; + } + } + + @Embeddable + public static class Investment { + private MonetaryAmount monetaryAmount; + private String description; + private Date date; + + @Embedded + public MonetaryAmount getMonetaryAmount() { + return monetaryAmount; + } + public void setMonetaryAmount(MonetaryAmount monetaryAmount) { + this.monetaryAmount = monetaryAmount; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public Date getDate() { + return date; + } + public void setDate(Date date) { + this.date = date; + } + } + + @Embeddable + public static class MonetaryAmount { + public static enum CurrencyCode { + USD, + EUR + } + private BigDecimal amount; + @Column(length = 3) + @Enumerated(EnumType.STRING) + private CurrencyCode currency; + + public BigDecimal getAmount() { + return amount; + } + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public CurrencyCode getCurrency() { + return currency; + } + public void setCurrency(CurrencyCode currency) { + this.currency = currency; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithCollectionResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyCollectionResultSetProcessorTest.java similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/loader/EntityWithCollectionResultSetProcessorTest.java rename to hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyCollectionResultSetProcessorTest.java index da1186e0f6..dfc467ccda 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithCollectionResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyCollectionResultSetProcessorTest.java @@ -67,7 +67,7 @@ import static org.junit.Assert.assertTrue; /** * @author Gail Badner */ -public class EntityWithCollectionResultSetProcessorTest extends BaseCoreFunctionalTestCase { +public class EntityWithNonLazyCollectionResultSetProcessorTest extends BaseCoreFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManyListResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManyListResultSetProcessorTest.java new file mode 100644 index 0000000000..abd4dadf7f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManyListResultSetProcessorTest.java @@ -0,0 +1,215 @@ +/* + * 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.loader; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.junit.Test; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.jdbc.Work; +import org.hibernate.loader.internal.EntityLoadQueryBuilderImpl; +import org.hibernate.loader.internal.LoadQueryAliasResolutionContextImpl; +import org.hibernate.loader.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.internal.SingleRootReturnLoadPlanBuilderStrategy; +import org.hibernate.loader.plan.spi.LoadPlan; +import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; +import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; +import org.hibernate.loader.spi.NamedParameterContext; +import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.ExtraAssertions; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class EntityWithNonLazyOneToManyListResultSetProcessorTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Poster.class, Message.class }; + } + + @Test + public void testEntityWithList() throws Exception { + final EntityPersister entityPersister = sessionFactory().getEntityPersister( Poster.class.getName() ); + + // create some test data + Session session = openSession(); + session.beginTransaction(); + Poster poster = new Poster(); + poster.pid = 0; + poster.name = "John Doe"; + Message message1 = new Message(); + message1.mid = 1; + message1.msgTxt = "Howdy!"; + message1.poster = poster; + poster.messages.add( message1 ); + Message message2 = new Message(); + message2.mid = 2; + message2.msgTxt = "Bye!"; + message2.poster = poster; + poster.messages.add( message2 ); + session.save( poster ); + session.getTransaction().commit(); + session.close(); + + session = openSession(); + session.beginTransaction(); + Poster posterGotten = (Poster) session.get( Poster.class, poster.pid ); + assertEquals( 0, posterGotten.pid.intValue() ); + assertEquals( poster.name, posterGotten.name ); + assertTrue( Hibernate.isInitialized( posterGotten.messages ) ); + assertEquals( 2, posterGotten.messages.size() ); + assertEquals( message1.msgTxt, posterGotten.messages.get( 0 ).msgTxt ); + assertEquals( message2.msgTxt, posterGotten.messages.get( 1 ).msgTxt ); + assertSame( posterGotten, posterGotten.messages.get( 0 ).poster ); + assertSame( posterGotten, posterGotten.messages.get( 1 ).poster ); + session.getTransaction().commit(); + session.close(); + + { + final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( + sessionFactory(), + LoadQueryInfluencers.NONE + ); + final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); + final LoadQueryAliasResolutionContext aliasResolutionContext = + new LoadQueryAliasResolutionContextImpl( + sessionFactory(), + 0, + Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) + ); + final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( + LoadQueryInfluencers.NONE, + plan + ); + final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); + final List results = new ArrayList(); + + final Session workSession = openSession(); + workSession.beginTransaction(); + workSession.doWork( + new Work() { + @Override + public void execute(Connection connection) throws SQLException { + PreparedStatement ps = connection.prepareStatement( sql ); + ps.setInt( 1, 0 ); + ResultSet resultSet = ps.executeQuery(); + results.addAll( + resultSetProcessor.extractResults( + NoOpLoadPlanAdvisor.INSTANCE, + resultSet, + (SessionImplementor) workSession, + new QueryParameters(), + new NamedParameterContext() { + @Override + public int[] getNamedParameterLocations(String name) { + return new int[0]; + } + }, + aliasResolutionContext, + true, + false, + null, + null + ) + ); + resultSet.close(); + ps.close(); + } + } + ); + assertEquals( 2, results.size() ); + Object result1 = results.get( 0 ); + assertNotNull( result1 ); + assertSame( result1, results.get( 1 ) ); + + Poster workPoster = ExtraAssertions.assertTyping( Poster.class, result1 ); + assertEquals( 0, workPoster.pid.intValue() ); + assertEquals( poster.name, workPoster.name ); + assertTrue( Hibernate.isInitialized( workPoster.messages ) ); + assertEquals( 2, workPoster.messages.size() ); + assertTrue( Hibernate.isInitialized( posterGotten.messages ) ); + assertEquals( 2, workPoster.messages.size() ); + assertEquals( message1.msgTxt, workPoster.messages.get( 0 ).msgTxt ); + assertEquals( message2.msgTxt, workPoster.messages.get( 1 ).msgTxt ); + assertSame( workPoster, workPoster.messages.get( 0 ).poster ); + assertSame( workPoster, workPoster.messages.get( 1 ).poster ); + workSession.getTransaction().commit(); + workSession.close(); + } + + // clean up test data + session = openSession(); + session.beginTransaction(); + session.delete( poster ); + session.getTransaction().commit(); + session.close(); + } + + @Entity( name = "Message" ) + public static class Message { + @Id + private Integer mid; + private String msgTxt; + @ManyToOne + @JoinColumn + private Poster poster; + } + + @Entity( name = "Poster" ) + public static class Poster { + @Id + private Integer pid; + private String name; + @OneToMany(mappedBy = "poster", fetch = FetchType.EAGER, cascade = CascadeType.ALL ) + private List messages = new ArrayList(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithOneToManyResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManySetResultSetProcessorTest.java similarity index 98% rename from hibernate-core/src/test/java/org/hibernate/loader/EntityWithOneToManyResultSetProcessorTest.java rename to hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManySetResultSetProcessorTest.java index 23b3e860ee..eee117e602 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithOneToManyResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManySetResultSetProcessorTest.java @@ -70,7 +70,7 @@ import static org.junit.Assert.fail; /** * @author Gail Badner */ -public class EntityWithOneToManyResultSetProcessorTest extends BaseCoreFunctionalTestCase { +public class EntityWithNonLazyOneToManySetResultSetProcessorTest extends BaseCoreFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() {