From 4308c5dac56d611043ee5ab90330d131cccc6425 Mon Sep 17 00:00:00 2001 From: Vincent Bouthinon Date: Wed, 3 Apr 2024 18:17:35 +0200 Subject: [PATCH] HHH-15722 @OneToMany mappedBy with a @Any --- .../boot/model/internal/CollectionBinder.java | 4 +- .../boot/model/internal/TableBinder.java | 4 + .../main/java/org/hibernate/mapping/Any.java | 4 + .../internal/ToOneAttributeMapping.java | 2 +- .../AbstractCollectionPersister.java | 40 +++- .../manytoone/ManyToOneWithAnyTest.java | 206 ++++++++++++++++++ 6 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/ManyToOneWithAnyTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java index dd4a09b6d9..2d8f5c0938 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java @@ -15,7 +15,6 @@ import java.util.Map; import java.util.Properties; import java.util.function.Supplier; -import jakarta.persistence.Entity; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; @@ -131,6 +130,7 @@ import jakarta.persistence.CollectionTable; import jakarta.persistence.ConstraintMode; import jakarta.persistence.ElementCollection; import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; import jakarta.persistence.Index; @@ -163,6 +163,7 @@ import static org.hibernate.boot.model.internal.AnnotatedJoinColumns.buildJoinTa import static org.hibernate.boot.model.internal.BinderHelper.buildAnyValue; import static org.hibernate.boot.model.internal.BinderHelper.checkMappedByType; import static org.hibernate.boot.model.internal.BinderHelper.createSyntheticPropertyReference; +import static org.hibernate.boot.model.internal.BinderHelper.extractFromPackage; import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy; import static org.hibernate.boot.model.internal.BinderHelper.getFetchMode; import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation; @@ -175,7 +176,6 @@ import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable; import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators; import static org.hibernate.boot.model.internal.HCANNHelper.findAnnotation; import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder; -import static org.hibernate.boot.model.internal.BinderHelper.extractFromPackage; import static org.hibernate.boot.model.source.internal.hbm.ModelBinder.useEntityWhereClauseForCollections; import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle; import static org.hibernate.internal.util.ReflectHelper.getDefaultSupplier; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java index abcf922ca9..f79a6345dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/TableBinder.java @@ -22,6 +22,7 @@ import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.dialect.Dialect; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.mapping.Any; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; @@ -798,6 +799,9 @@ public class TableBinder { } return element.getColumns(); } + else if (value instanceof Any) { + return ( (Any) value ).getKeyDescriptor().getColumns(); + } else { return value.getColumns(); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java index 8c9ac66792..18b304049e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java @@ -109,6 +109,10 @@ public class Any extends SimpleValue { return discriminatorDescriptor; } + public BasicValue getKeyDescriptor() { + return keyDescriptor; + } + public MetaValue getMetaMapping() { return metaMapping; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java index b54f57314b..cd11a6b382 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java @@ -765,7 +765,7 @@ public class ToOneAttributeMapping targetKeyPropertyNames.add( prefix ); } if ( type.isComponentType() ) { - final ComponentType componentType = (ComponentType) type; + final CompositeType componentType = (CompositeType) type; final String[] propertyNames = componentType.getPropertyNames(); final Type[] componentTypeSubtypes = componentType.getSubtypes(); for ( int i = 0, propertyNamesLength = propertyNames.length; i < propertyNamesLength; i++ ) { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index 4d27053b14..60803f6102 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -65,11 +65,15 @@ import org.hibernate.loader.ast.internal.CollectionLoaderSubSelectFetch; import org.hibernate.loader.ast.internal.LoaderSqlAstCreationState; import org.hibernate.loader.ast.spi.BatchLoaderFactory; import org.hibernate.loader.ast.spi.CollectionLoader; +import org.hibernate.mapping.Any; +import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; import org.hibernate.mapping.Formula; import org.hibernate.mapping.IdentifierCollection; import org.hibernate.mapping.IndexedCollection; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Table; import org.hibernate.mapping.Value; @@ -128,13 +132,17 @@ import org.hibernate.sql.model.jdbc.JdbcDeleteMutation; import org.hibernate.sql.model.jdbc.JdbcMutationOperation; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.type.AnyType; +import org.hibernate.type.BasicType; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; +import org.hibernate.type.MetaType; import org.hibernate.type.Type; import org.checkerframework.checker.nullness.qual.Nullable; +import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty; import static org.hibernate.internal.util.collections.CollectionHelper.arrayList; import static org.hibernate.jdbc.Expectations.createExpectation; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; @@ -303,9 +311,37 @@ public abstract class AbstractCollectionPersister spaces[i] = tables.next(); } - if ( StringHelper.isNotEmpty( collectionBootDescriptor.getWhere() ) ) { + String where = collectionBootDescriptor.getWhere(); + /* + * Add the predicate on the role in the WHERE clause before creating the SQL queries. + */ + if ( mappedByProperty != null && elementType.isEntityType() ) { + final String entityName = ( (EntityType) elementType ).getAssociatedEntityName(); + final PersistentClass persistentClass = creationContext.getBootModel().getEntityBinding( entityName ); + final Property property = persistentClass.getRecursiveProperty( mappedByProperty ); + final Value propertyValue = property.getValue(); + if ( propertyValue instanceof Any ) { + final Any any = (Any) propertyValue; + final BasicValue discriminatorDescriptor = any.getDiscriminatorDescriptor(); + final AnyType anyType = any.getType(); + final MetaType metaType = (MetaType) anyType.getDiscriminatorType(); + final Object discriminatorValue = metaType.getEntityNameToDiscriminatorValueMap().get( ownerPersister.getEntityName() ); + final BasicType discriminatorBaseType = (BasicType) metaType.getBaseType(); + final String discriminatorLiteral = discriminatorBaseType.getJdbcLiteralFormatter().toJdbcLiteral( + discriminatorValue, + creationContext.getDialect(), + creationContext.getSessionFactory().getWrapperOptions() + ); + where = getNonEmptyOrConjunctionIfBothNonEmpty( + where, + discriminatorDescriptor.getColumn().getText() + "=" + discriminatorLiteral + ); + } + } + + if ( StringHelper.isNotEmpty( where ) ) { hasWhere = true; - sqlWhereString = "(" + collectionBootDescriptor.getWhere() + ")"; + sqlWhereString = "(" + where + ")"; sqlWhereStringTemplate = Template.renderWhereStringTemplate( sqlWhereString, dialect, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/ManyToOneWithAnyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/ManyToOneWithAnyTest.java new file mode 100644 index 0000000000..5be9e4c4a2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/ManyToOneWithAnyTest.java @@ -0,0 +1,206 @@ +/* + * 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.mapping.manytoone; + +import java.util.Set; + +import org.hibernate.Session; +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyDiscriminatorValue; +import org.hibernate.annotations.AnyKeyJavaClass; +import org.hibernate.cfg.JdbcSettings; + +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +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.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Allows testing a @ManyToOne mappedBy relationship with a @Any as the return variable. + * + * @author Vincent Bouthinon + */ +@Jpa( + annotatedClasses = { + ManyToOneWithAnyTest.Library.class, + ManyToOneWithAnyTest.Book.class, + ManyToOneWithAnyTest.Shop.class + }, + integrationSettings = @Setting(name = JdbcSettings.SHOW_SQL, value = "true") +) +@JiraKey("HHH-15722") +class ManyToOneWithAnyTest { + + @Test + void testMappingManyToOneMappedByAny(EntityManagerFactoryScope scope) { + + scope.inTransaction( + entityManager -> { + Library library = new Library(); + Book firstBook = new Book(); + final Set books = Set.of( firstBook, new Book() ); + library.setBooks( books ); + + entityManager.persist( library ); + entityManager.flush(); + entityManager.clear(); + + firstBook = entityManager.unwrap( Session.class ) + .byId( firstBook.getClass() ) + .load( firstBook.getId() ); + + assertNotNull( firstBook ); + + entityManager.clear(); + + library = entityManager.unwrap( Session.class ) + .byId( library.getClass() ) + .load( library.getId() ); + + assertNotNull( library ); + assertEquals( 2, library.getBooks().size() ); + } + ); + } + + @Test + void testWithSameIdentifiantButSubTypeDifferent(EntityManagerFactoryScope scope) { + + scope.inTransaction( + entityManager -> { + Session session = entityManager.unwrap( Session.class ); + Library library = new Library(); + library.setId( 1L ); + library.setBooks( Set.of( new Book(), new Book() ) ); + + Shop shop = new Shop(); + shop.setId( 1L ); + shop.setBooks( Set.of( new Book(), new Book(), new Book() ) ); + + session.save( library ); + session.save( shop ); + session.flush(); + session.clear(); + + library = session.byId( library.getClass() ).load( library.getId() ); + assertNotNull( library ); + assertEquals( 2, library.getBooks().size() ); + + shop = session.byId( shop.getClass() ).load( shop.getId() ); + assertNotNull( shop ); + assertEquals( 3, shop.getBooks().size() ); + } + ); + } + + @Entity(name = "book") + @Table(name = "TBOOK") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + @Any + @AnyKeyJavaClass(Long.class) + @AnyDiscriminatorValue(entity = Shop.class, discriminator = "S") + @AnyDiscriminatorValue(entity = Library.class, discriminator = "L") + @Column(name = "STORE_ROLE") + @JoinColumn(name = "STORE_ID") + private Store store; + + public void setId(final Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public Store getStore() { + return store; + } + + public void setStore(final Store store) { + this.store = store; + } + } + + @Entity(name = "library") + @Table(name = "TLIBRARY") + public static class Library implements Store { + + @Id + @GeneratedValue + private Long id; + + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private Set books; + + public void setId(final Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public Set getBooks() { + return books; + } + + public void setBooks(final Set books) { + books.forEach( book -> book.setStore( this ) ); + this.books = books; + } + } + + @Entity(name = "shop") + @Table(name = "TSHOP") + public static class Shop implements Store { + @Id + @GeneratedValue + private Long id; + + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private Set books; + + public void setId(final Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public Set getBooks() { + return books; + } + + public void setBooks(final Set books) { + books.forEach( book -> book.setStore( this ) ); + this.books = books; + } + } + + public interface Store { + } +}