diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java index 625c5de530..3d26dd55b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/StructHelper.java @@ -10,6 +10,7 @@ import java.sql.Clob; import java.sql.NClob; import java.sql.SQLException; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.Internal; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; @@ -127,13 +128,13 @@ public class StructHelper { private static int injectJdbcValues( EmbeddableMappingType embeddableMappingType, - Object domainValue, + @Nullable Object domainValue, Object[] jdbcValues, int jdbcIndex, WrapperOptions options) throws SQLException { return injectJdbcValues( embeddableMappingType, - embeddableMappingType.getValues( domainValue ), + domainValue == null ? null : embeddableMappingType.getValues( domainValue ), jdbcValues, jdbcIndex, options @@ -142,12 +143,15 @@ public class StructHelper { private static int injectJdbcValues( EmbeddableMappingType embeddableMappingType, - Object[] values, + @Nullable Object[] values, Object[] jdbcValues, int jdbcIndex, WrapperOptions options) throws SQLException { final int jdbcValueCount = embeddableMappingType.getJdbcValueCount(); final int valueCount = jdbcValueCount + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); + if ( values == null ) { + return valueCount; + } int offset = 0; for ( int i = 0; i < values.length; i++ ) { offset += injectJdbcValue( @@ -252,22 +256,13 @@ public class StructHelper { ); } else { - jdbcValueCount = embeddableMappingType.getJdbcValueCount() + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); - final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); - final int numberOfValues = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); - final Object[] subValues = embeddableMappingType.getValues( attributeValues[attributeIndex] ); - int offset = 0; - for ( int i = 0; i < numberOfValues; i++ ) { - offset += injectJdbcValue( - getEmbeddedPart( embeddableMappingType, i ), - subValues, - i, - jdbcValues, - jdbcIndex + offset, - options - ); - } - assert offset == jdbcValueCount; + jdbcValueCount = injectJdbcValues( + embeddableMappingType, + attributeValues[attributeIndex], + jdbcValues, + jdbcIndex, + options + ); } } else { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToOneCompositeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToOneCompositeTest.java new file mode 100644 index 0000000000..52570af5dc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentManyToOneCompositeTest.java @@ -0,0 +1,173 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.component; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import org.hibernate.Hibernate; +import org.hibernate.annotations.Struct; +import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; +import org.hibernate.testing.orm.junit.BootstrapServiceRegistry; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +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.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BootstrapServiceRegistry( + // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete + integrators = SharedDriverManagerTypeCacheClearingIntegrator.class +) +@DomainModel( + annotatedClasses = { + StructComponentManyToOneCompositeTest.Book.class + } +) +@SessionFactory +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class) +public class StructComponentManyToOneCompositeTest { + + @BeforeEach + public void setUp(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book1 = new Book(); + book1.id = 1L; + book1.id2 = 1L; + book1.title = "Hibernate 3"; + book1.author = new Author( "Gavin", null ); + + session.persist( book1 ); + + Book book2 = new Book(); + book2.id = 2L; + book2.id2 = 2L; + book2.title = "Hibernate 6"; + book2.author = new Author( "Steve", book1 ); + + session.persist( book2 ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope){ + scope.inTransaction( + session -> + session.createQuery( "delete from Book" ).executeUpdate() + ); + } + + @Test + public void testGet(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult(); + assertFalse( Hibernate.isInitialized( book.author.getFavoriteBook() ) ); + assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() ); + } + ); + } + + @Test + public void testJoin(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + Book book = session.createQuery( + "from Book b join fetch b.author.favoriteBook where b.id = 2", + Book.class + ).getSingleResult(); + assertTrue( Hibernate.isInitialized( book.author.getFavoriteBook() ) ); + assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() ); + } + ); + } + + @Entity(name = "Book") + public static class Book { + @Id + private Long id; + @Id + private Long id2; + private String title; + private Author author; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getId2() { + return id2; + } + + public void setId2(Long id2) { + this.id2 = id2; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + } + + @Embeddable + @Struct( name = "author_type") + public static class Author { + + private String name; + @ManyToOne(fetch = FetchType.LAZY) + private Book favoriteBook; + + public Author() { + } + + public Author(String name, Book favoriteBook) { + this.name = name; + this.favoriteBook = favoriteBook; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Book getFavoriteBook() { + return favoriteBook; + } + + public void setFavoriteBook(Book favoriteBook) { + this.favoriteBook = favoriteBook; + } + } + +}