diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java index e7f6758cf3..673038f007 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java @@ -21,14 +21,12 @@ import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.UniqueKeyLoadable; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.results.graph.AbstractFetchParentAccess; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Initializer; -import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; @@ -148,14 +146,21 @@ public class BatchEntitySelectFetchInitializer extends AbstractFetchParentAccess } persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( entityKey ); + List objects = getObjects(); + parentAccess.registerResolutionListener( + o -> objects.add( o ) + ); + + isInitialized = true; + } + + private List getObjects() { List objects = toBatchLoad.get( entityKey ); if ( objects == null ) { objects = new ArrayList<>(); toBatchLoad.put( entityKey, objects ); } - objects.add( parentAccess.getInitializedInstance() ); - - isInitialized = true; + return objects; } protected boolean isAttributeAssignableToConcreteDescriptor() { @@ -195,6 +200,11 @@ public class BatchEntitySelectFetchInitializer extends AbstractFetchParentAccess return entityKey; } + @Override + public EntityPersister findFirstEntityPersister() { + return getEntityDescriptor(); + } + @Override public Object getParentKey() { throw new NotYetImplementedFor6Exception( getClass() ); @@ -222,8 +232,6 @@ public class BatchEntitySelectFetchInitializer extends AbstractFetchParentAccess @Override public void endLoading(ExecutionContext context) { - final int propertyIndex = ( (UniqueKeyLoadable) ( (AbstractEntityInitializer) parentAccess ).getEntityDescriptor() ) - .getPropertyIndex( referencedModelPart.getPartName() ); toBatchLoad.forEach( (entityKey, parentInstances) -> { final Object instance = context.getSession().internalLoad( @@ -232,19 +240,14 @@ public class BatchEntitySelectFetchInitializer extends AbstractFetchParentAccess true, referencedModelPart.isInternalLoadNullable() ); + for ( Object parentInstance : parentInstances ) { - ( (AbstractEntityPersister) referencedModelPart.getDeclaringType() ).setPropertyValue( - parentInstance, - referencedModelPart.getPartName(), - instance - ); - final EntityEntry entry = context.getSession() - .getPersistenceContext() - .getEntry( parentInstance ); + referencedModelPart.getPropertyAccess().getSetter().set(parentInstance, instance); + final EntityEntry entry = context.getSession().getPersistenceContext().getEntry( parentInstance ); if ( entry != null ) { final Object[] loadedState = entry.getLoadedState(); if ( loadedState != null ) { - loadedState[propertyIndex] = instance; + loadedState[0] = instance; } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/batch/EmbeddableBatchingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batch/EmbeddableBatchingTest.java index 19ef16ece5..30b9677dcf 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/batch/EmbeddableBatchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batch/EmbeddableBatchingTest.java @@ -1,11 +1,18 @@ package org.hibernate.orm.test.batch; +import java.util.List; +import java.util.Objects; + import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.internal.SessionImpl; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import jakarta.persistence.Embeddable; @@ -13,56 +20,340 @@ import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.OneToOne; -import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + @Jpa( annotatedClasses = { - EmbeddableBatchingTest.A.class, - EmbeddableBatchingTest.C.class + EmbeddableBatchingTest.EntityA.class, + EmbeddableBatchingTest.EntityB.class, + EmbeddableBatchingTest.EntityC.class, + EmbeddableBatchingTest.EntityD.class }, integrationSettings = { @Setting(name = Environment.DEFAULT_BATCH_FETCH_SIZE, value = "2") } ) @TestForIssue(jiraKey = "HHH-15644") class EmbeddableBatchingTest { - @Test - void testSelect(EntityManagerFactoryScope scope) { - scope.inEntityManager( + private static final EntityC ENTITY_C = new EntityC( 2, true ); + private static final EntityB ENTITY_B = new EntityB( 1, 2f ); + private static final EntityD ENTITY_D = new EntityD( 3, (byte) 100 ); + + private static final EmbeddableA EMBEDDABLE_A = new EmbeddableA( "b1", ENTITY_B ); + private static final EmbeddableB EMBEDDABLE_B = new EmbeddableB( (short) 3, new EmbeddableC( 4, ENTITY_C ) ); + + private static final EntityA ENTITY_A = new EntityA( 4, "a", null, ENTITY_D, EMBEDDABLE_A, EMBEDDABLE_B ); + + @BeforeEach + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { - final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); - final CriteriaQuery c = cb.createQuery( A.class ); - c.from( A.class ); - entityManager.createQuery( c ).getResultList(); + entityManager.persist( ENTITY_B ); + entityManager.persist( ENTITY_C ); + entityManager.persist( ENTITY_D ); + entityManager.persist( ENTITY_A ); } ); } - @Entity - public static class A { + @Test + void testSelect(EntityManagerFactoryScope scope) { + scope.inEntityManager( + entityManager -> { + final CriteriaQuery query = entityManager.getCriteriaBuilder() + .createQuery( EntityA.class ); + query.from( EntityA.class ); + + List results = entityManager.createQuery( query ).getResultList(); + assertThat( results.size() ).isEqualTo( 1 ); + + EntityA entityA = results.get( 0 ); + + assertThat( entityA ).isEqualTo( ENTITY_A ); + + EntityEntry entry = ( (SessionImpl) entityManager ).getPersistenceContext().getEntry( entityA ); + Object[] loadedState = entry.getLoadedState(); + EntityPersister persister = ( (SessionImpl) entityManager ).getEntityPersister( + "EntityA", + entityA + ); + + assertThat( loadedState[persister.getPropertyIndex( "name" )] ).isEqualTo( entityA.name ); + assertThat( loadedState[persister.getPropertyIndex( "anotherName" )] ).isEqualTo( null ); + assertThat( loadedState[persister.getPropertyIndex( "entityD" )] ).isEqualTo( ENTITY_D ); + assertThat( loadedState[persister.getPropertyIndex( "embeddableA" )] ).isEqualTo( EMBEDDABLE_A ); + assertThat( loadedState[persister.getPropertyIndex( "embeddableB" )] ).isEqualTo( EMBEDDABLE_B ); + } + ); + } + + @Entity(name = "EntityA") + public static class EntityA { @Id private Integer id; - @Embedded - private B b; + private String name; + private String anotherName; + + @OneToOne + private EntityD entityD; + + @Embedded + private EmbeddableA embeddableA; + + @Embedded + private EmbeddableB embeddableB; + + public EntityA() { + } + + public EntityA( + Integer id, + String name, + String anotherName, + EntityD entityD, + EmbeddableA embeddableA, + EmbeddableB embeddableB) { + this.id = id; + this.name = name; + this.anotherName = anotherName; + this.entityD = entityD; + this.embeddableA = embeddableA; + this.embeddableB = embeddableB; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EntityA entityA = (EntityA) o; + return Objects.equals( id, entityA.id ) && Objects.equals( + name, + entityA.name + ) && Objects.equals( anotherName, entityA.anotherName ) && Objects.equals( + entityD, + entityA.entityD + ) && Objects.equals( embeddableA, entityA.embeddableA ) && Objects.equals( + embeddableB, + entityA.embeddableB + ); + } + + @Override + public int hashCode() { + return Objects.hash( id, name, anotherName, entityD, embeddableA, embeddableB ); + } } @Embeddable - public static class B { + public static class EmbeddableA { + + private String aString; @OneToOne - private C c; + private EntityB entityB; + public EmbeddableA() { + } + + public EmbeddableA(String aString, EntityB entityB) { + this.aString = aString; + this.entityB = entityB; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EmbeddableA that = (EmbeddableA) o; + return Objects.equals( aString, that.aString ) && Objects.equals( entityB, that.entityB ); + } + + @Override + public int hashCode() { + return Objects.hash( aString, entityB ); + } } - @Entity - public static class C { + @Embeddable + public static class EmbeddableB { + + private short aShort; + + @Embedded + private EmbeddableC d; + + public EmbeddableB() { + } + + public EmbeddableB(short aShort, EmbeddableC d) { + this.aShort = aShort; + this.d = d; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EmbeddableB that = (EmbeddableB) o; + return aShort == that.aShort && Objects.equals( d, that.d ); + } + + @Override + public int hashCode() { + return Objects.hash( aShort, d ); + } + } + + @Embeddable + public static class EmbeddableC { + + private int anInteger; + + @OneToOne + private EntityC entityC; + + public EmbeddableC() { + } + + public EmbeddableC(int anInteger, EntityC entityC) { + this.anInteger = anInteger; + this.entityC = entityC; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EmbeddableC that = (EmbeddableC) o; + return Objects.equals( anInteger, that.anInteger ) && Objects.equals( entityC, that.entityC ); + } + + @Override + public int hashCode() { + return Objects.hash( anInteger, entityC ); + } + } + + @Entity(name = "EntityB") + public static class EntityB { @Id private Integer id; + private Float aFloat; + + public EntityB() { + } + + public EntityB(Integer id, Float aFloat) { + this.id = id; + this.aFloat = aFloat; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EntityB entityB = (EntityB) o; + return Objects.equals( id, entityB.id ) && Objects.equals( aFloat, entityB.aFloat ); + } + + @Override + public int hashCode() { + return Objects.hash( id, aFloat ); + } + } + + @Entity(name = "EntityC") + public static class EntityC { + + @Id + private Integer id; + + private boolean aBoolean; + + public EntityC() { + } + + public EntityC(Integer id, boolean aBoolean) { + this.id = id; + this.aBoolean = aBoolean; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EntityC entityC = (EntityC) o; + return aBoolean == entityC.aBoolean && Objects.equals( id, entityC.id ); + } + + @Override + public int hashCode() { + return Objects.hash( id, aBoolean ); + } + } + + @Entity(name = "EntityD") + public static class EntityD { + + @Id + private Integer id; + + private byte aByte; + + public EntityD() { + } + + public EntityD(Integer id, byte aByte) { + this.id = id; + this.aByte = aByte; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EntityD entityD = (EntityD) o; + return aByte == entityD.aByte && Objects.equals( id, entityD.id ); + } + + @Override + public int hashCode() { + return Objects.hash( id, aByte ); + } } }