HHH-18493 Resolving already initialized collection elements leads to assertion error
This commit is contained in:
parent
ab4439622b
commit
9b1eb785e5
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.sql.results.graph.collection.internal;
|
package org.hibernate.sql.results.graph.collection.internal;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
@ -131,9 +132,11 @@ public class ArrayInitializer extends AbstractImmediateCollectionInitializer<Abs
|
||||||
final Initializer<?> initializer = elementAssembler.getInitializer();
|
final Initializer<?> initializer = elementAssembler.getInitializer();
|
||||||
if ( initializer != null ) {
|
if ( initializer != null ) {
|
||||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||||
final Iterator iter = getCollectionInstance( data ).elements();
|
final Integer index = listIndexAssembler.assemble( rowProcessingState );
|
||||||
while ( iter.hasNext() ) {
|
if ( index != null ) {
|
||||||
initializer.resolveInstance( iter.next(), rowProcessingState );
|
final PersistentArrayHolder<?> arrayHolder = getCollectionInstance( data );
|
||||||
|
assert arrayHolder != null;
|
||||||
|
initializer.resolveInstance( Array.get( arrayHolder.getArray(), index ), rowProcessingState );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,19 +123,7 @@ public class BagInitializer extends AbstractImmediateCollectionInitializer<Abstr
|
||||||
protected void resolveInstanceSubInitializers(ImmediateCollectionInitializerData data) {
|
protected void resolveInstanceSubInitializers(ImmediateCollectionInitializerData data) {
|
||||||
final Initializer<?> initializer = elementAssembler.getInitializer();
|
final Initializer<?> initializer = elementAssembler.getInitializer();
|
||||||
if ( initializer != null ) {
|
if ( initializer != null ) {
|
||||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
initializer.resolveKey( data.getRowProcessingState() );
|
||||||
final PersistentCollection<?> persistentCollection = getCollectionInstance( data );
|
|
||||||
assert persistentCollection != null;
|
|
||||||
if ( persistentCollection instanceof PersistentBag<?> ) {
|
|
||||||
for ( Object element : ( (PersistentBag<?>) persistentCollection ) ) {
|
|
||||||
initializer.resolveInstance( element, rowProcessingState );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for ( Object element : ( (PersistentIdentifierBag<?>) persistentCollection ) ) {
|
|
||||||
initializer.resolveInstance( element, rowProcessingState );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,10 +123,11 @@ public class ListInitializer extends AbstractImmediateCollectionInitializer<Abst
|
||||||
final Initializer<?> initializer = elementAssembler.getInitializer();
|
final Initializer<?> initializer = elementAssembler.getInitializer();
|
||||||
if ( initializer != null ) {
|
if ( initializer != null ) {
|
||||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||||
|
final Integer index = listIndexAssembler.assemble( rowProcessingState );
|
||||||
|
if ( index != null ) {
|
||||||
final PersistentList<?> list = getCollectionInstance( data );
|
final PersistentList<?> list = getCollectionInstance( data );
|
||||||
assert list != null;
|
assert list != null;
|
||||||
for ( Object element : list ) {
|
initializer.resolveInstance( list.get( index ), rowProcessingState );
|
||||||
initializer.resolveInstance( element, rowProcessingState );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,17 +121,27 @@ public class MapInitializer extends AbstractImmediateCollectionInitializer<Abstr
|
||||||
protected void resolveInstanceSubInitializers(ImmediateCollectionInitializerData data) {
|
protected void resolveInstanceSubInitializers(ImmediateCollectionInitializerData data) {
|
||||||
final Initializer<?> keyInitializer = mapKeyAssembler.getInitializer();
|
final Initializer<?> keyInitializer = mapKeyAssembler.getInitializer();
|
||||||
final Initializer<?> valueInitializer = mapValueAssembler.getInitializer();
|
final Initializer<?> valueInitializer = mapValueAssembler.getInitializer();
|
||||||
if ( keyInitializer != null || valueInitializer != null ) {
|
|
||||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||||
|
if ( keyInitializer == null && valueInitializer != null ) {
|
||||||
|
// For now, we only support resolving the value initializer instance when keys have no initializer,
|
||||||
|
// though we could also support map keys with an initializer given that the initialized java type:
|
||||||
|
// * is an entity that uses only the primary key in equals/hashCode.
|
||||||
|
// If the primary key type is an embeddable, the next condition must hold for that
|
||||||
|
// * or is an embeddable that has no initializers for fields being used in the equals/hashCode
|
||||||
|
// which violate this same requirement (recursion)
|
||||||
|
final Object key = mapKeyAssembler.assemble( rowProcessingState );
|
||||||
|
if ( key != null ) {
|
||||||
final PersistentMap<?, ?> map = getCollectionInstance( data );
|
final PersistentMap<?, ?> map = getCollectionInstance( data );
|
||||||
assert map != null;
|
assert map != null;
|
||||||
for ( Map.Entry<?, ?> entry : map.entrySet() ) {
|
valueInitializer.resolveInstance( map.get( key ), rowProcessingState );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
if ( keyInitializer != null ) {
|
if ( keyInitializer != null ) {
|
||||||
keyInitializer.resolveInstance( entry.getKey(), rowProcessingState );
|
keyInitializer.resolveKey( rowProcessingState );
|
||||||
}
|
}
|
||||||
if ( valueInitializer != null ) {
|
if ( valueInitializer != null ) {
|
||||||
valueInitializer.resolveInstance( entry.getValue(), rowProcessingState );
|
valueInitializer.resolveKey( rowProcessingState );
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,12 +97,7 @@ public class SetInitializer extends AbstractImmediateCollectionInitializer<Abstr
|
||||||
protected void resolveInstanceSubInitializers(ImmediateCollectionInitializerData data) {
|
protected void resolveInstanceSubInitializers(ImmediateCollectionInitializerData data) {
|
||||||
final Initializer<?> initializer = elementAssembler.getInitializer();
|
final Initializer<?> initializer = elementAssembler.getInitializer();
|
||||||
if ( initializer != null ) {
|
if ( initializer != null ) {
|
||||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
initializer.resolveKey( data.getRowProcessingState() );
|
||||||
final PersistentSet<?> set = getCollectionInstance( data );
|
|
||||||
assert set != null;
|
|
||||||
for ( Object element : set ) {
|
|
||||||
initializer.resolveInstance( element, rowProcessingState );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,307 @@
|
||||||
|
/*
|
||||||
|
* 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.inheritance.join;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.Jira;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
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 jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToMany;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@DomainModel( annotatedClasses = {
|
||||||
|
ReloadMultipleCollectionElementsTest.Flight.class,
|
||||||
|
ReloadMultipleCollectionElementsTest.Ticket.class,
|
||||||
|
ReloadMultipleCollectionElementsTest.Customer.class,
|
||||||
|
ReloadMultipleCollectionElementsTest.Company.class
|
||||||
|
} )
|
||||||
|
@SessionFactory
|
||||||
|
@Jira("https://hibernate.atlassian.net/browse/HHH-18493")
|
||||||
|
public class ReloadMultipleCollectionElementsTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( s -> {
|
||||||
|
Flight f2 = new Flight();
|
||||||
|
f2.setId(1L);
|
||||||
|
f2.setName("Flight two");
|
||||||
|
|
||||||
|
Company us = new Company();
|
||||||
|
us.setName("Airline 2");
|
||||||
|
f2.setCompany(us);
|
||||||
|
|
||||||
|
Customer c1 = new Customer();
|
||||||
|
c1.setId( 1L );
|
||||||
|
c1.setName("Tom");
|
||||||
|
|
||||||
|
Customer c2 = new Customer();
|
||||||
|
c2.setId( 2L );
|
||||||
|
c2.setName("Pete");
|
||||||
|
|
||||||
|
Ticket t1 = new Ticket();
|
||||||
|
t1.setId(1L);
|
||||||
|
t1.setCustomer(c2);
|
||||||
|
t1.setNumber( "123" );
|
||||||
|
|
||||||
|
Ticket t2 = new Ticket();
|
||||||
|
t2.setId(2L);
|
||||||
|
t2.setCustomer(c2);
|
||||||
|
t2.setNumber( "456" );
|
||||||
|
|
||||||
|
f2.setCustomers(Set.of(c1, c2));
|
||||||
|
|
||||||
|
s.persist(c1);
|
||||||
|
s.persist(c2);
|
||||||
|
s.persist(f2);
|
||||||
|
s.persist(t1);
|
||||||
|
s.persist(t2);
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void cleanup(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( s -> {
|
||||||
|
s.createMutationQuery( "delete from Ticket" ).executeUpdate();
|
||||||
|
s.createMutationQuery( "delete from Flight" ).executeUpdate();
|
||||||
|
s.createMutationQuery( "delete from Customer" ).executeUpdate();
|
||||||
|
s.createMutationQuery( "delete from Company" ).executeUpdate();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveElementOfInitializedCollection(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( s -> {
|
||||||
|
// First load all customers with their flights collection and corresponding customers
|
||||||
|
List<Customer> customers = s.createQuery(
|
||||||
|
"from Customer c join fetch c.flights f join fetch f.customers order by c.id",
|
||||||
|
Customer.class
|
||||||
|
).getResultList();
|
||||||
|
assertEquals( 2, customers.size() );
|
||||||
|
assertFalse( Hibernate.isInitialized( customers.get( 0 ).getTickets() ) );
|
||||||
|
assertFalse( Hibernate.isInitialized( customers.get( 1 ).getTickets() ) );
|
||||||
|
|
||||||
|
// Then load all flights with their customers collection, but in addition, also the customers tickets
|
||||||
|
// This will trigger resolveInstance(Object, Data) with the existing collection and will
|
||||||
|
// fetch tickets data into existing customers
|
||||||
|
s.createQuery( "from Flight f join fetch f.customers c left join fetch c.tickets", Flight.class ).getResultList();
|
||||||
|
|
||||||
|
assertTrue( Hibernate.isInitialized( customers.get( 0 ).getTickets() ) );
|
||||||
|
assertTrue( Hibernate.isInitialized( customers.get( 1 ).getTickets() ) );
|
||||||
|
assertEquals( 0, customers.get( 0 ).getTickets().size() );
|
||||||
|
assertEquals( 2, customers.get( 1 ).getTickets().size() );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity( name = "Flight" )
|
||||||
|
public static class Flight {
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private Company company;
|
||||||
|
private Set<Customer> customers;
|
||||||
|
|
||||||
|
public Flight() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "flight_id")
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long long1) {
|
||||||
|
id = long1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Column(updatable = false, name = "flight_name", nullable = false, length = 50)
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String string) {
|
||||||
|
name = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ManyToOne(cascade = {CascadeType.ALL})
|
||||||
|
@JoinColumn(name = "comp_id")
|
||||||
|
public Company getCompany() {
|
||||||
|
return company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompany(Company company) {
|
||||||
|
this.company = company;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
|
||||||
|
public Set<Customer> getCustomers() {
|
||||||
|
return customers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomers(Set<Customer> customers) {
|
||||||
|
this.customers = customers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity( name = "Ticket" )
|
||||||
|
public static class Ticket {
|
||||||
|
|
||||||
|
Long id;
|
||||||
|
String number;
|
||||||
|
Customer customer;
|
||||||
|
|
||||||
|
public Ticket() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long long1) {
|
||||||
|
id = long1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Column(name = "nr")
|
||||||
|
public String getNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumber(String string) {
|
||||||
|
number = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToOne(cascade = CascadeType.ALL)
|
||||||
|
@JoinColumn(name = "CUSTOMER_ID")
|
||||||
|
public Customer getCustomer() {
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomer(Customer customer) {
|
||||||
|
this.customer = customer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity( name = "Customer" )
|
||||||
|
public static class Customer {
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private String address;
|
||||||
|
private Set<Ticket> tickets;
|
||||||
|
private Set<Flight> flights;
|
||||||
|
|
||||||
|
// Address address;
|
||||||
|
|
||||||
|
public Customer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
|
||||||
|
public Set<Ticket> getTickets() {
|
||||||
|
return tickets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTickets(Set<Ticket> tickets) {
|
||||||
|
this.tickets = tickets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "customers")
|
||||||
|
public Set<Flight> getFlights() {
|
||||||
|
return flights;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlights(Set<Flight> flights) {
|
||||||
|
this.flights = flights;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity( name = "Company" )
|
||||||
|
public static class Company {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private Set<Flight> flights = new HashSet<Flight>();
|
||||||
|
|
||||||
|
public Company() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "comp_id")
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long newId) {
|
||||||
|
id = newId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String string) {
|
||||||
|
name = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "company")
|
||||||
|
@Column(name = "flight_id")
|
||||||
|
public Set<Flight> getFlights() {
|
||||||
|
return flights;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlights(Set<Flight> flights) {
|
||||||
|
this.flights = flights;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue