diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchImpl.java index 6acde261f8..734d2b4555 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchImpl.java @@ -83,6 +83,8 @@ public class DelayedEntityFetchImpl implements Fetch { EntityInitializer entityInitializer = new DelayedEntityFetchInitializer( parentAccess, navigablePath, + fetchedAttribute.getMappedFetchStrategy(), + lockMode, (EntityPersister) fetchedAttribute.getMappedTypeDescriptor(), fkResult.createResultAssembler( collector, creationState ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchInitializer.java index 800e5a94e8..0f445aacb4 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchInitializer.java @@ -8,7 +8,10 @@ package org.hibernate.sql.results.internal.domain.entity; import java.util.function.Consumer; +import org.hibernate.LockMode; import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.FetchTiming; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.internal.domain.AbstractFetchParentAccess; @@ -25,6 +28,8 @@ public class DelayedEntityFetchInitializer extends AbstractFetchParentAccess imp private final FetchParentAccess parentAccess; private final NavigablePath navigablePath; + private FetchStrategy mappedFetchedStrategy; + private LockMode lockMode; private final EntityPersister concreteDescriptor; private final DomainResultAssembler fkValueAssembler; @@ -35,11 +40,15 @@ public class DelayedEntityFetchInitializer extends AbstractFetchParentAccess imp protected DelayedEntityFetchInitializer( FetchParentAccess parentAccess, NavigablePath fetchedNavigable, + FetchStrategy mappedFetchedStrategy, + LockMode lockMode, EntityPersister concreteDescriptor, DomainResultAssembler fkValueAssembler ) { this.parentAccess = parentAccess; this.navigablePath = fetchedNavigable; + this.mappedFetchedStrategy = mappedFetchedStrategy; + this.lockMode = lockMode; this.concreteDescriptor = concreteDescriptor; this.fkValueAssembler = fkValueAssembler; } @@ -66,19 +75,24 @@ public class DelayedEntityFetchInitializer extends AbstractFetchParentAccess imp entityInstance = null; } else { - if ( concreteDescriptor.hasProxy() ) { - entityInstance = concreteDescriptor.createProxy( - fkValue, - rowProcessingState.getSession() - ); + if ( mappedFetchedStrategy.getTiming() != FetchTiming.IMMEDIATE ) { + if ( concreteDescriptor.hasProxy() ) { + entityInstance = concreteDescriptor.createProxy( + fkValue, + rowProcessingState.getSession() + ); + } + else if ( concreteDescriptor + .getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading() ) { + entityInstance = concreteDescriptor.instantiate( + fkValue, + rowProcessingState.getSession() + ); + } } - else if ( concreteDescriptor - .getBytecodeEnhancementMetadata() - .isEnhancedForLazyLoading() ) { - entityInstance = concreteDescriptor.instantiate( - fkValue, - rowProcessingState.getSession() - ); + else { + entityInstance = concreteDescriptor.load( fkValue, null, lockMode, rowProcessingState.getSession() ); } notifyParentResolutionListeners( entityInstance ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/ManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/ManyToOneTest.java index ba8ae75881..9a075ec817 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/ManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/ManyToOneTest.java @@ -14,6 +14,7 @@ import javax.persistence.ManyToOne; import javax.persistence.Table; import org.hibernate.Hibernate; +import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.ServiceRegistry; @@ -41,17 +42,22 @@ import static org.junit.Assert.assertTrue; } ) @ServiceRegistry -@SessionFactory +@SessionFactory(generateStatistics = true) public class ManyToOneTest { @Test public void testHqlSelect(SessionFactoryScope scope) { + StatisticsImplementor statistics = scope.getSessionFactory().getStatistics(); + statistics.clear(); scope.inTransaction( session -> { + OtherEntity otherEntity = session. createQuery( "from OtherEntity", OtherEntity.class ) .uniqueResult(); + assertThat( statistics.getPrepareStatementCount(), is(1L) ); + assertThat( otherEntity.getName(), is( "Bar" ) ); SimpleEntity simpleEntity = otherEntity.getSimpleEntity(); assertFalse( Hibernate.isInitialized( simpleEntity ) ); @@ -61,6 +67,7 @@ public class ManyToOneTest { assertTrue( Hibernate.isInitialized( anotherSimpleEntity ) ); assertThat( simpleEntity.getName(), is( "Fab" ) ); + assertThat( statistics.getPrepareStatementCount(), is(2L) ); assertTrue( Hibernate.isInitialized( simpleEntity ) ); } @@ -79,30 +86,37 @@ public class ManyToOneTest { } ); + statistics.clear(); + scope.inTransaction( session -> { OtherEntity otherEntity = session. createQuery( "from OtherEntity", OtherEntity.class ) .uniqueResult(); + // the ManyToOne is eager but the value is not null so a second query is executed + assertThat( statistics.getPrepareStatementCount(), is(2L) ); assertThat( otherEntity.getName(), is( "Bar" ) ); SimpleEntity simpleEntity = otherEntity.getSimpleEntity(); assertFalse( Hibernate.isInitialized( simpleEntity ) ); AnotherSimpleEntity anotherSimpleEntity = otherEntity.getAnotherSimpleEntity(); - // the ManyToOne is eager but the value is not null so a second query is executed assertTrue( Hibernate.isInitialized( anotherSimpleEntity ) ); + assertThat( statistics.getPrepareStatementCount(), is(2L) ); } ); } @Test public void testHQLSelectWithFetchJoin(SessionFactoryScope scope) { + StatisticsImplementor statistics = scope.getSessionFactory().getStatistics(); + statistics.clear(); scope.inTransaction( session -> { OtherEntity otherEntity = session. createQuery( "from OtherEntity o join fetch o.simpleEntity", OtherEntity.class ) .uniqueResult(); + assertThat( statistics.getPrepareStatementCount(), is(1L) ); assertThat( otherEntity.getName(), is( "Bar" ) ); assertTrue( Hibernate.isInitialized( otherEntity.getSimpleEntity() ) ); @@ -110,12 +124,16 @@ public class ManyToOneTest { assertThat( otherEntity.getSimpleEntity().getName(), is( "Fab" ) ); AnotherSimpleEntity anotherSimpleEntity = otherEntity.getAnotherSimpleEntity(); assertTrue( Hibernate.isInitialized( anotherSimpleEntity ) ); + assertThat( statistics.getPrepareStatementCount(), is(1L) ); + } ); } @Test public void testSelectWithBothFetchJoin(SessionFactoryScope scope) { + StatisticsImplementor statistics = scope.getSessionFactory().getStatistics(); + statistics.clear(); scope.inTransaction( session -> { OtherEntity otherEntity = session. @@ -124,6 +142,7 @@ public class ManyToOneTest { OtherEntity.class ) .uniqueResult(); + assertThat( statistics.getPrepareStatementCount(), is(1L) ); assertThat( otherEntity.getName(), is( "Bar" ) ); assertTrue( Hibernate.isInitialized( otherEntity.getSimpleEntity() ) ); @@ -131,12 +150,48 @@ public class ManyToOneTest { assertThat( otherEntity.getSimpleEntity().getName(), is( "Fab" ) ); assertTrue( Hibernate.isInitialized( otherEntity.getAnotherSimpleEntity() ) ); assertThat( otherEntity.getAnotherSimpleEntity(), nullValue() ); + assertThat( statistics.getPrepareStatementCount(), is(1L) ); + } + ); + + scope.inTransaction( + session -> { + OtherEntity otherEntity = session. + createQuery( "from OtherEntity", OtherEntity.class ) + .uniqueResult(); + AnotherSimpleEntity anotherSimpleEntity = new AnotherSimpleEntity(); + anotherSimpleEntity.setId( 3 ); + anotherSimpleEntity.setName( "other" ); + session.save( anotherSimpleEntity ); + otherEntity.setAnotherSimpleEntity( anotherSimpleEntity ); + } + ); + + statistics.clear(); + + scope.inTransaction( + session -> { + OtherEntity otherEntity = session. + createQuery( "from OtherEntity o join fetch o.simpleEntity left join fetch o.anotherSimpleEntity", OtherEntity.class ) + .uniqueResult(); + // the ManyToOne is eager but the value is not null so a second query is executed + assertThat( statistics.getPrepareStatementCount(), is(1L) ); + + assertThat( otherEntity.getName(), is( "Bar" ) ); + SimpleEntity simpleEntity = otherEntity.getSimpleEntity(); + assertTrue( Hibernate.isInitialized( simpleEntity ) ); + + AnotherSimpleEntity anotherSimpleEntity = otherEntity.getAnotherSimpleEntity(); + assertTrue( Hibernate.isInitialized( anotherSimpleEntity ) ); + assertThat( statistics.getPrepareStatementCount(), is(1L) ); } ); } @Test public void testGet(SessionFactoryScope scope) { + StatisticsImplementor statistics = scope.getSessionFactory().getStatistics(); + statistics.clear(); scope.inTransaction( session -> { OtherEntity otherEntity = session.get( OtherEntity.class, 2 ); @@ -144,6 +199,7 @@ public class ManyToOneTest { assertThat( otherEntity.getName(), is( "Bar" ) ); assertFalse( Hibernate.isInitialized( otherEntity.getSimpleEntity() ) ); assertTrue( Hibernate.isInitialized( otherEntity.getAnotherSimpleEntity() ) ); + assertThat( statistics.getPrepareStatementCount(), is(1L) ); } ); } @@ -160,7 +216,6 @@ public class ManyToOneTest { session -> { assertThat( session.get( OtherEntity.class, 2 ), nullValue() ); assertThat( session.get( SimpleEntity.class, 1 ), notNullValue() ); - assertThat( session.get( AnotherSimpleEntity.class, 3 ), notNullValue() ); } ); } @@ -190,6 +245,7 @@ public class ManyToOneTest { work -> { Statement statement = work.createStatement(); statement.execute( "delete from mapping_other_entity" ); + statement.execute( "delete from mapping_another_simple_entity" ); statement.execute( "delete from mapping_simple_entity" ); statement.close(); }