From 0b87bda0f4cfbd31d544be98903d6c908b813ce0 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Mon, 27 Jan 2025 15:13:45 +0100 Subject: [PATCH 1/3] HHH-19076 Reproducer testcase --- .../CompositeInheritanceFailTest.java | 129 ++++++++++++++++++ .../CompositeInheritanceWorkingTest.java | 129 ++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/composite/CompositeInheritanceFailTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/composite/CompositeInheritanceWorkingTest.java 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; + } + + } + +} From 89ffdba465a376ba59d16b2a63ae9a76648e45a9 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Wed, 4 Jun 2025 12:44:13 +0200 Subject: [PATCH 2/3] HHH-19076 Fix SessionFactory error with a specific @IdClass setup This commit fixes HHH-19076. This bug is not trivial to reproduce since it requires a very specific entity setup: - There must be at least 2 entities - Both entities must extend the same @MappedSuperclass - The @MappedSuperclass must have an @Id - One (and only one) of the entities must additionally use @IdClass - The entities must be named / loaded / etc. so that the entity WITHOUT the @IdClass is processed first Specifically the last step is impossible to reproduce reliably, since it depends on the [order hibernate processes the entities][1], which in turn depends on the order of the values of [a map `entityBindingMap`][2]. Depending on the order the entites are processed, the [`MetadataContext.mappedSuperClassTypeToPersistentClass`][3] map stores for the shared @MappedSuperclass a different entity. However, to correctly process the attributes of the @MappedSuperclass, the entity with the @IdClass must be used in [`resolveVirtualIdentifierMember`][4]. To fix this, this commit changes the simple [`mappedSuperClassTypeToPersistentClass`][3] map to store a set of entities instead of only the first encountered entity. Next, the [`resolveVirtualIdentifierMember`][4] function uses the first matching entity of this set. [1]: https://github.com/hibernate/hibernate-orm/blob/3cfeb8fa29769258a0c0615b7e14b469798f0f3a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java#L627-L629 [2]: https://github.com/hibernate/hibernate-orm/blob/3cfeb8fa29769258a0c0615b7e14b469798f0f3a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java#L86 [3]: https://github.com/hibernate/hibernate-orm/blob/3cfeb8fa29769258a0c0615b7e14b469798f0f3a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java#L96 [4]: https://github.com/hibernate/hibernate-orm/blob/3cfeb8fa29769258a0c0615b7e14b469798f0f3a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java#L687 --- .../metamodel/internal/AttributeFactory.java | 60 ++++++++++++------- .../metamodel/internal/MetadataContext.java | 16 +++-- .../domain/internal/JpaMetamodelImpl.java | 11 ++-- 3 files changed, 58 insertions(+), 29 deletions(-) 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..b9a385af0e7a 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.get(0); + } + + 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.get(0); + 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 d6c80739eeb1..0d96c3028aad 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 @@ -92,7 +92,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<>(); @@ -204,13 +204,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. @@ -826,7 +832,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( From ebd8f9c51a598bc107f1b11bfd97b68f4ebe6c58 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Mon, 30 Jun 2025 15:14:29 +0200 Subject: [PATCH 3/3] HHH-19076 Add second composite key entity to the failing test --- .../CompositeInheritanceFailTest.java | 70 +++++++++++++++++++ .../CompositeInheritanceWorkingTest.java | 70 +++++++++++++++++++ 2 files changed, 140 insertions(+) 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 index 06dbdf3cfd2a..ede98a79273b 100644 --- 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 @@ -8,6 +8,7 @@ import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.Version; import org.hibernate.cfg.AvailableSettings; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.Jira; @@ -27,6 +28,7 @@ CompositeInheritanceFailTest.TupAbstractEntity.class, CompositeInheritanceFailTest.DummyEntity.class, CompositeInheritanceFailTest.TestEntity.class, // Here the class is called TestEntity + CompositeInheritanceFailTest.Test2Entity.class, } ) @ServiceRegistry( @@ -53,6 +55,18 @@ void hhh19076FailingTest(SessionFactoryScope scope) { } ); } + @Test + void hhh19076FailingTest2(SessionFactoryScope scope) { + scope.inTransaction( em -> { + Test2Entity e1 = new Test2Entity("foo", "xxxxxx"); + em.persist(e1); + + CompositeId2Class key = e1.getCompositeId(); + Test2Entity e2 = em.find(Test2Entity.class, key); + assertNotNull(e2); + } ); + } + @MappedSuperclass public static abstract class TupAbstractEntity { @Id @@ -103,6 +117,39 @@ public CompositeIdClass getCompositeId() { } + @Entity + @IdClass(CompositeId2Class.class) + public static class Test2Entity extends TupAbstractEntity { + + @Id + private String otherId; + + @Version + private long tanum = 0; + + protected Test2Entity() { + // for JPA + } + + public Test2Entity(String oid, String otherId) { + super(oid); + this.otherId = otherId; + } + + public String myId() { + return otherId; + } + + public long tanum() { + return tanum; + } + + public CompositeId2Class getCompositeId() { + return new CompositeId2Class(getOid(), otherId); + } + + } + public static class CompositeIdClass { private String oid; @@ -126,4 +173,27 @@ public String myId() { } + public static class CompositeId2Class { + + private String oid; + private String otherId; + + public CompositeId2Class(String oid, String otherId) { + this.oid = oid; + this.otherId = otherId; + } + + public CompositeId2Class() { + } + + public String oid() { + return oid; + } + + public String otherId() { + return otherId; + } + + } + } 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 index 4134dffa2e99..2a3c81603785 100644 --- 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 @@ -8,6 +8,7 @@ import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.Version; import org.hibernate.cfg.AvailableSettings; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.Jira; @@ -27,6 +28,7 @@ CompositeInheritanceWorkingTest.TupAbstractEntity.class, CompositeInheritanceWorkingTest.DummyEntity.class, CompositeInheritanceWorkingTest.FooEntity.class, // And here the class is called FooEntity and this works for some reason + CompositeInheritanceWorkingTest.Test2Entity.class, } ) @ServiceRegistry( @@ -53,11 +55,26 @@ void hhh19076WorkingTest(SessionFactoryScope scope) { } ); } + @Test + void hhh19076FailingTest2(SessionFactoryScope scope) { + scope.inTransaction( em -> { + Test2Entity e1 = new Test2Entity("foo", "xxxxxx"); + em.persist(e1); + + CompositeId2Class key = e1.getCompositeId(); + Test2Entity e2 = em.find( Test2Entity.class, key); + assertNotNull(e2); + } ); + } + @MappedSuperclass public static abstract class TupAbstractEntity { @Id private String oid = null; + @Version + private long tanum = 0; + @SuppressWarnings("this-escape") protected TupAbstractEntity() { @@ -71,6 +88,9 @@ public String getOid() { return oid; } + public long getTanum() { + return tanum; + } } @Entity @@ -103,6 +123,32 @@ public CompositeIdClass getCompositeId() { } + @Entity + @IdClass(CompositeId2Class.class) + public static class Test2Entity extends TupAbstractEntity { + + @Id + private String otherId; + + protected Test2Entity() { + // for JPA + } + + public Test2Entity(String oid, String otherId) { + super(oid); + this.otherId = otherId; + } + + public String myId() { + return otherId; + } + + public CompositeId2Class getCompositeId() { + return new CompositeId2Class(getOid(), otherId); + } + + } + public static class CompositeIdClass { private String oid; @@ -126,4 +172,28 @@ public String myId() { } + + public static class CompositeId2Class { + + private String oid; + private String otherId; + + public CompositeId2Class(String oid, String otherId) { + this.oid = oid; + this.otherId = otherId; + } + + public CompositeId2Class() { + } + + public String oid() { + return oid; + } + + public String otherId() { + return otherId; + } + + } + }