HHH-15722 @OneToMany mappedBy with a @Any

This commit is contained in:
Vincent Bouthinon 2024-04-03 18:17:35 +02:00 committed by Christian Beikov
parent 20d26a0126
commit 4308c5dac5
6 changed files with 255 additions and 5 deletions

View File

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

View File

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

View File

@ -109,6 +109,10 @@ public class Any extends SimpleValue {
return discriminatorDescriptor;
}
public BasicValue getKeyDescriptor() {
return keyDescriptor;
}
public MetaValue getMetaMapping() {
return metaMapping;
}

View File

@ -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++ ) {

View File

@ -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<Object> discriminatorBaseType = (BasicType<Object>) 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,

View File

@ -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<Book> 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<Book> books;
public void setId(final Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public Set<Book> getBooks() {
return books;
}
public void setBooks(final Set<Book> 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<Book> books;
public void setId(final Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public Set<Book> getBooks() {
return books;
}
public void setBooks(final Set<Book> books) {
books.forEach( book -> book.setStore( this ) );
this.books = books;
}
}
public interface Store {
}
}