From cb3b8ec606f7c5dce8652fbc5587cb3623b98f5b Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 11 Jul 2025 17:30:23 +0200 Subject: [PATCH 1/2] HHH-19585 Add test for issue --- ...lectionWithOrderInsertAndOrderUpdates.java | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/TestCollectionWithOrderInsertAndOrderUpdates.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/TestCollectionWithOrderInsertAndOrderUpdates.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/TestCollectionWithOrderInsertAndOrderUpdates.java new file mode 100644 index 000000000000..9883e425c188 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/TestCollectionWithOrderInsertAndOrderUpdates.java @@ -0,0 +1,272 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.collectionelement; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinColumns; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import org.hibernate.cfg.BatchSettings; +import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + + +@DomainModel( + annotatedClasses = { + TestCollectionWithOrderInsertAndOrderUpdates.ChildEntity.class, + TestCollectionWithOrderInsertAndOrderUpdates.ParentEntity.class, + TestCollectionWithOrderInsertAndOrderUpdates.AnotherEntity.class, + TestCollectionWithOrderInsertAndOrderUpdates.ThirdEntity.class, + } +) +@SessionFactory +@ServiceRegistry( + settings = { + @Setting(name = BatchSettings.ORDER_INSERTS, value = "true"), + @Setting(name = BatchSettings.ORDER_UPDATES, value = "true"), + } +) +@BytecodeEnhanced(runNotEnhancedAsWell = true) +@JiraKey("HHH-19585") +public class TestCollectionWithOrderInsertAndOrderUpdates { + + @BeforeEach + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + ParentEntity parentEntity = new ParentEntity( 1L, "Parent 1", 0L ); + parentEntity.addChild( new ChildEntity( 1L, "Child 1" ) ); + session.persist( parentEntity ); + + ParentEntity parentEntity2 = new ParentEntity( 2l, "Parent 2", 0L ); + parentEntity2.addChild( new ChildEntity( 2L, "Child 2" ) ); + session.persist( parentEntity2 ); + + ParentEntity parentEntity3 = new ParentEntity( 3l, "Parent 3", 0L ); + session.persist( parentEntity3 ); + + AnotherEntity anotherEntity = new AnotherEntity( 1L, "1", 1L, parentEntity ); + + AnotherEntity anotherEntity2 = new AnotherEntity( 2L, "2", 2L, parentEntity2 ); + AnotherEntity anotherEntity3 = new AnotherEntity( 3L, "3", 3L, parentEntity3 ); + + ThirdEntity thirdEntity = new ThirdEntity( 1L, "123" ); + thirdEntity.addAnotherEntity( anotherEntity ); + thirdEntity.addAnotherEntity( anotherEntity2 ); + thirdEntity.addAnotherEntity( anotherEntity3 ); + + session.persist( thirdEntity ); + } + ); + } + + @Test + public void testGetReference(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + ThirdEntity thirdEntity = session.getReference( ThirdEntity.class, 1L ); + for ( AnotherEntity thirdEntityDetail : thirdEntity.getAnotherEntities() ) { + thirdEntityDetail.getParentEntity().getName(); + } + } + ); + + scope.inTransaction( + session -> { + ParentEntity parentEntity = session.find( ParentEntity.class, 1L ); + assertThat( parentEntity.getChildren().size() ).isEqualTo( 1 ); + + ParentEntity parentEntity2 = session.find( ParentEntity.class, 2L ); + assertThat( parentEntity2.getChildren().size() ).isEqualTo( 1 ); + + ParentEntity parentEntity3 = session.find( ParentEntity.class, 3L ); + assertThat( parentEntity3.getChildren().size() ).isEqualTo( 0 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + + @Entity(name = "ChildEntity") + public static class ChildEntity { + @Id + private Long id; + + private String name; + + public ChildEntity() { + } + + public ChildEntity(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "ParentEntity") + public static class ParentEntity { + @Id + private Long id; + + private String name; + + @Column(name = "secondid") + private Long secondId; + + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumns({ + @JoinColumn(name = "parentid", referencedColumnName = "id"), + @JoinColumn(name = "secondid", referencedColumnName = "secondid") + }) + private List children; + + public ParentEntity() { + } + + public ParentEntity(Long id, String name, Long secondId) { + this.id = id; + this.name = name; + this.secondId = secondId; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public List getChildren() { + return children; + } + + public void addChild(ChildEntity childEntity) { + if ( this.children == null ) { + this.children = new ArrayList<>(); + } + this.children.add( childEntity ); + } + } + + @Entity(name = "AnotherEntity") + public static class AnotherEntity { + @Id + private Long id; + + private String name; + + @Column(name = "parentid") + private Long parentId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parentid", insertable = false, updatable = false, referencedColumnName = "id") + private ParentEntity parentEntity; + + public AnotherEntity() { + } + + public AnotherEntity(Long id, String name, Long parentId, ParentEntity parentEntity) { + this.id = id; + this.name = name; + this.parentId = parentId; + this.parentEntity = parentEntity; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Long getParentId() { + return parentId; + } + + public ParentEntity getParentEntity() { + return parentEntity; + } + } + + @Entity(name = "ThirdEntity") + public static class ThirdEntity { + @Id + private Long id; + + private String name; + + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "thirdEntityHeader", referencedColumnName = "id") + private List anotherEntities; + + public ThirdEntity() { + } + + public ThirdEntity(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public List getAnotherEntities() { + return anotherEntities; + } + + public void addAnotherEntity(AnotherEntity anotherEntity) { + if ( this.anotherEntities == null ) { + this.anotherEntities = new ArrayList<>(); + } + this.anotherEntities.add( anotherEntity ); + } + } + + +} From 33dafd3ab70279d99406d55efad5dd659ca29f40 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 11 Jul 2025 17:31:22 +0200 Subject: [PATCH 2/2] HHH-19585 Object relationship mapping issues | java.lang.NullPointerException: Cannot invoke java.lang.Comparable.compareTo(Object) because 'one' is null --- .../org/hibernate/sql/results/graph/InitializerParent.java | 7 ++++++- .../embeddable/internal/EmbeddableInitializerImpl.java | 2 +- .../graph/entity/internal/EntityInitializerImpl.java | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/InitializerParent.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/InitializerParent.java index b10f8741239b..df2fd0f42ebc 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/InitializerParent.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/InitializerParent.java @@ -6,6 +6,9 @@ */ package org.hibernate.sql.results.graph; + +import org.hibernate.Hibernate; + /** * Provides access to information about the owner/parent of a fetch * in relation to the current "row" being processed. @@ -13,5 +16,7 @@ * @author Steve Ebersole */ public interface InitializerParent extends Initializer { - + default Object getResolvedInstanceNoProxy(Data data){ + return Hibernate.unproxy( getResolvedInstance( data ) ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableInitializerImpl.java index 762af601e1a1..ad39bda4cafe 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableInitializerImpl.java @@ -465,7 +465,7 @@ private void prepareCompositeInstance(EmbeddableInitializerData data) { if ( parent != null && embedded instanceof VirtualModelPart && !isPartOfKey && data.getState() != State.MISSING ) { final InitializerData subData = parent.getData( data.getRowProcessingState() ); parent.resolveInstance( subData ); - data.setInstance( parent.getResolvedInstance( subData ) ); + data.setInstance( parent.getResolvedInstanceNoProxy( subData ) ); if ( data.getState() == State.INITIALIZED ) { return; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java index ec4a393395cd..f433733ace98 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java @@ -1794,6 +1794,11 @@ public String toString() { return "EntityJoinedFetchInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; } + @Override + public Object getResolvedInstanceNoProxy(EntityInitializerData data) { + return data.entityInstanceForNotify; + } + //######################### // For Hibernate Reactive //#########################