diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ArrayInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ArrayInitializer.java index 1417600a7f73..1af81b9fc668 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ArrayInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ArrayInitializer.java @@ -132,10 +132,13 @@ protected void resolveInstanceSubInitializers(ImmediateCollectionInitializerData final Initializer initializer = elementAssembler.getInitializer(); if ( initializer != null ) { final RowProcessingState rowProcessingState = data.getRowProcessingState(); - final Integer index = listIndexAssembler.assemble( rowProcessingState ); + Integer index = listIndexAssembler.assemble( rowProcessingState ); if ( index != null ) { final PersistentArrayHolder arrayHolder = getCollectionInstance( data ); assert arrayHolder != null; + if ( indexBase != 0 ) { + index -= indexBase; + } initializer.resolveInstance( Array.get( arrayHolder.getArray(), index ), rowProcessingState ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ListInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ListInitializer.java index 67e9c16a74fb..4ab0d800e2d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ListInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/ListInitializer.java @@ -123,10 +123,13 @@ protected void resolveInstanceSubInitializers(ImmediateCollectionInitializerData final Initializer initializer = elementAssembler.getInitializer(); if ( initializer != null ) { final RowProcessingState rowProcessingState = data.getRowProcessingState(); - final Integer index = listIndexAssembler.assemble( rowProcessingState ); + Integer index = listIndexAssembler.assemble( rowProcessingState ); if ( index != null ) { final PersistentList list = getCollectionInstance( data ); assert list != null; + if ( listIndexBase != 0 ) { + index -= listIndexBase; + } initializer.resolveInstance( list.get( index ), rowProcessingState ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexArrayInitializerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexArrayInitializerTest.java new file mode 100644 index 000000000000..c35b57f615aa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexArrayInitializerTest.java @@ -0,0 +1,110 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.collections; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderColumn; +import org.hibernate.annotations.ListIndexBase; +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.Test; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@JiraKey("HHH-18876") +@DomainModel( + annotatedClasses = { + OrderColumnListIndexArrayInitializerTest.Person.class, + OrderColumnListIndexArrayInitializerTest.Phone.class, + } +) +@SessionFactory +class OrderColumnListIndexArrayInitializerTest { + + @Test + void hhh18876Test(SessionFactoryScope scope) { + + // prepare data + scope.inTransaction( session -> { + + // person + Person person = new Person(); + person.id = 1L; + session.persist( person ); + + // add phone + Phone phone = new Phone(); + phone.id = 1L; + phone.person = person; + + person.phones = new Phone[1]; + person.phones[0] = phone; + + // add children + Person children = new Person(); + children.id = 2L; + children.mother = person; + + person.children = new Person[1]; + person.children[0] = children; + } ); + + // load and assert + scope.inTransaction( session -> { + Person person = session.createSelectionQuery( "select p from Person p where id=1", Person.class ) + .getSingleResult(); + assertNotNull( person ); + assertEquals( 1, person.phones.length ); + assertNotNull( person.phones[0] ); + assertEquals( 1, person.children.length ); + assertEquals( person, person.children[0].mother ); + } ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + Long id; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "person", cascade = CascadeType.ALL) + @OrderColumn(name = "order_id") + @ListIndexBase(100) + Phone[] phones; + + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinTable(name = "parent_child_relationships", joinColumns = @JoinColumn(name = "parent_id"), + inverseJoinColumns = @JoinColumn(name = "child_id")) + @OrderColumn(name = "pos") + @ListIndexBase(200) + Person[] children; + + @ManyToOne + @JoinColumn(name = "mother_id") + Person mother; + } + + @Entity(name = "Phone") + public static class Phone { + + @Id + Long id; + + @ManyToOne + Person person; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexHHH18771ListInitializerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexHHH18771ListInitializerTest.java new file mode 100644 index 000000000000..93a3e88d1cf7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/OrderColumnListIndexHHH18771ListInitializerTest.java @@ -0,0 +1,148 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.collections; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.ListIndexBase; +import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderColumn; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @author Selaron + */ +public class OrderColumnListIndexHHH18771ListInitializerTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class, + }; + } + + @Test + public void testLifecycle() { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person( 1L ); + entityManager.persist( person ); + person.addPhone( new Phone( 1L ) ); + person.getChildren().add( new Person( 2L ) ); + person.getChildren().get( 0 ).setMother( person ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = entityManager.find( Person.class, 1L ); + assertNotNull( person ); + assertEquals( 1, person.getPhones().size() ); + assertNotNull( person.getPhones().get( 0 ) ); + assertEquals( 1, person.getChildren().size() ); + assertEquals( person, person.getChildren().get( 0 ).getMother() ); + } ); + } + + @Entity(name = "Person") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + public static class Person { + + @Id + private Long id; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "person", cascade = CascadeType.ALL) + @OrderColumn(name = "order_id") + @ListIndexBase(100) + private List phones = new ArrayList<>(); + + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinTable(name = "parent_child_relationships", joinColumns = @JoinColumn(name = "parent_id"), inverseJoinColumns = @JoinColumn(name = "child_id")) + @OrderColumn(name = "pos") + @ListIndexBase(200) + private List children = new ArrayList<>(); + + @ManyToOne + @JoinColumn(name = "mother_id") + private Person mother; + + public Person() { + } + + public Person(Long id) { + this.id = id; + } + + public List getPhones() { + return phones; + } + + public void addPhone(Phone phone) { + phones.add( phone ); + phone.setPerson( this ); + } + + public List getChildren() { + return children; + } + + public Person getMother() { + return mother; + } + + public void setMother(Person mother) { + this.mother = mother; + } + + public void removePhone(Phone phone) { + phones.remove( phone ); + phone.setPerson( null ); + } + } + + @Entity(name = "Phone") + public static class Phone { + + @Id + private Long id; + + @ManyToOne + private Person person; + + public Phone() { + } + + public Phone(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } +}