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 164f3e54fc68..6fd9c7817920 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 @@ -9,6 +9,7 @@ import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.util.HashMap; +import java.util.Set; import org.hibernate.AssertionFailure; import org.hibernate.PropertyNotFoundException; @@ -395,20 +396,29 @@ private static JavaType determineRelationalJavaType( private static EntityPersister getDeclaringEntity( AbstractIdentifiableType ownerType, MetadataContext metadataContext) { + final java.util.List resultList = getDeclarerEntityPersister( ownerType, metadataContext ); + return resultList.isEmpty() ? null : resultList.getFirst(); + } + + private static java.util.List getAllDeclaringEntities(AbstractIdentifiableType ownerType, + MetadataContext metadataContext) { return getDeclarerEntityPersister( ownerType, metadataContext ); } - private static EntityPersister getDeclarerEntityPersister( + private static java.util.List getDeclarerEntityPersister( AbstractIdentifiableType ownerType, MetadataContext metadataContext) { final Type.PersistenceType persistenceType = ownerType.getPersistenceType(); if ( persistenceType == Type.PersistenceType.ENTITY ) { - return metadataContext.getMetamodel().getEntityDescriptor( ownerType.getTypeName() ); + return java.util.List.of(metadataContext.getMetamodel().getEntityDescriptor( ownerType.getTypeName() )); } else if ( persistenceType == Type.PersistenceType.MAPPED_SUPERCLASS ) { - final PersistentClass persistentClass = + final Set persistentClassSet = metadataContext.getPersistentClassHostingProperties( (MappedSuperclassTypeImpl) ownerType ); - return persistentClass != null ? metadataContext.getMetamodel().findEntityDescriptor( persistentClass.getClassName() ) : null; + if (persistentClassSet == null) { + return java.util.List.of(); + } + return persistentClassSet.stream().map( persistentClass -> metadataContext.getMetamodel().findEntityDescriptor( persistentClass.getClassName() ) ).toList(); } else { throw new AssertionFailure( "Cannot get the metamodel for PersistenceType: " + persistenceType ); @@ -680,18 +690,27 @@ private static EmbeddableRepresentationStrategy ownerRepresentationStrategy( private static final MemberResolver virtualIdentifierMemberResolver = (attributeContext, metadataContext) -> { final AbstractIdentifiableType identifiableType = (AbstractIdentifiableType) attributeContext.getOwnerType(); - final EntityPersister declaringEntity = getDeclaringEntity( identifiableType, metadataContext ); - return resolveVirtualIdentifierMember( attributeContext.getPropertyMapping(), declaringEntity ); + final java.util.List declaringEntities = getAllDeclaringEntities( identifiableType, metadataContext ); + return resolveVirtualIdentifierMember( attributeContext.getPropertyMapping(), declaringEntities ); }; - private static Member resolveVirtualIdentifierMember( Property property, EntityPersister entityPersister) { - final EntityIdentifierMapping identifierMapping = entityPersister.getIdentifierMapping(); + private static Member resolveVirtualIdentifierMember( Property property, java.util.List entityPersisters) { + CompositeIdentifierMapping cid = null; + + // HHH-19076: Find the first EntityPersister for the property with a VIRTUAL identifierMapping + for (EntityPersister entityPersister : entityPersisters) { + EntityIdentifierMapping identifierMapping = entityPersister.getIdentifierMapping(); + + if ( identifierMapping.getNature() == EntityIdentifierMapping.Nature.VIRTUAL ) { + cid = (CompositeIdentifierMapping) identifierMapping; + break; + } + } - if ( identifierMapping.getNature() != EntityIdentifierMapping.Nature.VIRTUAL ) { + if ( cid == null ) { throw new IllegalArgumentException( "expecting IdClass mapping" ); } - final CompositeIdentifierMapping cid = (CompositeIdentifierMapping) identifierMapping; final EmbeddableMappingType embeddable = cid.getPartMappingType(); final String attributeName = property.getName(); final AttributeMapping attributeMapping = embeddable.findAttributeMapping( attributeName ); @@ -718,7 +737,7 @@ private static Member resolveVirtualIdentifierMember( Property property, EntityP return switch ( persistenceType ) { case ENTITY -> resolveEntityMember( property, - getDeclaringEntity( (AbstractIdentifiableType) ownerType, metadataContext ) ); + getAllDeclaringEntities( (AbstractIdentifiableType) ownerType, metadataContext ) ); case MAPPED_SUPERCLASS -> resolveMappedSuperclassMember( property, (MappedSuperclassDomainType) ownerType, metadataContext ); case EMBEDDABLE -> @@ -727,23 +746,24 @@ private static Member resolveVirtualIdentifierMember( Property property, EntityP }; }; - private static Member resolveEntityMember(Property property, EntityPersister declaringEntity) { + private static Member resolveEntityMember(Property property, java.util.List declaringEntities) { final String propertyName = property.getName(); - final AttributeMapping attributeMapping = declaringEntity.findAttributeMapping( propertyName ); + final EntityPersister firstDeclaringEntity = declaringEntities.getFirst(); + final AttributeMapping attributeMapping = firstDeclaringEntity.findAttributeMapping( propertyName ); return attributeMapping == null // just like in #determineIdentifierJavaMember , this *should* indicate we have an IdClass mapping - ? resolveVirtualIdentifierMember( property, declaringEntity ) - : getter( declaringEntity, property, propertyName, property.getType().getReturnedClass() ); + ? resolveVirtualIdentifierMember( property, declaringEntities ) + : getter( firstDeclaringEntity, property, propertyName, property.getType().getReturnedClass() ); } private static Member resolveMappedSuperclassMember( Property property, MappedSuperclassDomainType ownerType, MetadataContext context) { - final EntityPersister declaringEntity = - getDeclaringEntity( (AbstractIdentifiableType) ownerType, context ); - if ( declaringEntity != null ) { - return resolveEntityMember( property, declaringEntity ); + final java.util.List declaringEntities = + getAllDeclaringEntities( (AbstractIdentifiableType) ownerType, context ); + if ( !declaringEntities.isEmpty() ) { + return resolveEntityMember( property, declaringEntities ); } else { final ManagedDomainType subType = ownerType.getSubTypes().iterator().next(); @@ -751,7 +771,7 @@ private static Member resolveMappedSuperclassMember( return switch ( persistenceType ) { case ENTITY -> resolveEntityMember( property, - getDeclaringEntity( (AbstractIdentifiableType) subType, context ) ); + getAllDeclaringEntities( (AbstractIdentifiableType) subType, context ) ); case MAPPED_SUPERCLASS -> resolveMappedSuperclassMember( property, (MappedSuperclassDomainType) subType, context ); case EMBEDDABLE -> 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 1a46f17858aa..29fd0a2b4e04 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 @@ -93,7 +93,7 @@ public class MetadataContext { private final Map, Component> componentByEmbeddable = new HashMap<>(); private final Map> mappedSuperclassByMappedSuperclassMapping = new HashMap<>(); - private final Map, PersistentClass> mappedSuperClassTypeToPersistentClass = new HashMap<>(); + private final Map, Set> mappedSuperClassTypeToPersistentClass = new HashMap<>(); //this list contains MappedSuperclass and EntityTypes ordered by superclass first private final List orderedMappings = new ArrayList<>(); @@ -211,13 +211,19 @@ public void registerMappedSuperclassType( identifiableTypesByName.put( mappedSuperclassType.getTypeName(), mappedSuperclassType ); mappedSuperclassByMappedSuperclassMapping.put( mappedSuperclass, mappedSuperclassType ); orderedMappings.add( mappedSuperclass ); - if ( !stackOfPersistentClassesBeingProcessed.isEmpty() ) { - mappedSuperClassTypeToPersistentClass.put( mappedSuperclassType, getEntityWorkedOn() ); - } knownMappedSuperclasses.remove( mappedSuperclass ); } + public void registerMappedSuperclassForPersistenceClass(MappedSuperclassDomainType mappedSuperclassType) { + if ( stackOfPersistentClassesBeingProcessed.isEmpty() ) { + return; + } + + final Set persistentClassSet = mappedSuperClassTypeToPersistentClass.computeIfAbsent( mappedSuperclassType, x -> new HashSet<>() ); + persistentClassSet.add( getEntityWorkedOn() ); + } + /** * Given a Hibernate {@link PersistentClass}, locate the corresponding JPA {@link org.hibernate.type.EntityType} * implementation. May return null if the given {@link PersistentClass} has not yet been processed. @@ -855,7 +861,7 @@ private PersistentClass getEntityWorkedOn() { ); } - public PersistentClass getPersistentClassHostingProperties(MappedSuperclassTypeImpl mappedSuperclassType) { + public Set getPersistentClassHostingProperties(MappedSuperclassTypeImpl mappedSuperclassType) { return mappedSuperClassTypeToPersistentClass.get( mappedSuperclassType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index 01bf7fd6b2a3..b07508bb6421 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -780,11 +780,14 @@ private MappedSuperclassDomainType locateOrBuildMappedSuperclassType( MetadataContext context, TypeConfiguration typeConfiguration) { @SuppressWarnings("unchecked") - final MappedSuperclassDomainType mappedSuperclassType = + MappedSuperclassDomainType mappedSuperclassType = (MappedSuperclassDomainType) context.locateMappedSuperclassType( mappedSuperclass ); - return mappedSuperclassType == null - ? buildMappedSuperclassType( mappedSuperclass, context, typeConfiguration ) - : mappedSuperclassType; + if (mappedSuperclassType == null) { + mappedSuperclassType = buildMappedSuperclassType( mappedSuperclass, context, typeConfiguration ); + } + // HHH-19076: Ensure that each mapped superclass knows ALL its implementations + context.registerMappedSuperclassForPersistenceClass( mappedSuperclassType ); + return mappedSuperclassType; } private MappedSuperclassTypeImpl buildMappedSuperclassType( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/composite/CompositeInheritanceFailTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/composite/CompositeInheritanceFailTest.java new file mode 100644 index 000000000000..06dbdf3cfd2a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/composite/CompositeInheritanceFailTest.java @@ -0,0 +1,129 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.identifier.composite; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.MappedSuperclass; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * This test Fails + */ +@DomainModel( + annotatedClasses = { + CompositeInheritanceFailTest.TupAbstractEntity.class, + CompositeInheritanceFailTest.DummyEntity.class, + CompositeInheritanceFailTest.TestEntity.class, // Here the class is called TestEntity + } +) +@ServiceRegistry( + settings = { + // For your own convenience to see generated queries: + @Setting(name = AvailableSettings.SHOW_SQL, value = "true"), + @Setting(name = AvailableSettings.FORMAT_SQL, value = "true"), + // @Setting( name = AvailableSettings.GENERATE_STATISTICS, value = "true" ), + } +) +@SessionFactory +@Jira("HHH-19076") +public class CompositeInheritanceFailTest { + + @Test + void hhh19076FailingTest(SessionFactoryScope scope) { + scope.inTransaction( em -> { + TestEntity e1 = new TestEntity("foo", "bar"); + em.persist(e1); + + CompositeIdClass key = e1.getCompositeId(); + TestEntity e2 = em.find(TestEntity.class, key); + assertNotNull(e2); + } ); + } + + @MappedSuperclass + public static abstract class TupAbstractEntity { + @Id + private String oid = null; + + + @SuppressWarnings("this-escape") + protected TupAbstractEntity() { + } + + protected TupAbstractEntity(String oid) { + this.oid = oid; + } + + public String getOid() { + return oid; + } + + } + + @Entity + public static class DummyEntity extends TupAbstractEntity { + } + + @Entity + @IdClass(CompositeIdClass.class) + public static class TestEntity extends TupAbstractEntity { + + @Id + private String myId; + + protected TestEntity() { + // for JPA + } + + public TestEntity(String oid, String myId) { + super(oid); + this.myId = myId; + } + + public String myId() { + return myId; + } + + public CompositeIdClass getCompositeId() { + return new CompositeIdClass(getOid(), myId); + } + + } + + public static class CompositeIdClass { + + private String oid; + private String myId; + + public CompositeIdClass(String oid, String myId) { + this.oid = oid; + this.myId = myId; + } + + public CompositeIdClass() { + } + + public String oid() { + return oid; + } + + public String myId() { + return myId; + } + + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/composite/CompositeInheritanceWorkingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/composite/CompositeInheritanceWorkingTest.java new file mode 100644 index 000000000000..4134dffa2e99 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/composite/CompositeInheritanceWorkingTest.java @@ -0,0 +1,129 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.identifier.composite; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.MappedSuperclass; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * This test works for some reason... + */ +@DomainModel( + annotatedClasses = { + CompositeInheritanceWorkingTest.TupAbstractEntity.class, + CompositeInheritanceWorkingTest.DummyEntity.class, + CompositeInheritanceWorkingTest.FooEntity.class, // And here the class is called FooEntity and this works for some reason + } +) +@ServiceRegistry( + settings = { + // For your own convenience to see generated queries: + @Setting(name = AvailableSettings.SHOW_SQL, value = "true"), + @Setting(name = AvailableSettings.FORMAT_SQL, value = "true"), + // @Setting( name = AvailableSettings.GENERATE_STATISTICS, value = "true" ), + } +) +@SessionFactory +@Jira("HHH-19076") +public class CompositeInheritanceWorkingTest { + + @Test + void hhh19076WorkingTest(SessionFactoryScope scope) { + scope.inTransaction( em -> { + FooEntity e1 = new FooEntity("foo", "bar"); + em.persist(e1); + + CompositeIdClass key = e1.getCompositeId(); + FooEntity e2 = em.find( FooEntity.class, key); + assertNotNull(e2); + } ); + } + + @MappedSuperclass + public static abstract class TupAbstractEntity { + @Id + private String oid = null; + + + @SuppressWarnings("this-escape") + protected TupAbstractEntity() { + } + + protected TupAbstractEntity(String oid) { + this.oid = oid; + } + + public String getOid() { + return oid; + } + + } + + @Entity + public static class DummyEntity extends TupAbstractEntity { + } + + @Entity + @IdClass(CompositeIdClass.class) + public static class FooEntity extends TupAbstractEntity { + + @Id + private String myId; + + protected FooEntity() { + // for JPA + } + + public FooEntity(String oid, String myId) { + super(oid); + this.myId = myId; + } + + public String myId() { + return myId; + } + + public CompositeIdClass getCompositeId() { + return new CompositeIdClass(getOid(), myId); + } + + } + + public static class CompositeIdClass { + + private String oid; + private String myId; + + public CompositeIdClass(String oid, String myId) { + this.oid = oid; + this.myId = myId; + } + + public CompositeIdClass() { + } + + public String oid() { + return oid; + } + + public String myId() { + return myId; + } + + } + +}