HHH-18103 Correct metamodel for embeddables with a mapped superclass

This commit is contained in:
Marco Belladelli 2024-05-23 11:46:59 +02:00
parent 00c7707de0
commit 062afdb6cd
9 changed files with 169 additions and 64 deletions

View File

@ -287,7 +287,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder {
}
if ( inheritanceState.isEmbeddableSuperclass() ) {
persistentClass.addMappedSuperclassProperty( property );
addPropertyToMappedSuperclass( property, declaringClass );
addPropertyToMappedSuperclass( property, declaringClass, getContext() );
}
else {
persistentClass.addProperty( property );
@ -298,10 +298,10 @@ public class ClassPropertyHolder extends AbstractPropertyHolder {
}
}
private void addPropertyToMappedSuperclass(Property prop, XClass declaringClass) {
final Class<?> type = getContext().getBootstrapContext().getReflectionManager().toClass( declaringClass );
final MappedSuperclass superclass = getContext().getMetadataCollector().getMappedSuperclass( type );
prepareActualProperty( prop, type, true, getContext(), superclass::addDeclaredProperty );
static void addPropertyToMappedSuperclass(Property prop, XClass declaringClass, MetadataBuildingContext context) {
final Class<?> type = context.getBootstrapContext().getReflectionManager().toClass( declaringClass );
final MappedSuperclass superclass = context.getMetadataCollector().getMappedSuperclass( type );
prepareActualProperty( prop, type, true, context, superclass::addDeclaredProperty );
}
static void prepareActualProperty(
@ -458,7 +458,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder {
}
if ( inheritanceState.isEmbeddableSuperclass() ) {
join.addMappedSuperclassProperty( property );
addPropertyToMappedSuperclass( property, declaringClass );
addPropertyToMappedSuperclass( property, declaringClass, getContext() );
}
else {
join.addProperty( property );

View File

@ -30,7 +30,10 @@ import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import static org.hibernate.boot.model.internal.ClassPropertyHolder.addPropertyToMappedSuperclass;
import static org.hibernate.boot.model.internal.ClassPropertyHolder.handleGenericComponentProperty;
import static org.hibernate.boot.model.internal.HCANNHelper.hasAnnotation;
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.StringHelper.qualifyConditionally;
import static org.hibernate.spi.NavigablePath.IDENTIFIER_MAPPER_PROPERTY;
@ -67,6 +70,7 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
private final Component component;
private final boolean isOrWithinEmbeddedId;
private final boolean isWithinElementCollection;
private final Map<XClass, InheritanceState> inheritanceStatePerClass;
private final String embeddedAttributeName;
private final Map<String,AttributeConversionInfo> attributeConversionInfoMap;
@ -76,7 +80,8 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
String path,
PropertyData inferredData,
PropertyHolder parent,
MetadataBuildingContext context) {
MetadataBuildingContext context,
Map<XClass, InheritanceState> inheritanceStatePerClass) {
super( path, parent, inferredData.getPropertyClass(), context );
final XProperty embeddedXProperty = inferredData.getProperty();
setCurrentProperty( embeddedXProperty );
@ -85,6 +90,7 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
|| hasAnnotation( embeddedXProperty, Id.class, EmbeddedId.class );
this.isWithinElementCollection = parent.isWithinElementCollection() ||
parent instanceof CollectionPropertyHolder;
this.inheritanceStatePerClass = inheritanceStatePerClass;
if ( embeddedXProperty != null ) {
this.embeddedAttributeName = embeddedXProperty.getName();
@ -314,6 +320,13 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
@Override
public void addProperty(Property prop, XClass declaringClass) {
handleGenericComponentProperty( prop, getContext() );
if ( declaringClass != null ) {
final InheritanceState inheritanceState = inheritanceStatePerClass.get( declaringClass );
if ( inheritanceState != null && inheritanceState.isEmbeddableSuperclass() ) {
addPropertyToMappedSuperclass( prop, declaringClass, getContext() );
}
}
component.addProperty( prop, declaringClass );
}

View File

@ -353,7 +353,8 @@ public class EmbeddableBinder {
subpath,
inferredData,
propertyHolder,
context
context,
inheritanceStatePerClass
);
// propertyHolder here is the owner of the component property.
@ -381,18 +382,23 @@ public class EmbeddableBinder {
context
);
bindDiscriminator(
component,
returnedClassOrElement,
propertyHolder,
subholder,
inferredData,
inheritanceStatePerClass,
context
);
final InheritanceState inheritanceState = inheritanceStatePerClass.get( returnedClassOrElement );
if ( inheritanceState != null ) {
inheritanceState.postProcess( component );
// Main entry point for binding embeddable inheritance
bindDiscriminator(
component,
returnedClassOrElement,
propertyHolder,
subholder,
inferredData,
inheritanceState,
context
);
}
final Map<String, String> subclassToSuperclass = component.isPolymorphic() ? new HashMap<>() : null;
final XClass annotatedClass = inferredData.getPropertyClass();
final Map<String, String> subclassToSuperclass = component.isPolymorphic() ? new HashMap<>() : null;
final List<PropertyData> classElements = collectClassElements(
propertyAccessor,
context,
@ -487,13 +493,11 @@ public class EmbeddableBinder {
PropertyHolder parentHolder,
PropertyHolder holder,
PropertyData propertyData,
Map<XClass, InheritanceState> inheritanceStatePerClass,
InheritanceState inheritanceState,
MetadataBuildingContext context) {
final InheritanceState inheritanceState = inheritanceStatePerClass.get( componentClass );
if ( inheritanceState == null ) {
return;
}
final AnnotatedDiscriminatorColumn discriminatorColumn = processEmbeddableDiscriminatorProperties(
componentClass,
propertyData,

View File

@ -17,7 +17,9 @@ import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Table;
import jakarta.persistence.Access;
import jakarta.persistence.EmbeddedId;
@ -163,6 +165,15 @@ public class InheritanceState {
return elementsToProcess;
}
public void postProcess(Component component) {
if ( classesToProcessForMappedSuperclass.isEmpty() ) {
// Component classes might be processed more than once,
// so only do this the first time we encounter them
getMappedSuperclassesTillNextEntityOrdered();
}
addMappedSuperClassInMetadata( component );
}
public XClass getClassWithIdClass(boolean evenIfSubclass) {
if ( !evenIfSubclass && hasParents() ) {
return null;
@ -300,7 +311,21 @@ public class InheritanceState {
while ( superclassState != null && superclassState.isEmbeddableSuperclass() );
}
private void addMappedSuperClassInMetadata(Component component) {
org.hibernate.mapping.MappedSuperclass mappedSuperclass = processMappedSuperclass( component.getTable() );
if ( mappedSuperclass != null ) {
component.setMappedSuperclass( mappedSuperclass );
}
}
private void addMappedSuperClassInMetadata(PersistentClass persistentClass) {
org.hibernate.mapping.MappedSuperclass mappedSuperclass = processMappedSuperclass( persistentClass.getImplicitTable() );
if ( mappedSuperclass != null ) {
persistentClass.setSuperMappedSuperclass( mappedSuperclass );
}
}
private org.hibernate.mapping.MappedSuperclass processMappedSuperclass(Table implicitTable) {
//add @MappedSuperclass in the metadata
// classes from 0 to n-1 are @MappedSuperclass and should be linked
final InheritanceState superEntityState = getInheritanceStateOfSuperEntity( clazz, inheritanceStatePerClass );
@ -317,14 +342,12 @@ public class InheritanceState {
//add MappedSuperclass if not already there
mappedSuperclass = buildingContext.getMetadataCollector().getMappedSuperclass( type );
if ( mappedSuperclass == null ) {
mappedSuperclass = new org.hibernate.mapping.MappedSuperclass( parentSuperclass, superEntity, persistentClass.getImplicitTable() );
mappedSuperclass = new org.hibernate.mapping.MappedSuperclass( parentSuperclass, superEntity, implicitTable );
mappedSuperclass.setMappedClass( type );
buildingContext.getMetadataCollector().addMappedSuperclass( type, mappedSuperclass );
}
}
if ( mappedSuperclass != null ) {
persistentClass.setSuperMappedSuperclass( mappedSuperclass );
}
return mappedSuperclass;
}
public static final class ElementsToProcess {

View File

@ -54,8 +54,9 @@ public final class PropertyHolderBuilder {
String path,
PropertyData inferredData,
PropertyHolder parent,
MetadataBuildingContext context) {
return new ComponentPropertyHolder( component, path, inferredData, parent, context );
MetadataBuildingContext context,
Map<XClass, InheritanceState> inheritanceStatePerClass) {
return new ComponentPropertyHolder( component, path, inferredData, parent, context, inheritanceStatePerClass );
}
/**

View File

@ -86,6 +86,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
private boolean isKey;
private Boolean isGeneric;
private String roleName;
private MappedSuperclass mappedSuperclass;
private Value discriminator;
private transient DiscriminatorType<?> discriminatorType;
private Map<Object, String> discriminatorValues;
@ -601,6 +602,14 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
this.roleName = roleName;
}
public MappedSuperclass getMappedSuperclass() {
return mappedSuperclass;
}
public void setMappedSuperclass(MappedSuperclass mappedSuperclass) {
this.mappedSuperclass = mappedSuperclass;
}
public Value getDiscriminator() {
return discriminator;
}

View File

@ -24,6 +24,7 @@ import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.List;
import org.hibernate.mapping.Map;
import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.OneToMany;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
@ -44,6 +45,7 @@ import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.MappedSuperclassDomainType;
import org.hibernate.metamodel.model.domain.PersistentAttribute;
import org.hibernate.metamodel.model.domain.SimpleDomainType;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
@ -263,10 +265,20 @@ public class AttributeFactory {
}
}
final MappedSuperclass mappedSuperclass = component.getMappedSuperclass();
final MappedSuperclassDomainType<? super Y> superType;
if ( mappedSuperclass != null ) {
//noinspection unchecked
superType = (MappedSuperclassDomainType<? super Y>) context.locateMappedSuperclassType( mappedSuperclass );
}
else {
superType = null;
}
final DomainType<?> discriminatorType = component.isPolymorphic() ? component.getDiscriminatorType() : null;
final EmbeddableTypeImpl<Y> embeddableType = new EmbeddableTypeImpl<>(
context.getJavaTypeRegistry().resolveManagedTypeDescriptor( embeddableClass ),
null,
superType,
discriminatorType,
false,
context.getJpaMetamodel()
@ -409,7 +421,7 @@ public class AttributeFactory {
else if ( persistenceType == Type.PersistenceType.MAPPED_SUPERCLASS ) {
final PersistentClass persistentClass =
metadataContext.getPersistentClassHostingProperties( (MappedSuperclassTypeImpl<?>) ownerType );
return metadataContext.getMetamodel().findEntityDescriptor( persistentClass.getClassName() );
return persistentClass != null ? metadataContext.getMetamodel().findEntityDescriptor( persistentClass.getClassName() ) : null;
}
else {
throw new AssertionFailure( "Cannot get the metamodel for PersistenceType: " + persistenceType );
@ -646,6 +658,13 @@ public class AttributeFactory {
private static final MemberResolver embeddedMemberResolver = (attributeContext, metadataContext) -> {
// the owner is an embeddable
final EmbeddableDomainType<?> ownerType = (EmbeddableDomainType<?>) attributeContext.getOwnerType();
return resolveEmbeddedMember( attributeContext.getPropertyMapping(), ownerType, metadataContext );
};
private static Member resolveEmbeddedMember(
Property property,
EmbeddableDomainType<?> ownerType,
MetadataContext metadataContext) {
final Component ownerBootDescriptor = metadataContext.getEmbeddableBootDescriptor( ownerType );
final CompositeTypeImplementor ownerComponentType = (CompositeTypeImplementor) ownerBootDescriptor.getType();
@ -655,16 +674,15 @@ public class AttributeFactory {
ownerRepresentationStrategy( metadataContext, ownerMappingModelDescriptor, ownerBootDescriptor );
if ( ownerRepStrategy.getMode() == RepresentationMode.MAP ) {
final Property propertyMapping = attributeContext.getPropertyMapping();
return new MapMember( propertyMapping.getName(), propertyMapping.getType().getReturnedClass() );
return new MapMember( property.getName(), property.getType().getReturnedClass() );
}
else {
return ownerRepStrategy
.resolvePropertyAccess( attributeContext.getPropertyMapping() )
.resolvePropertyAccess( property )
.getGetter()
.getMember();
}
};
}
private static EmbeddableRepresentationStrategy ownerRepresentationStrategy(
MetadataContext metadataContext, EmbeddableValuedModelPart ownerMappingModelDescriptor, Component ownerBootDescriptor) {
@ -688,7 +706,11 @@ public class AttributeFactory {
private static final MemberResolver virtualIdentifierMemberResolver = (attributeContext, metadataContext) -> {
final AbstractIdentifiableType<?> identifiableType = (AbstractIdentifiableType<?>) attributeContext.getOwnerType();
final EntityPersister entityPersister = getDeclarerEntityPersister( identifiableType, metadataContext );
final EntityPersister declaringEntity = getDeclaringEntity( identifiableType, metadataContext );
return resolveVirtualIdentifierMember( attributeContext.getPropertyMapping(), declaringEntity );
};
private static Member resolveVirtualIdentifierMember( Property property, EntityPersister entityPersister) {
final EntityIdentifierMapping identifierMapping = entityPersister.getIdentifierMapping();
if ( identifierMapping.getNature() != EntityIdentifierMapping.Nature.VIRTUAL ) {
@ -697,7 +719,7 @@ public class AttributeFactory {
final CompositeIdentifierMapping cid = (CompositeIdentifierMapping) identifierMapping;
final EmbeddableMappingType embeddable = cid.getPartMappingType();
final String attributeName = attributeContext.getPropertyMapping().getName();
final String attributeName = property.getName();
final AttributeMapping attributeMapping = embeddable.findAttributeMapping( attributeName );
if ( attributeMapping == null ) {
throw new PropertyNotFoundException(
@ -708,9 +730,9 @@ public class AttributeFactory {
final Getter getter = attributeMapping.getPropertyAccess().getGetter();
return getter instanceof PropertyAccessMapImpl.GetterImpl
? new MapMember( attributeName, attributeContext.getPropertyMapping().getType().getReturnedClass() )
? new MapMember( attributeName, property.getType().getReturnedClass() )
: getter.getMember();
};
}
/**
* A {@link Member} resolver for normal attributes.
@ -722,29 +744,66 @@ public class AttributeFactory {
if ( Type.PersistenceType.EMBEDDABLE == persistenceType ) {
return embeddedMemberResolver.resolveMember( attributeContext, metadataContext );
}
else if ( Type.PersistenceType.ENTITY == persistenceType
|| Type.PersistenceType.MAPPED_SUPERCLASS == persistenceType ) {
final AbstractIdentifiableType<?> identifiableType = (AbstractIdentifiableType<?>) ownerType;
final EntityPersister declaringEntityPersister = getDeclaringEntity( identifiableType, metadataContext );
final String propertyName = property.getName();
final AttributeMapping attributeMapping = declaringEntityPersister.findAttributeMapping( propertyName );
if ( attributeMapping == null ) {
// just like in #determineIdentifierJavaMember , this *should* indicate we have an IdClass mapping
return virtualIdentifierMemberResolver.resolveMember( attributeContext, metadataContext );
}
else {
final Getter getter = getter( declaringEntityPersister, property );
return getter instanceof PropertyAccessMapImpl.GetterImpl
? new MapMember( propertyName, property.getType().getReturnedClass() )
: getter.getMember();
}
else if ( Type.PersistenceType.MAPPED_SUPERCLASS == persistenceType ) {
return resolveMappedSuperclassMember(
property,
(MappedSuperclassDomainType<?>) ownerType,
metadataContext
);
}
else if ( Type.PersistenceType.ENTITY == persistenceType ) {
return resolveEntityMember( property, getDeclaringEntity( (AbstractIdentifiableType<?>) ownerType, metadataContext ) );
}
else {
throw new IllegalArgumentException( "Unexpected owner type : " + persistenceType );
}
};
private static Member resolveEntityMember(Property property, EntityPersister declaringEntity) {
final String propertyName = property.getName();
final AttributeMapping attributeMapping = declaringEntity.findAttributeMapping( propertyName );
if ( attributeMapping == null ) {
// just like in #determineIdentifierJavaMember , this *should* indicate we have an IdClass mapping
return resolveVirtualIdentifierMember( property, declaringEntity );
}
else {
final Getter getter = getter( declaringEntity, property );
return getter instanceof PropertyAccessMapImpl.GetterImpl
? new MapMember( propertyName, property.getType().getReturnedClass() )
: getter.getMember();
}
}
private static Member resolveMappedSuperclassMember(
Property property,
MappedSuperclassDomainType<?> ownerType,
MetadataContext metadataContext) {
final EntityPersister declaringEntity = getDeclaringEntity( (AbstractIdentifiableType<?>) ownerType, metadataContext );
if ( declaringEntity != null ) {
return resolveEntityMember( property, declaringEntity );
}
else {
final ManagedDomainType<?> subType = ownerType.getSubTypes().iterator().next();
final Type.PersistenceType persistenceType = subType.getPersistenceType();
if ( persistenceType == Type.PersistenceType.ENTITY ) {
return resolveEntityMember( property, getDeclaringEntity( (AbstractIdentifiableType<?>) subType, metadataContext ) );
}
else if ( persistenceType == Type.PersistenceType.EMBEDDABLE ) {
return resolveEmbeddedMember( property, (EmbeddableDomainType<?>) subType, metadataContext );
}
else if ( persistenceType == Type.PersistenceType.MAPPED_SUPERCLASS ) {
return resolveMappedSuperclassMember(
property,
(MappedSuperclassDomainType<?>) subType,
metadataContext
);
}
else {
throw new IllegalArgumentException( "Unexpected sub-type: " + persistenceType );
}
}
}
private final MemberResolver identifierMemberResolver = (attributeContext, metadataContext) -> {
final AbstractIdentifiableType<?> identifiableType =
(AbstractIdentifiableType<?>) attributeContext.getOwnerType();

View File

@ -211,7 +211,9 @@ public class MetadataContext {
identifiableTypesByName.put( mappedSuperclassType.getTypeName(), mappedSuperclassType );
mappedSuperclassByMappedSuperclassMapping.put( mappedSuperclass, mappedSuperclassType );
orderedMappings.add( mappedSuperclass );
mappedSuperClassTypeToPersistentClass.put( mappedSuperclassType, getEntityWorkedOn() );
if ( !stackOfPersistentClassesBeingProcessed.isEmpty() ) {
mappedSuperClassTypeToPersistentClass.put( mappedSuperclassType, getEntityWorkedOn() );
}
knownMappedSuperclasses.remove( mappedSuperclass );
}
@ -804,14 +806,7 @@ public class MetadataContext {
}
public PersistentClass getPersistentClassHostingProperties(MappedSuperclassTypeImpl<?> mappedSuperclassType) {
final PersistentClass persistentClass = mappedSuperClassTypeToPersistentClass.get( mappedSuperclassType );
if ( persistentClass == null ) {
throw new AssertionFailure(
"Could not find PersistentClass for MappedSuperclassType: "
+ mappedSuperclassType.getJavaType()
);
}
return persistentClass;
return mappedSuperClassTypeToPersistentClass.get( mappedSuperclassType );
}
public Set<MappedSuperclass> getUnusedMappedSuperclasses() {

View File

@ -32,7 +32,8 @@ public class AuditedEmbeddableWithNoDeclaredDataTest extends BaseEnversJPAFuncti
return new Class[] {
EntityWithAuditedEmbeddableWithNoDeclaredData.class,
AbstractAuditedEmbeddable.class,
AuditedEmbeddableWithDeclaredData.class
AuditedEmbeddableWithDeclaredData.class,
AuditedEmbeddableWithNoDeclaredData.class,
};
}