HHH-18730 Multi-column association in aggregate component doesn't work

This commit is contained in:
Christian Beikov 2024-10-15 17:23:43 +02:00
parent d647599fe9
commit 9bb5e64521
2 changed files with 187 additions and 19 deletions

View File

@ -10,6 +10,7 @@ import java.sql.Clob;
import java.sql.NClob; import java.sql.NClob;
import java.sql.SQLException; import java.sql.SQLException;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.Internal; import org.hibernate.Internal;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.BasicValuedMapping;
@ -127,13 +128,13 @@ public class StructHelper {
private static int injectJdbcValues( private static int injectJdbcValues(
EmbeddableMappingType embeddableMappingType, EmbeddableMappingType embeddableMappingType,
Object domainValue, @Nullable Object domainValue,
Object[] jdbcValues, Object[] jdbcValues,
int jdbcIndex, int jdbcIndex,
WrapperOptions options) throws SQLException { WrapperOptions options) throws SQLException {
return injectJdbcValues( return injectJdbcValues(
embeddableMappingType, embeddableMappingType,
embeddableMappingType.getValues( domainValue ), domainValue == null ? null : embeddableMappingType.getValues( domainValue ),
jdbcValues, jdbcValues,
jdbcIndex, jdbcIndex,
options options
@ -142,12 +143,15 @@ public class StructHelper {
private static int injectJdbcValues( private static int injectJdbcValues(
EmbeddableMappingType embeddableMappingType, EmbeddableMappingType embeddableMappingType,
Object[] values, @Nullable Object[] values,
Object[] jdbcValues, Object[] jdbcValues,
int jdbcIndex, int jdbcIndex,
WrapperOptions options) throws SQLException { WrapperOptions options) throws SQLException {
final int jdbcValueCount = embeddableMappingType.getJdbcValueCount(); final int jdbcValueCount = embeddableMappingType.getJdbcValueCount();
final int valueCount = jdbcValueCount + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); final int valueCount = jdbcValueCount + ( embeddableMappingType.isPolymorphic() ? 1 : 0 );
if ( values == null ) {
return valueCount;
}
int offset = 0; int offset = 0;
for ( int i = 0; i < values.length; i++ ) { for ( int i = 0; i < values.length; i++ ) {
offset += injectJdbcValue( offset += injectJdbcValue(
@ -252,22 +256,13 @@ public class StructHelper {
); );
} }
else { else {
jdbcValueCount = embeddableMappingType.getJdbcValueCount() + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); jdbcValueCount = injectJdbcValues(
final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); embeddableMappingType,
final int numberOfValues = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); attributeValues[attributeIndex],
final Object[] subValues = embeddableMappingType.getValues( attributeValues[attributeIndex] ); jdbcValues,
int offset = 0; jdbcIndex,
for ( int i = 0; i < numberOfValues; i++ ) { options
offset += injectJdbcValue( );
getEmbeddedPart( embeddableMappingType, i ),
subValues,
i,
jdbcValues,
jdbcIndex + offset,
options
);
}
assert offset == jdbcValueCount;
} }
} }
else { else {

View File

@ -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;
}
}
}