From 2f98e4bfe867ea6de76c53d0053b0451d2d8e7c1 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 20 Mar 2020 12:37:25 +0000 Subject: [PATCH] Fix ToOne @Fetch(FetchMode.SELECT) --- .../internal/MappingModelCreationHelper.java | 118 +++--- .../walking/internal/FetchStrategyHelper.java | 9 - .../EagerToOneWithJoinFetchModeTests.java | 364 ++++++++++++++++ .../EagerToOneWithSelectFetchModeTests.java | 389 +++++++++++++++++ .../LazyToOneWithJoinFetchModeTests.java | 400 ++++++++++++++++++ .../LazyToOneWithSelectFetchModeTests.java | 352 +++++++++++++++ .../orm/test/onetoone/OneToOneLazy.java | 138 ++++++ 7 files changed, 1709 insertions(+), 61 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/EagerToOneWithJoinFetchModeTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/EagerToOneWithSelectFetchModeTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/LazyToOneWithJoinFetchModeTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/LazyToOneWithSelectFetchModeTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/OneToOneLazy.java diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java index fedf55b9fd..1214ba596b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java @@ -34,6 +34,7 @@ import org.hibernate.mapping.Component; import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.OneToMany; +import org.hibernate.mapping.OneToOne; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; @@ -1041,59 +1042,72 @@ public class MappingModelCreationHelper { PropertyAccess propertyAccess, CascadeStyle cascadeStyle, MappingModelCreationProcess creationProcess) { - ToOne value = (ToOne) bootProperty.getValue(); - final EntityPersister entityPersister = creationProcess.getEntityPersister( value.getReferencedEntityName() ); + if ( bootProperty.getValue() instanceof ToOne ) { + final ToOne value = (ToOne) bootProperty.getValue(); + final EntityPersister entityPersister = creationProcess.getEntityPersister( value.getReferencedEntityName() ); + final StateArrayContributorMetadataAccess stateArrayContributorMetadataAccess = getStateArrayContributorMetadataAccess( + bootProperty, + attrType, + propertyAccess, + cascadeStyle, + creationProcess + ); + SessionFactoryImplementor sessionFactory = creationProcess.getCreationContext().getSessionFactory(); - final StateArrayContributorMetadataAccess stateArrayContributorMetadataAccess = getStateArrayContributorMetadataAccess( - bootProperty, - attrType, - propertyAccess, - cascadeStyle, - creationProcess - ); - SessionFactoryImplementor sessionFactory = creationProcess.getCreationContext().getSessionFactory(); - - final AssociationType type = (AssociationType) bootProperty.getType(); - final FetchStyle fetchStyle = FetchStrategyHelper - .determineFetchStyleByMetadata( - bootProperty.getValue().getFetchMode(), - type, - sessionFactory - ); - - final FetchTiming fetchTiming = FetchStrategyHelper.determineFetchTiming( fetchStyle, type, sessionFactory ); - - final FetchStrategy fetchStrategy = new FetchStrategy( fetchTiming, fetchStyle ); - - final SingularAssociationAttributeMapping attributeMapping = new SingularAssociationAttributeMapping( - attrName, - stateArrayPosition, - (ToOne) bootProperty.getValue(), - stateArrayContributorMetadataAccess, - fetchStrategy, - entityPersister, - declaringType, - propertyAccess - ); - creationProcess.registerInitializationCallback( - () -> { - final Dialect dialect = creationProcess.getCreationContext() - .getSessionFactory() - .getJdbcServices() - .getDialect(); - - MappingModelCreationHelper.interpretKeyDescriptor( - attributeMapping, - bootProperty, - (ToOne) bootProperty.getValue(), - declaringType.findContainingEntityMapping(), - dialect, - creationProcess + final AssociationType type = (AssociationType) bootProperty.getType(); + final FetchStyle fetchStyle = FetchStrategyHelper + .determineFetchStyleByMetadata( + bootProperty.getValue().getFetchMode(), + type, + sessionFactory ); - return true; - } - ); - return attributeMapping; - } + final FetchTiming fetchTiming; + + if ( fetchStyle == fetchStyle.JOIN + || ( value instanceof OneToOne && value.isNullable() ) + || !( value ).isLazy() ) { + fetchTiming = FetchTiming.IMMEDIATE; + } + else { + fetchTiming = FetchStrategyHelper.determineFetchTiming( fetchStyle, type, sessionFactory ); + } + + final FetchStrategy fetchStrategy = new FetchStrategy( fetchTiming, fetchStyle ); + + final SingularAssociationAttributeMapping attributeMapping = new SingularAssociationAttributeMapping( + attrName, + stateArrayPosition, + (ToOne) bootProperty.getValue(), + stateArrayContributorMetadataAccess, + fetchStrategy, + entityPersister, + declaringType, + propertyAccess + ); + + creationProcess.registerInitializationCallback( + () -> { + final Dialect dialect = creationProcess.getCreationContext() + .getSessionFactory() + .getJdbcServices() + .getDialect(); + + MappingModelCreationHelper.interpretKeyDescriptor( + attributeMapping, + bootProperty, + (ToOne) bootProperty.getValue(), + declaringType.findContainingEntityMapping(), + dialect, + creationProcess + ); + return true; + } + ); + return attributeMapping; + } + else { + throw new NotYetImplementedFor6Exception( "AnyType support has not yet been implemented" ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java index 02bae3d67d..70d46f62ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java @@ -15,7 +15,6 @@ import org.hibernate.engine.profile.FetchProfile; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.PropertyPath; -import org.hibernate.mapping.ToOne; import org.hibernate.persister.collection.AbstractCollectionPersister; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -120,14 +119,6 @@ public final class FetchStrategyHelper { FetchStyle style, AssociationType type, SessionFactoryImplementor sessionFactory) { - if ( type instanceof ToOne ) { - if ( ( (ToOne) type ).isLazy() ) { - return FetchTiming.DELAYED; - } - else { - return FetchTiming.IMMEDIATE; - } - } switch ( style ) { case JOIN: { return FetchTiming.IMMEDIATE; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/EagerToOneWithJoinFetchModeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/EagerToOneWithJoinFetchModeTests.java new file mode 100644 index 0000000000..f337080827 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/EagerToOneWithJoinFetchModeTests.java @@ -0,0 +1,364 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.fetchmode.toone; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryProducer; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +@DomainModel( + annotatedClasses = { + EagerToOneWithJoinFetchModeTests.RootEntity.class, + EagerToOneWithJoinFetchModeTests.SimpleEntity.class + } +) +@SessionFactory +@ServiceRegistry( + settings = { + @ServiceRegistry.Setting(name = AvailableSettings.HBM2DDL_DATABASE_ACTION, value = "create-drop") + } +) +public class EagerToOneWithJoinFetchModeTests implements SessionFactoryProducer { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) { + final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder(); + sqlStatementInterceptor = new SQLStatementInterceptor( sessionFactoryBuilder ); + return (SessionFactoryImplementor) sessionFactoryBuilder.build(); + } + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + SimpleEntity manyToOneSimpleEntity = new SimpleEntity( 1, "manyToOne" ); + SimpleEntity oneToOneSimpleEntity = new SimpleEntity( 2, "oneToOne" ); + session.save( manyToOneSimpleEntity ); + session.save( oneToOneSimpleEntity ); + + RootEntity rootEntity = new RootEntity( 1, "root" ); + rootEntity.manyToOneSimpleEntity = manyToOneSimpleEntity; + rootEntity.oneToOneSimpleEntity = oneToOneSimpleEntity; + session.save( rootEntity ); + } ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createQuery( "delete from RootEntity" ).executeUpdate(); + session.createQuery( "delete from SimpleEntity" ).executeUpdate(); + } ); + } + + @Test + public void testFind(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.find( RootEntity.class, 1 ); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + + assertThat( sqls.size(), is( 1 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String executedStatement = sqls.get( 0 ); + assertThat( executedStatement, containsString( " root_entity " ) ); + assertThat( executedStatement, containsString( " left outer join simple_entity " ) ); + assertThat( + executedStatement.replaceFirst( "left outer join", "" ), + containsString( " left outer join " ) + ); + assertThat( + executedStatement.replaceFirst( " left outer join", "" ) + .replaceFirst( "left outer join", "" ), + not( containsString( " join " ) ) + ); + } + ); + } + + @Test + public void testHql(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + assertThat( sqls.get( 0 ), not( containsString( " join " ) ) ); + assertThat( sqls.get( 0 ), containsString( " root_entity " ) ); + + assertThat( sqls.get( 1 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 1 ), not( containsString( " join " ) ) ); + + assertThat( sqls.get( 2 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 2 ), not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinManyToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "select r from RootEntity r join r.manyToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + assertThat( sqls.get( 1 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 1 ), not( containsString( " join " ) ) ); + + assertThat( sqls.get( 2 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 2 ), not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "select r from RootEntity r join r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + assertThat( sqls.get( 1 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 1 ), not( containsString( " join " ) ) ); + + assertThat( sqls.get( 2 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 2 ), not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinFetchManyToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.manyToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 2 ) ); + + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( firstStatement, containsString( " join simple_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + assertThat( sqls.get( 1 ), containsString( " simple_entity " ) ); + } + ); + } + + @Test + public void testHqlJoinFetchOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 2 ) ); + + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( firstStatement, containsString( " join simple_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + assertThat( sqls.get( 1 ), containsString( " simple_entity " ) ); + } + ); + } + + @Test + public void testHqlJoinManyToOneAndOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "select r from RootEntity r join r.manyToOneSimpleEntity join r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + containsString( " inner join " ) + ); + assertThat( + firstStatement + .replaceFirst( "inner join", "" ) + .replaceFirst( "inner join", "" ) + , + not( containsString( " join " ) ) + ); + + assertThat( sqls.get( 1 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 1 ), not( containsString( " join " ) ) ); + + assertThat( sqls.get( 2 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 2 ), not( containsString( " join " ) ) ); + + } + ); + } + + @Entity(name = "RootEntity") + @Table(name = "root_entity") + public static class RootEntity { + @Id + private Integer id; + private String name; + + @ManyToOne + @Fetch(FetchMode.JOIN) + private SimpleEntity manyToOneSimpleEntity; + + @OneToOne + @Fetch(FetchMode.JOIN) + private SimpleEntity oneToOneSimpleEntity; + + public RootEntity() { + } + + public RootEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + } + + @Entity(name = "SimpleEntity") + @Table(name = "simple_entity") + public static class SimpleEntity { + + @Id + private Integer id; + + private String name; + + public SimpleEntity() { + } + + public SimpleEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/EagerToOneWithSelectFetchModeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/EagerToOneWithSelectFetchModeTests.java new file mode 100644 index 0000000000..fd6543649a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/EagerToOneWithSelectFetchModeTests.java @@ -0,0 +1,389 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.fetchmode.toone; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryProducer; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize; + +/** + * @author Nathan Xu + */ +@DomainModel( + annotatedClasses = { + EagerToOneWithSelectFetchModeTests.RootEntity.class, + EagerToOneWithSelectFetchModeTests.SimpleEntity.class + } +) +@SessionFactory +@ServiceRegistry( + settings = { + @ServiceRegistry.Setting(name = AvailableSettings.HBM2DDL_DATABASE_ACTION, value = "create-drop") + } +) +public class EagerToOneWithSelectFetchModeTests implements SessionFactoryProducer { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) { + final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder(); + sqlStatementInterceptor = new SQLStatementInterceptor( sessionFactoryBuilder ); + return (SessionFactoryImplementor) sessionFactoryBuilder.build(); + } + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + SimpleEntity manyToOneSimpleEntity = new SimpleEntity( 1, "manyToOne" ); + SimpleEntity oneToOneSimpleEntity = new SimpleEntity( 2, "oneToOne" ); + session.save( manyToOneSimpleEntity ); + session.save( oneToOneSimpleEntity ); + + RootEntity rootEntity = new RootEntity( 1, "root" ); + rootEntity.manyToOneSimpleEntity = manyToOneSimpleEntity; + rootEntity.oneToOneSimpleEntity = oneToOneSimpleEntity; + session.save( rootEntity ); + } ); + } + + @Test + public void testFind(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.find( RootEntity.class, 1 ); + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + assertThat( sqls.get( 0 ), not( containsString( " join " ) ) ); + assertThat( sqls.get( 0 ), containsString( " root_entity " ) ); + assertThat( sqls.get( 1 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 2 ), containsString( " simple_entity " ) ); + } + ); + } + + @Test + public void testHql(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, not( containsString( " join " ) ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + + String thirthStatement = sqls.get( 2 ); + assertThat( thirthStatement, containsString( " simple_entity " ) ); + assertThat( thirthStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinManyToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "select r from RootEntity r join r.manyToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + + String thirthStatement = sqls.get( 2 ); + assertThat( thirthStatement, containsString( " simple_entity " ) ); + assertThat( thirthStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "select r from RootEntity r join r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + + String thirthStatement = sqls.get( 2 ); + assertThat( thirthStatement, containsString( " simple_entity " ) ); + assertThat( thirthStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinFetchManyToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.manyToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 2 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( firstStatement, containsString( " join simple_entity " ) ); + assertThat( + firstStatement.replaceFirst( " join ", "" ), + not( containsString( " join " ) ) + ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinManyToOneAndOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join r.manyToOneSimpleEntity join r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + containsString( " inner join " ) + ); + assertThat( + firstStatement + .replaceFirst( "inner join", "" ) + .replaceFirst( "inner join", "" ) + , + not( containsString( " join " ) ) + ); + + assertThat( sqls.get( 1 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 1 ), not( containsString( " join " ) ) ); + + assertThat( sqls.get( 2 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 2 ), not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinFetchOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 2 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( firstStatement, containsString( " join simple_entity " ) ); + assertThat( + firstStatement.replaceFirst( " join ", "" ), + not( containsString( " join " ) ) + ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinFetchManyToOneAndOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.manyToOneSimpleEntity join fetch r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + containsString( " inner join " ) + ); + + assertThat( + firstStatement + .replaceFirst( " inner join ", "" ) + .replaceFirst( " inner join ", "" ), + not( containsString( " join " ) ) + ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createQuery( "delete from RootEntity" ).executeUpdate(); + session.createQuery( "delete from SimpleEntity" ).executeUpdate(); + } ); + } + + @Entity(name = "RootEntity") + @Table(name = "root_entity") + public static class RootEntity { + @Id + private Integer id; + private String name; + + @ManyToOne + @Fetch(FetchMode.SELECT) + private SimpleEntity manyToOneSimpleEntity; + + @OneToOne + @Fetch(FetchMode.SELECT) + private SimpleEntity oneToOneSimpleEntity; + + public RootEntity() { + } + + public RootEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + } + + @Entity(name = "SimpleEntity") + @Table(name = "simple_entity") + public static class SimpleEntity { + + @Id + private Integer id; + + private String name; + + public SimpleEntity() { + } + + public SimpleEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/LazyToOneWithJoinFetchModeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/LazyToOneWithJoinFetchModeTests.java new file mode 100644 index 0000000000..35f362fc21 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/LazyToOneWithJoinFetchModeTests.java @@ -0,0 +1,400 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.fetchmode.toone; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryProducer; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize; + +/** + * @author Nathan Xu + */ +@DomainModel( + annotatedClasses = { + LazyToOneWithJoinFetchModeTests.RootEntity.class, + LazyToOneWithJoinFetchModeTests.SimpleEntity.class + } +) +@SessionFactory +@ServiceRegistry( + settings = { + @ServiceRegistry.Setting(name = AvailableSettings.HBM2DDL_DATABASE_ACTION, value = "create-drop") + } +) +public class LazyToOneWithJoinFetchModeTests implements SessionFactoryProducer { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) { + final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder(); + sqlStatementInterceptor = new SQLStatementInterceptor( sessionFactoryBuilder ); + return (SessionFactoryImplementor) sessionFactoryBuilder.build(); + } + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + SimpleEntity manyToOneSimpleEntity = new SimpleEntity( 1, "manyToOne" ); + SimpleEntity oneToOneSimpleEntity = new SimpleEntity( 2, "oneToOne" ); + session.save( manyToOneSimpleEntity ); + session.save( oneToOneSimpleEntity ); + + RootEntity rootEntity = new RootEntity( 1, "root" ); + rootEntity.manyToOneSimpleEntity = manyToOneSimpleEntity; + rootEntity.oneToOneSimpleEntity = oneToOneSimpleEntity; + session.save( rootEntity ); + } ); + } + + @Test + public void testFind(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.find( RootEntity.class, 1 ); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String executedStatement = sqls.get( 0 ); + assertThat( executedStatement, containsString( " root_entity " ) ); + assertThat( executedStatement, containsString( " left outer join simple_entity " ) ); + assertThat( + executedStatement.replaceFirst( "left outer join", "" ), + containsString( " left outer join " ) + ); + assertThat( + executedStatement.replaceFirst( " left outer join", "" ) + .replaceFirst( "left outer join", "" ), + not( containsString( " join " ) ) + ); + } + ); + } + + @Test + public void testHql(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, not( containsString( " join " ) ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + + String thirthStatement = sqls.get( 2 ); + assertThat( thirthStatement, containsString( " simple_entity " ) ); + assertThat( thirthStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinManyToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "select r from RootEntity r join r.manyToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + + String thirthStatement = sqls.get( 2 ); + assertThat( thirthStatement, containsString( " simple_entity " ) ); + assertThat( thirthStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "select r from RootEntity r join r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + + String thirthStatement = sqls.get( 2 ); + assertThat( thirthStatement, containsString( " simple_entity " ) ); + assertThat( thirthStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinFetchManyToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.manyToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 2 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( firstStatement, containsString( " join simple_entity " ) ); + assertThat( + firstStatement.replaceFirst( " join ", "" ), + not( containsString( " join " ) ) + ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinManyToOneAndOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join r.manyToOneSimpleEntity join r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 3 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + containsString( " inner join " ) + ); + assertThat( + firstStatement + .replaceFirst( "inner join", "" ) + .replaceFirst( "inner join", "" ) + , + not( containsString( " join " ) ) + ); + + assertThat( sqls.get( 1 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 1 ), not( containsString( " join " ) ) ); + + assertThat( sqls.get( 2 ), containsString( " simple_entity " ) ); + assertThat( sqls.get( 2 ), not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinFetchOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 2 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( firstStatement, containsString( " join simple_entity " ) ); + assertThat( + firstStatement.replaceFirst( " join ", "" ), + not( containsString( " join " ) ) + ); + + String secondStatement = sqls.get( 1 ); + assertThat( secondStatement, containsString( " simple_entity " ) ); + assertThat( secondStatement, not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHqlJoinFetchManyToOneAndOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.manyToOneSimpleEntity join fetch r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + containsString( " inner join " ) + ); + + assertThat( + firstStatement + .replaceFirst( " inner join ", "" ) + .replaceFirst( " inner join ", "" ), + not( containsString( " join " ) ) + ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createQuery( "delete from RootEntity" ).executeUpdate(); + session.createQuery( "delete from SimpleEntity" ).executeUpdate(); + } ); + } + + @Entity(name = "RootEntity") + @Table(name = "root_entity") + public static class RootEntity { + @Id + private Integer id; + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @Fetch(FetchMode.JOIN) + private SimpleEntity manyToOneSimpleEntity; + + @OneToOne(fetch = FetchType.LAZY) + @Fetch(FetchMode.JOIN) + private SimpleEntity oneToOneSimpleEntity; + + public RootEntity() { + } + + public RootEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + } + + @Entity(name = "SimpleEntity") + @Table(name = "simple_entity") + public static class SimpleEntity { + + @Id + private Integer id; + + private String name; + + public SimpleEntity() { + } + + public SimpleEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/LazyToOneWithSelectFetchModeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/LazyToOneWithSelectFetchModeTests.java new file mode 100644 index 0000000000..81999bb399 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/fetchmode/toone/LazyToOneWithSelectFetchModeTests.java @@ -0,0 +1,352 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.fetchmode.toone; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryProducer; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize; + +/** + * @author Nathan Xu + */ +@DomainModel( + annotatedClasses = { + LazyToOneWithSelectFetchModeTests.RootEntity.class, + LazyToOneWithSelectFetchModeTests.SimpleEntity.class + } +) +@SessionFactory +@ServiceRegistry( + settings = { + @ServiceRegistry.Setting(name = AvailableSettings.HBM2DDL_DATABASE_ACTION, value = "create-drop") + } +) +public class LazyToOneWithSelectFetchModeTests implements SessionFactoryProducer { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) { + final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder(); + sqlStatementInterceptor = new SQLStatementInterceptor( sessionFactoryBuilder ); + return (SessionFactoryImplementor) sessionFactoryBuilder.build(); + } + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + SimpleEntity manyToOneSimpleEntity = new SimpleEntity( 1, "manyToOne" ); + SimpleEntity oneToOneSimpleEntity = new SimpleEntity( 2, "oneToOne" ); + session.save( manyToOneSimpleEntity ); + session.save( oneToOneSimpleEntity ); + + RootEntity rootEntity = new RootEntity( 1, "root" ); + rootEntity.manyToOneSimpleEntity = manyToOneSimpleEntity; + rootEntity.oneToOneSimpleEntity = oneToOneSimpleEntity; + session.save( rootEntity ); + } ); + } + + @Test + public void testFind(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.find( RootEntity.class, 1 ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( false ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( false ) ); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls, hasSize( 1 ) ); + assertThat( sqls.get( 0 ), not( containsString( " join " ) ) ); + } + ); + } + + @Test + public void testHql(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + assertThat( sqls.get( 0 ), not( containsString( " join " ) ) ); + + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( false ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( false ) ); + } + ); + } + + @Test + public void testHqlJoinManyToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "select r from RootEntity r join r.manyToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( false ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( false ) ); + } + ); + } + + @Test + public void testHqlJoinOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "select r from RootEntity r join r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + not( containsString( " join " ) ) + ); + + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( false ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( false ) ); + + } + ); + } + + @Test + public void testHqlJoinFetchManyToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.manyToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( false ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( firstStatement, containsString( " join simple_entity " ) ); + assertThat( + firstStatement.replaceFirst( " join ", "" ), + not( containsString( " join " ) ) + ); + + } + ); + } + + @Test + public void testHqlJoinManyToOneAndOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join r.manyToOneSimpleEntity join r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( false ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( false ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( firstStatement, containsString( " join simple_entity " ) ); + + assertThat( + firstStatement.replaceFirst( " join ", "" ), + containsString( " join " ) + ); + assertThat( + firstStatement.replaceFirst( " join ", "" ).replaceFirst( " join ", "" ), + not( containsString( " join " ) ) + ); + } + ); + } + + @Test + public void testHqlJoinFetchOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( false ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( firstStatement, containsString( " join simple_entity " ) ); + assertThat( + firstStatement.replaceFirst( " join ", "" ), + not( containsString( " join " ) ) + ); + + } + ); + } + + @Test + public void testHqlJoinFetchManyToOneAndOneToOne(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + sqlStatementInterceptor.clear(); + + RootEntity rootEntity = session.createQuery( + "from RootEntity r join fetch r.manyToOneSimpleEntity join fetch r.oneToOneSimpleEntity where r.id = :id", + RootEntity.class + ).setParameter( "id", 1 ).getSingleResult(); + + List sqls = sqlStatementInterceptor.getSqlQueries(); + assertThat( sqls.size(), is( 1 ) ); + assertThat( Hibernate.isInitialized( rootEntity.manyToOneSimpleEntity ), is( true ) ); + assertThat( Hibernate.isInitialized( rootEntity.oneToOneSimpleEntity ), is( true ) ); + + String firstStatement = sqls.get( 0 ); + assertThat( firstStatement, containsString( " inner join " ) ); + assertThat( firstStatement, containsString( " root_entity " ) ); + assertThat( + firstStatement.replaceFirst( "inner join", "" ), + containsString( " inner join " ) + ); + + assertThat( + firstStatement + .replaceFirst( " inner join ", "" ) + .replaceFirst( " inner join ", "" ), + not( containsString( " join " ) ) + ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createQuery( "delete from RootEntity" ).executeUpdate(); + session.createQuery( "delete from SimpleEntity" ).executeUpdate(); + } ); + } + + @Entity(name = "RootEntity") + @Table(name = "root_entity") + public static class RootEntity { + @Id + private Integer id; + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @Fetch(FetchMode.SELECT) + private SimpleEntity manyToOneSimpleEntity; + + @OneToOne(fetch = FetchType.LAZY) + @Fetch(FetchMode.SELECT) + private SimpleEntity oneToOneSimpleEntity; + + public RootEntity() { + } + + public RootEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + } + + @Entity(name = "SimpleEntity") + @Table(name = "simple_entity") + public static class SimpleEntity { + + @Id + private Integer id; + + private String name; + + public SimpleEntity() { + } + + public SimpleEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/OneToOneLazy.java b/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/OneToOneLazy.java new file mode 100644 index 0000000000..52fdd17a6a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/OneToOneLazy.java @@ -0,0 +1,138 @@ +/* + * 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 . + */ + +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.onetoone; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Andrea Boriero + */ +@DomainModel( + annotatedClasses = { + OneToOneLazy.Title.class, + OneToOneLazy.Book.class + } +) +@SessionFactory +public class OneToOneLazy { + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + sesison -> { + Title title = new Title( 1L ); + Book book = new Book( 2L, title ); + + sesison.save( title ); + sesison.save( book ); + } + ); + } + + @Test + public void testLazyLoading(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Book book = session.find( Book.class, 2L ); + assertThat( Hibernate.isInitialized( book.getTitle() ), is( false ) ); + } + ); + + scope.inTransaction( + session -> { + Title title = session.find( Title.class, 1L ); + assertThat( Hibernate.isInitialized( title.getBook() ), is( true ) ); + + } + ); + } + + @Entity + public static class Book { + + @Id + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + private Title title; + + public Book() { + } + + public Book(Long id, Title title) { + this.id = id; + this.title = title; + title.setBook( this ); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Title getTitle() { + return title; + } + } + + @Entity + public static class Title { + + @Id + private Long id; + + @OneToOne(fetch = FetchType.LAZY, mappedBy = "title") + private Book book; + + public Title() { + } + + public Title(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Book getBook() { + return book; + } + + public void setBook(Book book) { + this.book = book; + } + } +}