diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java index 59d0ab5088..1bbf946cd8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java @@ -288,6 +288,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { } // If the property depends on a type variable, we have to copy it and the Value final Property actualProperty = prop.copy(); + actualProperty.setGeneric( true ); actualProperty.setReturnedClassName( inferredData.getTypeName() ); final Value value = actualProperty.getValue().copy(); if ( value instanceof Collection ) { @@ -320,7 +321,6 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { setTypeName( value, inferredData.getTypeName() ); } if ( value instanceof Component ) { - actualProperty.setGenericEmbeddable( true ); final Component component = ( (Component) value ); final Iterator propertyIterator = component.getPropertyIterator(); while ( propertyIterator.hasNext() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index 72f1ce6eba..003f3ec48e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -57,7 +57,7 @@ public class Property implements Serializable, MetaAttributable { private java.util.Map metaAttributes; private PersistentClass persistentClass; private boolean naturalIdentifier; - private boolean genericEmbeddable; + private boolean isGeneric; private boolean lob; private java.util.List callbackDefinitions; private String returnedClassName; @@ -418,12 +418,12 @@ public class Property implements Serializable, MetaAttributable { this.naturalIdentifier = naturalIdentifier; } - public boolean isGenericEmbeddable() { - return genericEmbeddable; + public boolean isGeneric() { + return isGeneric; } - public void setGenericEmbeddable(boolean genericEmbeddable) { - this.genericEmbeddable = genericEmbeddable; + public void setGeneric(boolean generic) { + this.isGeneric = generic; } public boolean isLob() { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java index e7126d010a..4e6c909bdf 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java @@ -129,7 +129,7 @@ public class AttributeFactory { false, false, property.isOptional(), - property.isGenericEmbeddable(), + property.isGeneric(), metadataContext ); } @@ -177,7 +177,7 @@ public class AttributeFactory { (SimpleDomainType) determineSimpleType( attributeMetadata.getValueContext() ), attributeMetadata.getMember(), attributeMetadata.getAttributeClassification(), - property.isGenericEmbeddable(), + property.isGeneric(), context ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index d2ff45cd1c..20c2f74c63 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -24,7 +24,6 @@ import org.hibernate.internal.HEMLogging; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Component; -import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; @@ -274,6 +273,7 @@ public class MetadataContext { applyIdMetadata( safeMapping, jpaMapping ); applyVersionAttribute( safeMapping, jpaMapping ); + applyGenericEmbeddableProperties( safeMapping, jpaMapping ); for ( Property property : safeMapping.getDeclaredProperties() ) { if ( property.getValue() == safeMapping.getIdentifierMapper() ) { @@ -573,6 +573,43 @@ public class MetadataContext { } } + private void applyGenericEmbeddableProperties( + PersistentClass persistentClass, + EntityDomainType entityType) { + MappedSuperclass mappedSuperclass = getMappedSuperclass( persistentClass ); + while ( mappedSuperclass != null ) { + for ( Property superclassProperty : mappedSuperclass.getDeclaredProperties() ) { + if ( superclassProperty.isGeneric() && superclassProperty.isComposite() ) { + final Property property = persistentClass.getProperty( superclassProperty.getName() ); + final SingularPersistentAttribute attribute = (SingularPersistentAttribute) attributeFactory.buildAttribute( + entityType, + property + ); + //noinspection unchecked rawtypes + ( (AttributeContainer) entityType ).getInFlightAccess().addConcreteEmbeddableAttribute( attribute ); + } + } + mappedSuperclass = getMappedSuperclass( mappedSuperclass ); + } + } + + private MappedSuperclass getMappedSuperclass(PersistentClass persistentClass) { + while ( persistentClass != null ) { + final MappedSuperclass mappedSuperclass = persistentClass.getSuperMappedSuperclass(); + if ( mappedSuperclass != null ) { + return mappedSuperclass; + } + persistentClass = persistentClass.getSuperclass(); + } + return null; + } + + private MappedSuperclass getMappedSuperclass(MappedSuperclass mappedSuperclass) { + return mappedSuperclass.getSuperMappedSuperclass() != null + ? mappedSuperclass.getSuperMappedSuperclass() + : getMappedSuperclass( mappedSuperclass.getSuperPersistentClass() ); + } + private Set> buildIdClassAttributes( IdentifiableDomainType ownerType, List properties) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/MultipleEmbeddedGenericsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/MultipleEmbeddedGenericsTest.java new file mode 100644 index 0000000000..7c25d20a51 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/MultipleEmbeddedGenericsTest.java @@ -0,0 +1,266 @@ +/* + * 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.annotations.generics; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@DomainModel(annotatedClasses = { + MultipleEmbeddedGenericsTest.Customer.class, + MultipleEmbeddedGenericsTest.Invoice.class +}) +@SessionFactory +@Jira("https://hibernate.atlassian.net/browse/HHH-TODO") // todo marco : create specific issue for this +public class MultipleEmbeddedGenericsTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Customer customer = new Customer( "Marco" ); + session.persist( customer ); + final Invoice invoice = new Invoice( 123 ); + session.persist( invoice ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from Customer" ).executeUpdate(); + session.createMutationQuery( "delete from Invoice" ).executeUpdate(); + } ); + } + + @Test + public void testCustomer(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Customer customer = session.createQuery( + "from Customer c where c.firstEmbedded.genericPropertyA = '1' and c.secondEmbedded.customerPropertyB = 2", + Customer.class + ).getSingleResult(); + assertThat( customer.getName() ).isEqualTo( "Marco" ); + } ); + } + + @Test + public void testCustomerCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( Customer.class ); + final Root root = query.from( Customer.class ); + final Path firstEmbedded = root.get( "firstEmbedded" ); + assertThat( firstEmbedded.getJavaType() ).isEqualTo( GenericEmbeddableOne.class ); + assertThat( firstEmbedded.getModel() ).isSameAs( root.getModel().getAttribute( "firstEmbedded" ) ); + final Path secondEmbedded = root.get( "secondEmbedded" ); + assertThat( secondEmbedded.getJavaType() ).isEqualTo( GenericEmbeddableTwo.class ); + assertThat( secondEmbedded.getModel() ).isSameAs( root.getModel().getAttribute( "secondEmbedded" ) ); + query.select( root ).where( cb.and( + cb.equal( firstEmbedded.get( "genericPropertyA" ), "1" ), + cb.equal( secondEmbedded.get( "customerPropertyB" ), 2 ) + ) ); + final Customer customer = session.createQuery( query ).getSingleResult(); + assertThat( customer.getName() ).isEqualTo( "Marco" ); + } ); + } + + @Test + public void testInvoice(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Invoice invoice = session.createQuery( + "from Invoice i where i.firstEmbedded.invoicePropertyA = 1 and i.secondEmbedded.genericPropertyB = '2'", + Invoice.class + ).getSingleResult(); + assertThat( invoice.getSerial() ).isEqualTo( 123 ); + } ); + } + + @Test + public void testInvoiceCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( Invoice.class ); + final Root root = query.from( Invoice.class ); + final Path firstEmbedded = root.get( "firstEmbedded" ); + assertThat( firstEmbedded.getJavaType() ).isEqualTo( GenericEmbeddableOne.class ); + assertThat( firstEmbedded.getModel() ).isSameAs( root.getModel().getAttribute( "firstEmbedded" ) ); + final Path secondEmbedded = root.get( "secondEmbedded" ); + assertThat( secondEmbedded.getJavaType() ).isEqualTo( GenericEmbeddableTwo.class ); + assertThat( secondEmbedded.getModel() ).isSameAs( root.getModel().getAttribute( "secondEmbedded" ) ); + query.select( root ).where( cb.and( + cb.equal( firstEmbedded.get( "invoicePropertyA" ), 1 ), + cb.equal( secondEmbedded.get( "genericPropertyB" ), "2" ) + ) ); + final Invoice invoice = session.createQuery( query ).getSingleResult(); + assertThat( invoice.getSerial() ).isEqualTo( 123 ); + } ); + } + + @Embeddable + @MappedSuperclass + public abstract static class GenericEmbeddableOne { + private String genericPropertyA; + + public GenericEmbeddableOne() { + } + + public GenericEmbeddableOne(String genericPropertyA) { + this.genericPropertyA = genericPropertyA; + } + } + + @Embeddable + @MappedSuperclass + public abstract static class GenericEmbeddableTwo { + private String genericPropertyB; + + public GenericEmbeddableTwo() { + } + + public GenericEmbeddableTwo(String genericPropertyB) { + this.genericPropertyB = genericPropertyB; + } + } + + @MappedSuperclass + public abstract static class GenericEntity { + @Embedded + private A firstEmbedded; + + @Embedded + private B secondEmbedded; + + public GenericEntity() { + } + + public GenericEntity(A firstEmbedded, B secondEmbedded) { + this.firstEmbedded = firstEmbedded; + this.secondEmbedded = secondEmbedded; + } + } + + @Embeddable + public static class CustomerEmbeddableOne extends GenericEmbeddableOne { + private int customerPropertyA; + + public CustomerEmbeddableOne() { + } + + public CustomerEmbeddableOne(String genericPropertyA, int customerPropertyA) { + super( genericPropertyA ); + this.customerPropertyA = customerPropertyA; + } + } + + @Embeddable + public static class CustomerEmbeddableTwo extends GenericEmbeddableTwo { + private int customerPropertyB; + + public CustomerEmbeddableTwo() { + } + + public CustomerEmbeddableTwo(String genericPropertyB, int customerPropertyB) { + super( genericPropertyB ); + this.customerPropertyB = customerPropertyB; + } + } + + @Entity(name = "Customer") + public static class Customer extends GenericEntity { + @Id + @GeneratedValue + private Long id; + + private String name; + + public Customer() { + } + + public Customer(String name) { + super( new CustomerEmbeddableOne( "1", 1 ), new CustomerEmbeddableTwo( "2", 2 ) ); + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Embeddable + public static class InvoiceEmbeddableOne extends GenericEmbeddableOne { + private int invoicePropertyA; + + public InvoiceEmbeddableOne() { + } + + public InvoiceEmbeddableOne(String genericPropertyA, int invoicePropertyA) { + super( genericPropertyA ); + this.invoicePropertyA = invoicePropertyA; + } + } + + @Embeddable + public static class InvoiceEmbeddableTwo extends GenericEmbeddableTwo { + private int invoicePropertyB; + + public InvoiceEmbeddableTwo() { + } + + public InvoiceEmbeddableTwo(String genericPropertyB, int invoicePropertyB) { + super( genericPropertyB ); + this.invoicePropertyB = invoicePropertyB; + } + } + + @Entity(name = "Invoice") + public static class Invoice extends GenericEntity { + @Id + @GeneratedValue + private Long id; + + private Integer serial; + + public Invoice() { + } + + public Invoice(Integer serial) { + super( new InvoiceEmbeddableOne( "1", 1 ), new InvoiceEmbeddableTwo( "2", 2 ) ); + this.serial = serial; + } + + public Integer getSerial() { + return serial; + } + + public void setSerial(Integer serial) { + this.serial = serial; + } + } +}