From b5171ec2f5047141311c6e119bc5d27fc8abaed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Cedomir=20Igaly?= Date: Mon, 7 Jul 2025 12:06:45 +0200 Subject: [PATCH 1/2] HHH-19596 Test case from example in Jira issue --- ...tructArrayWithNullElementTestDemoTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/array/StructArrayWithNullElementTestDemoTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/array/StructArrayWithNullElementTestDemoTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/array/StructArrayWithNullElementTestDemoTest.java new file mode 100644 index 000000000000..bae64a5d622a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/array/StructArrayWithNullElementTestDemoTest.java @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.array; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.annotations.Struct; +import org.hibernate.testing.orm.junit.DialectFeatureChecks.SupportsStructAggregate; +import org.hibernate.testing.orm.junit.DialectFeatureChecks.SupportsTypedArrays; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +@SessionFactory +@DomainModel(annotatedClasses = { + StructArrayWithNullElementTestDemoTest.Book.class, + StructArrayWithNullElementTestDemoTest.Author.class +}) +@RequiresDialectFeature(feature = SupportsStructAggregate.class) +@RequiresDialectFeature(feature = SupportsTypedArrays.class) +class StructArrayWithNullElementTestDemoTest { + + @Test + void test(SessionFactoryScope scope) { + scope.inTransaction( session -> { + var book = new Book(); + book.id = 1; + book.authors = Arrays.asList( + new Author( "John", "Smith" ), + null + ); + session.persist( book ); + } ); + + scope.inSession( session -> { + final var book = session.find( Book.class, 1 ); + assertEquals( 2, book.authors.size() ); + assertEquals( new Author( "John", "Smith" ), book.authors.get( 0 ) ); + assertNull( book.authors.get( 1 ) ); + } ); + } + + @Entity(name = "Book") + @Table(name = "books") + static class Book { + @Id + int id; + List authors; + } + + @Embeddable + @Struct(name = "Author") + record Author(String firstName, String lastName) { + } +} From cd5a1fb20e6aacd4f5274ba298daf4c0baa455be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Cedomir=20Igaly?= Date: Tue, 8 Jul 2025 11:01:53 +0200 Subject: [PATCH 2/2] HHH-19596 Added checks to avoid NPE's --- .../type/AbstractPostgreSQLStructJdbcType.java | 6 +++++- .../java/spi/EmbeddableAggregateJavaType.java | 3 +++ .../type/descriptor/jdbc/ArrayJdbcType.java | 14 ++++++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java index bf0d680227ad..132e247bf15f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java @@ -1156,7 +1156,11 @@ public Object createJdbcValue(Object domainValue, WrapperOptions options) throws public Object[] extractJdbcValues(Object rawJdbcValue, WrapperOptions options) throws SQLException { assert embeddableMappingType != null; final Object[] array = new Object[embeddableMappingType.getJdbcValueCount()]; - deserializeStruct( getRawStructFromJdbcValue( rawJdbcValue ), 0, 0, array, true, options ); + final String struct = getRawStructFromJdbcValue( rawJdbcValue ); + if ( struct == null ) { + return null; + } + deserializeStruct( struct, 0, 0, array, true, options ); if ( inverseOrderMapping != null ) { StructHelper.orderJdbcValues( embeddableMappingType, inverseOrderMapping, array.clone(), array ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/EmbeddableAggregateJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/EmbeddableAggregateJavaType.java index 644bfd3b90da..d3dfb8e39b14 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/EmbeddableAggregateJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/EmbeddableAggregateJavaType.java @@ -94,6 +94,9 @@ public X unwrap(T value, Class type, WrapperOptions options) { @Override public T wrap(X value, WrapperOptions options) { + if ( value == null ) { + return null; + } if ( getJavaTypeClass().isInstance( value ) ) { //noinspection unchecked return (T) value; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java index 1f0bb009e81b..748276f46e9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/ArrayJdbcType.java @@ -176,10 +176,16 @@ protected X getArray(BasicExtractor extractor, java.sql.Array array, Wrap final Object rawArray = array.getArray(); final Object[] domainObjects = new Object[Array.getLength( rawArray )]; for ( int i = 0; i < domainObjects.length; i++ ) { - final Object[] aggregateRawValues = aggregateJdbcType.extractJdbcValues( Array.get( rawArray, i ), options ); - final StructAttributeValues attributeValues = - StructHelper.getAttributeValues( embeddableMappingType, aggregateRawValues, options ); - domainObjects[i] = instantiate( embeddableMappingType, attributeValues ); + final Object rawJdbcValue = Array.get( rawArray, i ); + if ( rawJdbcValue == null ) { + domainObjects[i] = null; + } + else { + final Object[] aggregateRawValues = aggregateJdbcType.extractJdbcValues( rawJdbcValue, options ); + final StructAttributeValues attributeValues = + StructHelper.getAttributeValues( embeddableMappingType, aggregateRawValues, options ); + domainObjects[i] = instantiate( embeddableMappingType, attributeValues ); + } } return extractor.getJavaType().wrap( domainObjects, options ); }