Skip to content

Commit b72f71c

Browse files
committed
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
1 parent 1b2f7c8 commit b72f71c

File tree

3 files changed

+58
-29
lines changed

3 files changed

+58
-29
lines changed

hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.lang.reflect.Method;
1010
import java.lang.reflect.ParameterizedType;
1111
import java.util.HashMap;
12+
import java.util.Set;
1213

1314
import org.hibernate.AssertionFailure;
1415
import org.hibernate.PropertyNotFoundException;
@@ -395,20 +396,29 @@ private static JavaType<?> determineRelationalJavaType(
395396
private static EntityPersister getDeclaringEntity(
396397
AbstractIdentifiableType<?> ownerType,
397398
MetadataContext metadataContext) {
399+
final java.util.List<EntityPersister> resultList = getDeclarerEntityPersister( ownerType, metadataContext );
400+
return resultList.isEmpty() ? null : resultList.getFirst();
401+
}
402+
403+
private static java.util.List<EntityPersister> getAllDeclaringEntities(AbstractIdentifiableType<?> ownerType,
404+
MetadataContext metadataContext) {
398405
return getDeclarerEntityPersister( ownerType, metadataContext );
399406
}
400407

401-
private static EntityPersister getDeclarerEntityPersister(
408+
private static java.util.List<EntityPersister> getDeclarerEntityPersister(
402409
AbstractIdentifiableType<?> ownerType,
403410
MetadataContext metadataContext) {
404411
final Type.PersistenceType persistenceType = ownerType.getPersistenceType();
405412
if ( persistenceType == Type.PersistenceType.ENTITY ) {
406-
return metadataContext.getMetamodel().getEntityDescriptor( ownerType.getTypeName() );
413+
return java.util.List.of(metadataContext.getMetamodel().getEntityDescriptor( ownerType.getTypeName() ));
407414
}
408415
else if ( persistenceType == Type.PersistenceType.MAPPED_SUPERCLASS ) {
409-
final PersistentClass persistentClass =
416+
final Set<PersistentClass> persistentClassSet =
410417
metadataContext.getPersistentClassHostingProperties( (MappedSuperclassTypeImpl<?>) ownerType );
411-
return persistentClass != null ? metadataContext.getMetamodel().findEntityDescriptor( persistentClass.getClassName() ) : null;
418+
if (persistentClassSet == null) {
419+
return java.util.List.of();
420+
}
421+
return persistentClassSet.stream().map( persistentClass -> metadataContext.getMetamodel().findEntityDescriptor( persistentClass.getClassName() ) ).toList();
412422
}
413423
else {
414424
throw new AssertionFailure( "Cannot get the metamodel for PersistenceType: " + persistenceType );
@@ -680,18 +690,27 @@ private static EmbeddableRepresentationStrategy ownerRepresentationStrategy(
680690

681691
private static final MemberResolver virtualIdentifierMemberResolver = (attributeContext, metadataContext) -> {
682692
final AbstractIdentifiableType<?> identifiableType = (AbstractIdentifiableType<?>) attributeContext.getOwnerType();
683-
final EntityPersister declaringEntity = getDeclaringEntity( identifiableType, metadataContext );
684-
return resolveVirtualIdentifierMember( attributeContext.getPropertyMapping(), declaringEntity );
693+
final java.util.List<EntityPersister> declaringEntities = getAllDeclaringEntities( identifiableType, metadataContext );
694+
return resolveVirtualIdentifierMember( attributeContext.getPropertyMapping(), declaringEntities );
685695
};
686696

687-
private static Member resolveVirtualIdentifierMember( Property property, EntityPersister entityPersister) {
688-
final EntityIdentifierMapping identifierMapping = entityPersister.getIdentifierMapping();
697+
private static Member resolveVirtualIdentifierMember( Property property, java.util.List<EntityPersister> entityPersisters) {
698+
CompositeIdentifierMapping cid = null;
699+
700+
// HHH-19076: Find the first EntityPersister for the property with a VIRTUAL identifierMapping
701+
for (EntityPersister entityPersister : entityPersisters) {
702+
EntityIdentifierMapping identifierMapping = entityPersister.getIdentifierMapping();
703+
704+
if ( identifierMapping.getNature() == EntityIdentifierMapping.Nature.VIRTUAL ) {
705+
cid = (CompositeIdentifierMapping) identifierMapping;
706+
break;
707+
}
708+
}
689709

690-
if ( identifierMapping.getNature() != EntityIdentifierMapping.Nature.VIRTUAL ) {
710+
if ( cid == null ) {
691711
throw new IllegalArgumentException( "expecting IdClass mapping" );
692712
}
693713

694-
final CompositeIdentifierMapping cid = (CompositeIdentifierMapping) identifierMapping;
695714
final EmbeddableMappingType embeddable = cid.getPartMappingType();
696715
final String attributeName = property.getName();
697716
final AttributeMapping attributeMapping = embeddable.findAttributeMapping( attributeName );
@@ -718,7 +737,7 @@ private static Member resolveVirtualIdentifierMember( Property property, EntityP
718737
return switch ( persistenceType ) {
719738
case ENTITY ->
720739
resolveEntityMember( property,
721-
getDeclaringEntity( (AbstractIdentifiableType<?>) ownerType, metadataContext ) );
740+
getAllDeclaringEntities( (AbstractIdentifiableType<?>) ownerType, metadataContext ) );
722741
case MAPPED_SUPERCLASS ->
723742
resolveMappedSuperclassMember( property, (MappedSuperclassDomainType<?>) ownerType, metadataContext );
724743
case EMBEDDABLE ->
@@ -727,31 +746,32 @@ private static Member resolveVirtualIdentifierMember( Property property, EntityP
727746
};
728747
};
729748

730-
private static Member resolveEntityMember(Property property, EntityPersister declaringEntity) {
749+
private static Member resolveEntityMember(Property property, java.util.List<EntityPersister> declaringEntities) {
731750
final String propertyName = property.getName();
732-
final AttributeMapping attributeMapping = declaringEntity.findAttributeMapping( propertyName );
751+
final EntityPersister firstDeclaringEntity = declaringEntities.getFirst();
752+
final AttributeMapping attributeMapping = firstDeclaringEntity.findAttributeMapping( propertyName );
733753
return attributeMapping == null
734754
// just like in #determineIdentifierJavaMember , this *should* indicate we have an IdClass mapping
735-
? resolveVirtualIdentifierMember( property, declaringEntity )
736-
: getter( declaringEntity, property, propertyName, property.getType().getReturnedClass() );
755+
? resolveVirtualIdentifierMember( property, declaringEntities )
756+
: getter( firstDeclaringEntity, property, propertyName, property.getType().getReturnedClass() );
737757
}
738758

739759
private static Member resolveMappedSuperclassMember(
740760
Property property,
741761
MappedSuperclassDomainType<?> ownerType,
742762
MetadataContext context) {
743-
final EntityPersister declaringEntity =
744-
getDeclaringEntity( (AbstractIdentifiableType<?>) ownerType, context );
745-
if ( declaringEntity != null ) {
746-
return resolveEntityMember( property, declaringEntity );
763+
final java.util.List<EntityPersister> declaringEntities =
764+
getAllDeclaringEntities( (AbstractIdentifiableType<?>) ownerType, context );
765+
if ( !declaringEntities.isEmpty() ) {
766+
return resolveEntityMember( property, declaringEntities );
747767
}
748768
else {
749769
final ManagedDomainType<?> subType = ownerType.getSubTypes().iterator().next();
750770
final Type.PersistenceType persistenceType = subType.getPersistenceType();
751771
return switch ( persistenceType ) {
752772
case ENTITY ->
753773
resolveEntityMember( property,
754-
getDeclaringEntity( (AbstractIdentifiableType<?>) subType, context ) );
774+
getAllDeclaringEntities( (AbstractIdentifiableType<?>) subType, context ) );
755775
case MAPPED_SUPERCLASS ->
756776
resolveMappedSuperclassMember( property, (MappedSuperclassDomainType<?>) subType, context );
757777
case EMBEDDABLE ->

hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public class MetadataContext {
9393
private final Map<EmbeddableDomainType<?>, Component> componentByEmbeddable = new HashMap<>();
9494

9595
private final Map<MappedSuperclass, MappedSuperclassDomainType<?>> mappedSuperclassByMappedSuperclassMapping = new HashMap<>();
96-
private final Map<MappedSuperclassDomainType<?>, PersistentClass> mappedSuperClassTypeToPersistentClass = new HashMap<>();
96+
private final Map<MappedSuperclassDomainType<?>, Set<PersistentClass>> mappedSuperClassTypeToPersistentClass = new HashMap<>();
9797

9898
//this list contains MappedSuperclass and EntityTypes ordered by superclass first
9999
private final List<Object> orderedMappings = new ArrayList<>();
@@ -211,13 +211,19 @@ public void registerMappedSuperclassType(
211211
identifiableTypesByName.put( mappedSuperclassType.getTypeName(), mappedSuperclassType );
212212
mappedSuperclassByMappedSuperclassMapping.put( mappedSuperclass, mappedSuperclassType );
213213
orderedMappings.add( mappedSuperclass );
214-
if ( !stackOfPersistentClassesBeingProcessed.isEmpty() ) {
215-
mappedSuperClassTypeToPersistentClass.put( mappedSuperclassType, getEntityWorkedOn() );
216-
}
217214

218215
knownMappedSuperclasses.remove( mappedSuperclass );
219216
}
220217

218+
public void registerMappedSuperclassForPersistenceClass(MappedSuperclassDomainType<?> mappedSuperclassType) {
219+
if ( stackOfPersistentClassesBeingProcessed.isEmpty() ) {
220+
return;
221+
}
222+
223+
final Set<PersistentClass> persistentClassSet = mappedSuperClassTypeToPersistentClass.computeIfAbsent( mappedSuperclassType, x -> new HashSet<>() );
224+
persistentClassSet.add( getEntityWorkedOn() );
225+
}
226+
221227
/**
222228
* Given a Hibernate {@link PersistentClass}, locate the corresponding JPA {@link org.hibernate.type.EntityType}
223229
* implementation. May return null if the given {@link PersistentClass} has not yet been processed.
@@ -855,7 +861,7 @@ private PersistentClass getEntityWorkedOn() {
855861
);
856862
}
857863

858-
public PersistentClass getPersistentClassHostingProperties(MappedSuperclassTypeImpl<?> mappedSuperclassType) {
864+
public Set<PersistentClass> getPersistentClassHostingProperties(MappedSuperclassTypeImpl<?> mappedSuperclassType) {
859865
return mappedSuperClassTypeToPersistentClass.get( mappedSuperclassType );
860866
}
861867

hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -780,11 +780,14 @@ private <T> MappedSuperclassDomainType<T> locateOrBuildMappedSuperclassType(
780780
MetadataContext context,
781781
TypeConfiguration typeConfiguration) {
782782
@SuppressWarnings("unchecked")
783-
final MappedSuperclassDomainType<T> mappedSuperclassType =
783+
MappedSuperclassDomainType<T> mappedSuperclassType =
784784
(MappedSuperclassDomainType<T>) context.locateMappedSuperclassType( mappedSuperclass );
785-
return mappedSuperclassType == null
786-
? buildMappedSuperclassType( mappedSuperclass, context, typeConfiguration )
787-
: mappedSuperclassType;
785+
if (mappedSuperclassType == null) {
786+
mappedSuperclassType = buildMappedSuperclassType( mappedSuperclass, context, typeConfiguration );
787+
}
788+
// HHH-19076: Ensure that each mapped superclass knows ALL its implementations
789+
context.registerMappedSuperclassForPersistenceClass( mappedSuperclassType );
790+
return mappedSuperclassType;
788791
}
789792

790793
private <T> MappedSuperclassTypeImpl<T> buildMappedSuperclassType(

0 commit comments

Comments
 (0)