From 5f08ffed8373c9a89e0935e305e902e77d2e25c3 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 3 Jan 2023 17:47:09 +0100 Subject: [PATCH] HHH-15982 Add parentAccess logic to unique key initializer --- .../entity/AbstractEntityInitializer.java | 5 +- ...titySelectFetchByUniqueKeyInitializer.java | 16 ++ .../EntitySelectFetchInitializer.java | 2 +- ...ctionalOneToOneWithConverterEagerTest.java | 256 ++++++++++++++++++ ...ctionalOneToOneWithConverterLazyTest.java} | 92 ++++--- .../BidirectionalOneToOneEagerFKTest.java | 218 +++++++++++++++ ...a => BidirectionalOneToOneLazyFKTest.java} | 91 ++++--- 7 files changed, 614 insertions(+), 66 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/onetoone/BidirectionalOneToOneWithConverterEagerTest.java rename hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/{BidirectionalOneToOneWithConverterTest.java => onetoone/BidirectionalOneToOneWithConverterLazyTest.java} (68%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneEagerFKTest.java rename hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/{BidirectionalOneToOneInstanceTest.java => BidirectionalOneToOneLazyFKTest.java} (61%) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java index 7f20fa2baf..032f610b5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java @@ -378,7 +378,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces @Override public void resolveInstance(RowProcessingState rowProcessingState) { - if ( !missing ) { + if ( !missing && !isInitialized ) { // Special case map proxy to avoid stack overflows // We know that a map proxy will always be of "the right type" so just use that object final LoadingEntityEntry existingLoadingEntry = @@ -389,6 +389,9 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces if ( entityInstance == null ) { resolveEntityInstance( rowProcessingState, existingLoadingEntry, entityKey.getIdentifier() ); } + else if ( existingLoadingEntry != null && existingLoadingEntry.getEntityInitializer() != this ) { + isInitialized = true; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java index eed1618330..09fcfd4695 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java @@ -15,6 +15,7 @@ import org.hibernate.persister.entity.UniqueKeyLoadable; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; @@ -41,6 +42,15 @@ public class EntitySelectFetchByUniqueKeyInitializer extends EntitySelectFetchIn return; } + final EntityInitializer parentEntityInitializer = getParentEntityInitializer( parentAccess ); + if ( parentEntityInitializer != null && parentEntityInitializer.getEntityKey() != null ) { + // make sure parentEntityInitializer.resolveInstance has been called before + parentEntityInitializer.resolveInstance( rowProcessingState ); + if ( parentEntityInitializer.isInitialized() ) { + return; + } + } + if ( !isAttributeAssignableToConcreteDescriptor() ) { isInitialized = true; return; @@ -95,4 +105,10 @@ public class EntitySelectFetchByUniqueKeyInitializer extends EntitySelectFetchIn isInitialized = true; } + private EntityInitializer getParentEntityInitializer(FetchParentAccess parentAccess) { + if ( parentAccess != null ) { + return parentAccess.findFirstEntityInitializer(); + } + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java index 1b37642e53..0d4160bb7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java @@ -39,7 +39,7 @@ import static org.hibernate.internal.log.LoggingHelper.toLoggableString; public class EntitySelectFetchInitializer extends AbstractFetchParentAccess implements EntityInitializer { private static final String CONCRETE_NAME = EntitySelectFetchInitializer.class.getSimpleName(); - private final FetchParentAccess parentAccess; + protected final FetchParentAccess parentAccess; private final NavigablePath navigablePath; private final boolean isEnhancedForLazyLoading; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/onetoone/BidirectionalOneToOneWithConverterEagerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/onetoone/BidirectionalOneToOneWithConverterEagerTest.java new file mode 100644 index 0000000000..ba5a87eb4b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/onetoone/BidirectionalOneToOneWithConverterEagerTest.java @@ -0,0 +1,256 @@ +/* + * 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.mapping.converted.converter.onetoone; + +import java.io.Serializable; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +import org.hibernate.engine.internal.StatisticalLoggingSessionEventListener; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + BidirectionalOneToOneWithConverterEagerTest.FooEntity.class, + BidirectionalOneToOneWithConverterEagerTest.BarEntity.class, +}) +@JiraKey("HHH-15950") +public class BidirectionalOneToOneWithConverterEagerTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + BarEntity bar = new BarEntity(); + bar.setBusinessId( new BusinessId( UUID.randomUUID().toString() ) ); + bar.setaDouble( 0.5 ); + + FooEntity foo = new FooEntity(); + foo.setBusinessId( new BusinessId( UUID.randomUUID().toString() ) ); + foo.setName( "foo_name" ); + + foo.setBar( bar ); + bar.setFoo( foo ); + + session.persist( bar ); + session.persist( foo ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from FooEntity" ).executeUpdate(); + session.createMutationQuery( "delete from BarEntity" ).executeUpdate(); + } ); + } + + @Test + public void testBidirectionalFetch(SessionFactoryScope scope) { + scope.inTransaction( session -> { + FooEntity foo = session.find( FooEntity.class, 1L ); + + final AtomicInteger queryExecutionCount = new AtomicInteger(); + session.getEventListenerManager().addListener( new StatisticalLoggingSessionEventListener() { + @Override + public void jdbcExecuteStatementStart() { + super.jdbcExecuteStatementStart(); + queryExecutionCount.getAndIncrement(); + } + } ); + + BarEntity bar = foo.getBar(); + assertEquals( 0, queryExecutionCount.get() ); + assertEquals( 0.5, bar.getaDouble() ); + + FooEntity associatedFoo = bar.getFoo(); + assertEquals( 0, queryExecutionCount.get() ); + assertEquals( "foo_name", associatedFoo.getName() ); + assertEquals( foo, associatedFoo ); + + assertEquals( bar, associatedFoo.getBar() ); + } ); + } + + @Test + public void testBidirectionalFetchInverse(SessionFactoryScope scope) { + scope.inTransaction( session -> { + BarEntity bar = session.find( BarEntity.class, 1L ); + + final AtomicInteger queryExecutionCount = new AtomicInteger(); + session.getEventListenerManager().addListener( new StatisticalLoggingSessionEventListener() { + @Override + public void jdbcExecuteStatementStart() { + super.jdbcExecuteStatementStart(); + queryExecutionCount.getAndIncrement(); + } + } ); + + FooEntity foo = bar.getFoo(); + assertEquals( 0, queryExecutionCount.get() ); + assertEquals( "foo_name", foo.getName() ); + + BarEntity associatedBar = foo.getBar(); + assertEquals( 0, queryExecutionCount.get() ); + assertEquals( 0.5, associatedBar.getaDouble() ); + assertEquals( bar, associatedBar ); + + assertEquals( foo, associatedBar.getFoo() ); + } ); + } + + public static class BusinessId implements Serializable { + private String value; + + public BusinessId() { + } + + public BusinessId(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public static class BusinessIdConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(BusinessId uuid) { + return uuid != null ? uuid.getValue() : null; + } + + @Override + public BusinessId convertToEntityAttribute(String s) { + return s == null ? null : new BusinessId( s ); + } + } + + @Entity(name = "FooEntity") + @Table(name = "foo") + public static class FooEntity { + @Id + @GeneratedValue + private Long id; + + @Column(name = "uuid", unique = true, updatable = false) + @Convert(converter = BusinessIdConverter.class) + private BusinessId businessId; + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "bar_uuid", referencedColumnName = "uuid", nullable = false, updatable = false) + private BarEntity bar; + + private String name; + + public BarEntity getBar() { + return bar; + } + + public void setBar(BarEntity bar) { + this.bar = bar; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public BusinessId getBusinessId() { + return businessId; + } + + public void setBusinessId(BusinessId businessId) { + this.businessId = businessId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "BarEntity") + @Table(name = "bar") + public static class BarEntity { + @Id + @GeneratedValue + private Long id; + + @Column(name = "uuid", unique = true, updatable = false) + @Convert(converter = BusinessIdConverter.class) + private BusinessId businessId; + + @OneToOne(fetch = FetchType.EAGER, mappedBy = "bar") + private FooEntity foo; + + private Double aDouble; + + public FooEntity getFoo() { + return foo; + } + + public void setFoo(FooEntity foo) { + this.foo = foo; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public BusinessId getBusinessId() { + return businessId; + } + + public void setBusinessId(BusinessId businessId) { + this.businessId = businessId; + } + + public Double getaDouble() { + return aDouble; + } + + public void setaDouble(Double aDouble) { + this.aDouble = aDouble; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/BidirectionalOneToOneWithConverterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/onetoone/BidirectionalOneToOneWithConverterLazyTest.java similarity index 68% rename from hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/BidirectionalOneToOneWithConverterTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/onetoone/BidirectionalOneToOneWithConverterLazyTest.java index de3f442618..bceb520e40 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/BidirectionalOneToOneWithConverterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/onetoone/BidirectionalOneToOneWithConverterLazyTest.java @@ -4,10 +4,9 @@ * 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.mapping.converted.converter; +package org.hibernate.orm.test.mapping.converted.converter.onetoone; import java.io.Serializable; -import java.util.Date; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -17,6 +16,8 @@ import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import jakarta.persistence.AttributeConverter; @@ -37,24 +38,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals; */ @SessionFactory @DomainModel(annotatedClasses = { - BidirectionalOneToOneWithConverterTest.FooEntity.class, - BidirectionalOneToOneWithConverterTest.BarEntity.class, + BidirectionalOneToOneWithConverterLazyTest.FooEntity.class, + BidirectionalOneToOneWithConverterLazyTest.BarEntity.class, }) @JiraKey("HHH-15950") -public class BidirectionalOneToOneWithConverterTest { - @Test - public void testBidirectionalFetch(SessionFactoryScope scope) { - String name = "foo_name"; - Date date = new Date(); - +public class BidirectionalOneToOneWithConverterLazyTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { scope.inTransaction( session -> { BarEntity bar = new BarEntity(); bar.setBusinessId( new BusinessId( UUID.randomUUID().toString() ) ); - bar.setDate( date ); + bar.setaDouble( 0.5 ); FooEntity foo = new FooEntity(); foo.setBusinessId( new BusinessId( UUID.randomUUID().toString() ) ); - foo.setName( name ); + foo.setName( "foo_name" ); foo.setBar( bar ); bar.setFoo( foo ); @@ -62,10 +60,20 @@ public class BidirectionalOneToOneWithConverterTest { session.persist( bar ); session.persist( foo ); } ); + } + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from FooEntity" ).executeUpdate(); + session.createMutationQuery( "delete from BarEntity" ).executeUpdate(); + } ); + } + + @Test + public void testBidirectionalFetch(SessionFactoryScope scope) { scope.inTransaction( session -> { FooEntity foo = session.find( FooEntity.class, 1L ); - assertEquals( name, foo.getName() ); final AtomicInteger queryExecutionCount = new AtomicInteger(); session.getEventListenerManager().addListener( new StatisticalLoggingSessionEventListener() { @@ -77,24 +85,44 @@ public class BidirectionalOneToOneWithConverterTest { } ); BarEntity bar = foo.getBar(); - // no queries should be executed assertEquals( 0, queryExecutionCount.get() ); - assertEquals( date, bar.getDate() ); + assertEquals( 0.5, bar.getaDouble() ); - FooEntity associatedFoo = bar.getFoo(); - // no queries should be executed - assertEquals(0, queryExecutionCount.get()); - assertEquals( foo, associatedFoo ); + FooEntity associatedFoo = bar.getFoo(); + assertEquals( 0, queryExecutionCount.get() ); + assertEquals( "foo_name", associatedFoo.getName() ); + assertEquals( foo, associatedFoo ); + + assertEquals( bar, associatedFoo.getBar() ); } ); } - // todo marco : verifica che get su associazione non faccia altra query - // foo.getBar() - non deve fare query - // bar.getFoo() - non deve fare query + deve essere stessa instance di quello col find - // todo marco : provare anche contrario (session.find(Bar.class, 1L); + @Test + public void testBidirectionalFetchInverse(SessionFactoryScope scope) { + scope.inTransaction( session -> { + BarEntity bar = session.find( BarEntity.class, 1L ); - // todo marco : fare un altro test con associazione EAGER - // questo dovrebbe fare il detect della circularity + final AtomicInteger queryExecutionCount = new AtomicInteger(); + session.getEventListenerManager().addListener( new StatisticalLoggingSessionEventListener() { + @Override + public void jdbcExecuteStatementStart() { + super.jdbcExecuteStatementStart(); + queryExecutionCount.getAndIncrement(); + } + } ); + + FooEntity foo = bar.getFoo(); + assertEquals( 0, queryExecutionCount.get() ); + assertEquals( "foo_name", foo.getName() ); + + BarEntity associatedBar = foo.getBar(); + assertEquals( 0, queryExecutionCount.get() ); + assertEquals( 0.5, associatedBar.getaDouble() ); + assertEquals( bar, associatedBar ); + + assertEquals( foo, associatedBar.getFoo() ); + } ); + } public static class BusinessId implements Serializable { private String value; @@ -127,7 +155,7 @@ public class BidirectionalOneToOneWithConverterTest { } } - @Entity + @Entity(name = "FooEntity") @Table(name = "foo") public static class FooEntity { @Id @@ -177,7 +205,7 @@ public class BidirectionalOneToOneWithConverterTest { } } - @Entity + @Entity(name = "BarEntity") @Table(name = "bar") public static class BarEntity { @Id @@ -191,7 +219,7 @@ public class BidirectionalOneToOneWithConverterTest { @OneToOne(fetch = FetchType.LAZY, mappedBy = "bar") private FooEntity foo; - private Date date; + private Double aDouble; public FooEntity getFoo() { return foo; @@ -217,12 +245,12 @@ public class BidirectionalOneToOneWithConverterTest { this.businessId = businessId; } - public Date getDate() { - return date; + public Double getaDouble() { + return aDouble; } - public void setDate(Date date) { - this.date = date; + public void setaDouble(Double aDouble) { + this.aDouble = aDouble; } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneEagerFKTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneEagerFKTest.java new file mode 100644 index 0000000000..678322bd3d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneEagerFKTest.java @@ -0,0 +1,218 @@ +/* + * 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.bidirectional; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.hibernate.engine.internal.StatisticalLoggingSessionEventListener; + +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.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + BidirectionalOneToOneEagerFKTest.FooEntity.class, + BidirectionalOneToOneEagerFKTest.BarEntity.class +}) +public class BidirectionalOneToOneEagerFKTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + BarEntity bar = new BarEntity(); + bar.setBusinessId( 1L ); + bar.setaDouble( 0.5 ); + + FooEntity foo = new FooEntity(); + foo.setBusinessId( 2L ); + foo.setName( "foo_name" ); + + foo.setBar( bar ); + bar.setFoo( foo ); + + session.persist( bar ); + session.persist( foo ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from FooEntity" ).executeUpdate(); + session.createMutationQuery( "delete from BarEntity" ).executeUpdate(); + } ); + } + + @Test + public void testBidirectionalFetchJoinColumnSide(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final AtomicInteger queryExecutionCount = new AtomicInteger(); + session.getEventListenerManager().addListener( new StatisticalLoggingSessionEventListener() { + @Override + public void jdbcExecuteStatementStart() { + super.jdbcExecuteStatementStart(); + queryExecutionCount.getAndIncrement(); + } + } ); + + FooEntity foo = session.find( FooEntity.class, 1L ); + + BarEntity bar = foo.getBar(); + assertEquals( 1, queryExecutionCount.get() ); + assertEquals( 0.5, bar.getaDouble() ); + + FooEntity associatedFoo = bar.getFoo(); + assertEquals( 1, queryExecutionCount.get() ); + assertEquals( "foo_name", associatedFoo.getName() ); + assertEquals( foo, associatedFoo ); + + assertEquals( bar, associatedFoo.getBar() ); + } ); + } + + @Test + public void testBidirectionalFetchMappedBySide(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final AtomicInteger queryExecutionCount = new AtomicInteger(); + session.getEventListenerManager().addListener( new StatisticalLoggingSessionEventListener() { + @Override + public void jdbcExecuteStatementStart() { + super.jdbcExecuteStatementStart(); + queryExecutionCount.getAndIncrement(); + } + } ); + + BarEntity bar = session.find( BarEntity.class, 1L ); + assertEquals( 1, queryExecutionCount.get() ); + + FooEntity foo = bar.getFoo(); + assertEquals( 1, queryExecutionCount.get() ); + assertEquals( "foo_name", foo.getName() ); + + BarEntity associatedBar = foo.getBar(); + assertEquals( 1, queryExecutionCount.get() ); + assertEquals( 0.5, associatedBar.getaDouble() ); + assertEquals( bar, associatedBar ); + + assertEquals( foo, associatedBar.getFoo() ); + } ); + } + + @Entity(name = "FooEntity") + @Table(name = "foo") + public static class FooEntity { + @Id + @GeneratedValue + private Long id; + + @Column(name = "business_id", unique = true, updatable = false) + private Long businessId; + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "bar_business_id", referencedColumnName = "business_id", nullable = false, updatable = false) + private BarEntity bar; + + private String name; + + public BarEntity getBar() { + return bar; + } + + public void setBar(BarEntity bar) { + this.bar = bar; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getBusinessId() { + return businessId; + } + + public void setBusinessId(Long businessId) { + this.businessId = businessId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "BarEntity") + @Table(name = "bar") + public static class BarEntity { + @Id + @GeneratedValue + private Long id; + + @Column(name = "business_id", unique = true, updatable = false) + private Long businessId; + + @OneToOne(fetch = FetchType.EAGER, mappedBy = "bar") + private FooEntity foo; + + private Double aDouble; + + public FooEntity getFoo() { + return foo; + } + + public void setFoo(FooEntity foo) { + this.foo = foo; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getBusinessId() { + return businessId; + } + + public void setBusinessId(Long businessId) { + this.businessId = businessId; + } + + public Double getaDouble() { + return aDouble; + } + + public void setaDouble(Double aDouble) { + this.aDouble = aDouble; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneInstanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneLazyFKTest.java similarity index 61% rename from hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneInstanceTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneLazyFKTest.java index 6dfc994495..d207a4f5a0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneInstanceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/onetoone/bidirectional/BidirectionalOneToOneLazyFKTest.java @@ -1,10 +1,3 @@ -/* - * 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 - */ - /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -13,7 +6,6 @@ */ package org.hibernate.orm.test.onetoone.bidirectional; -import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; import org.hibernate.engine.internal.StatisticalLoggingSessionEventListener; @@ -21,6 +13,8 @@ import org.hibernate.engine.internal.StatisticalLoggingSessionEventListener; 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.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import jakarta.persistence.Column; @@ -39,24 +33,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals; */ @SessionFactory @DomainModel(annotatedClasses = { - BidirectionalOneToOneInstanceTest.FooEntity.class, - BidirectionalOneToOneInstanceTest.BarEntity.class + BidirectionalOneToOneLazyFKTest.FooEntity.class, + BidirectionalOneToOneLazyFKTest.BarEntity.class }) -public class BidirectionalOneToOneInstanceTest { - - @Test - public void testBidirectionalFetch(SessionFactoryScope scope) { - String name = "foo_name"; - Date date = new Date(); - +public class BidirectionalOneToOneLazyFKTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { scope.inTransaction( session -> { BarEntity bar = new BarEntity(); bar.setBusinessId( 1L ); - bar.setDate( date ); + bar.setaDouble( 0.5 ); FooEntity foo = new FooEntity(); foo.setBusinessId( 2L ); - foo.setName( name ); + foo.setName( "foo_name" ); foo.setBar( bar ); bar.setFoo( foo ); @@ -64,7 +54,18 @@ public class BidirectionalOneToOneInstanceTest { session.persist( bar ); session.persist( foo ); } ); + } + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from FooEntity" ).executeUpdate(); + session.createMutationQuery( "delete from BarEntity" ).executeUpdate(); + } ); + } + + @Test + public void testBidirectionalFetchJoinColumnSide(SessionFactoryScope scope) { scope.inTransaction( session -> { FooEntity foo = session.find( FooEntity.class, 1L ); @@ -77,17 +78,43 @@ public class BidirectionalOneToOneInstanceTest { } } ); - assertEquals( name, foo.getName() ); - BarEntity bar = foo.getBar(); - // no queries should be executed assertEquals( 0, queryExecutionCount.get() ); - assertEquals( date, bar.getDate() ); + assertEquals( 0.5, bar.getaDouble() ); FooEntity associatedFoo = bar.getFoo(); - // no queries should be executed assertEquals( 0, queryExecutionCount.get() ); + assertEquals( "foo_name", associatedFoo.getName() ); assertEquals( foo, associatedFoo ); + + assertEquals( bar, associatedFoo.getBar() ); + } ); + } + + @Test + public void testBidirectionalFetchMappedBySide(SessionFactoryScope scope) { + scope.inTransaction( session -> { + BarEntity bar = session.find( BarEntity.class, 1L ); + + final AtomicInteger queryExecutionCount = new AtomicInteger(); + session.getEventListenerManager().addListener( new StatisticalLoggingSessionEventListener() { + @Override + public void jdbcExecuteStatementStart() { + super.jdbcExecuteStatementStart(); + queryExecutionCount.getAndIncrement(); + } + } ); + + FooEntity foo = bar.getFoo(); + assertEquals( 0, queryExecutionCount.get() ); + assertEquals( "foo_name", foo.getName() ); + + BarEntity associatedBar = foo.getBar(); + assertEquals( 0, queryExecutionCount.get() ); + assertEquals( 0.5, associatedBar.getaDouble() ); + assertEquals( bar, associatedBar ); + + assertEquals( foo, associatedBar.getFoo() ); } ); } @@ -98,11 +125,11 @@ public class BidirectionalOneToOneInstanceTest { @GeneratedValue private Long id; - @Column(name = "uuid", unique = true, updatable = false) + @Column(name = "business_id", unique = true, updatable = false) private Long businessId; @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "bar_uuid", referencedColumnName = "uuid", nullable = false, updatable = false) + @JoinColumn(name = "bar_business_id", referencedColumnName = "business_id", nullable = false, updatable = false) private BarEntity bar; private String name; @@ -147,13 +174,13 @@ public class BidirectionalOneToOneInstanceTest { @GeneratedValue private Long id; - @Column(name = "uuid", unique = true, updatable = false) + @Column(name = "business_id", unique = true, updatable = false) private Long businessId; @OneToOne(fetch = FetchType.LAZY, mappedBy = "bar") private FooEntity foo; - private Date date; + private Double aDouble; public FooEntity getFoo() { return foo; @@ -179,12 +206,12 @@ public class BidirectionalOneToOneInstanceTest { this.businessId = businessId; } - public Date getDate() { - return date; + public Double getaDouble() { + return aDouble; } - public void setDate(Date date) { - this.date = date; + public void setaDouble(Double aDouble) { + this.aDouble = aDouble; } } }