diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java index aed22e582b..befd68180f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ClassPropertyHolder.java @@ -319,7 +319,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { } } - private String getTypeName(Value value) { + public static String getTypeName(Value value) { if ( value instanceof Component ) { final Component component = (Component) value; final String typeName = component.getTypeName(); @@ -331,7 +331,7 @@ public class ClassPropertyHolder extends AbstractPropertyHolder { return ( (SimpleValue) value ).getTypeName(); } - private void setTypeName(Value value, String typeName) { + public static void setTypeName(Value value, String typeName) { if ( value instanceof ToOne ) { final ToOne toOne = (ToOne) value; toOne.setReferencedEntityName( typeName ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java index cee5813c89..b276b87760 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java @@ -13,6 +13,7 @@ import jakarta.persistence.Version; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.PropertyNotFoundException; import org.hibernate.annotations.AttributeBinderType; import org.hibernate.annotations.Generated; import org.hibernate.annotations.Immutable; @@ -25,13 +26,17 @@ import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.AccessType; import org.hibernate.cfg.AnnotationBinder; import org.hibernate.cfg.AnnotatedColumn; +import org.hibernate.cfg.ClassPropertyHolder; import org.hibernate.cfg.InheritanceState; +import org.hibernate.cfg.PropertyData; import org.hibernate.cfg.PropertyHolder; +import org.hibernate.cfg.PropertyInferredData; import org.hibernate.cfg.PropertyPreloadedData; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Component; import org.hibernate.mapping.KeyValue; +import org.hibernate.mapping.MappedSuperclass; import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SimpleValue; @@ -47,6 +52,7 @@ import org.hibernate.tuple.ValueGenerator; import org.jboss.logging.Logger; import java.lang.annotation.Annotation; +import java.util.Iterator; import java.util.Map; import static org.hibernate.cfg.BinderHelper.getMappedSuperclassOrNull; @@ -264,18 +270,12 @@ public class PropertyBinder { } else { rootClass.setIdentifierProperty( prop ); - final org.hibernate.mapping.MappedSuperclass superclass = getMappedSuperclassOrNull( + final MappedSuperclass superclass = getMappedSuperclassOrNull( declaringClass, inheritanceStatePerClass, buildingContext ); - if (superclass != null) { - superclass.setDeclaredIdentifierProperty(prop); - } - else { - //we know the property is on the actual entity - rootClass.setDeclaredIdentifierProperty( prop ); - } + setDeclaredIdentifier( rootClass, superclass, prop ); } } } @@ -288,6 +288,65 @@ public class PropertyBinder { return prop; } + private void setDeclaredIdentifier(RootClass rootClass, MappedSuperclass superclass, Property prop) { + if ( superclass == null ) { + rootClass.setDeclaredIdentifierProperty( prop ); + return; + } + // If the type has type parameters, we have to set the declared identifier property on the rootClass + // to be able to retrieve it with the correct type based on type variable assignment in the subclass + final Class type = buildingContext.getBootstrapContext().getReflectionManager().toClass( declaringClass ); + if ( type.getTypeParameters().length == 0 ) { + superclass.setDeclaredIdentifierProperty( prop ); + } + else { + // If the type has type parameters, we have to look up the XClass and actual property again + // because the given XClass has a TypeEnvironment based on the type variable assignments of a subclass + // and that might result in a wrong property type being used for a property which uses a type variable + final XClass actualDeclaringClass = buildingContext.getBootstrapContext().getReflectionManager().toXClass( type ); + for ( XProperty declaredProperty : actualDeclaringClass.getDeclaredProperties( prop.getPropertyAccessorName() ) ) { + if ( prop.getName().equals( declaredProperty.getName() ) ) { + final PropertyData inferredData = new PropertyInferredData( + actualDeclaringClass, + declaredProperty, + null, + buildingContext.getBootstrapContext().getReflectionManager() + ); + final Value originalValue = prop.getValue(); + if ( originalValue instanceof SimpleValue ) { + // Avoid copying when the property doesn't depend on a type variable + if ( inferredData.getTypeName().equals( ClassPropertyHolder.getTypeName( originalValue ) ) ) { + superclass.setDeclaredIdentifierProperty( prop ); + return; + } + } + // If the property depends on a type variable, we have to copy it and the Value + final Property actualProperty = prop.copy(); + actualProperty.setReturnedClassName( inferredData.getTypeName() ); + final Value value = actualProperty.getValue().copy(); + assert !(value instanceof Collection); + ClassPropertyHolder.setTypeName( value, inferredData.getTypeName() ); + if ( value instanceof Component ) { + Component component = ( (Component) value ); + Iterator propertyIterator = component.getPropertyIterator(); + while ( propertyIterator.hasNext() ) { + Property property = propertyIterator.next(); + try { + property.getGetter( component.getComponentClass() ); + } + catch (PropertyNotFoundException e) { + propertyIterator.remove(); + } + } + } + actualProperty.setValue( value ); + superclass.setDeclaredIdentifierProperty( actualProperty ); + break; + } + } + } + } + private Class resolveCustomInstantiator(XProperty property, XClass embeddableClass) { final org.hibernate.annotations.EmbeddableInstantiator propertyAnnotation = property.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class ); 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 875274a945..81ae868178 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 @@ -596,9 +596,22 @@ public class AttributeFactory { final CompositeTypeImplementor ownerComponentType = (CompositeTypeImplementor) ownerBootDescriptor.getType(); final EmbeddableValuedModelPart ownerMappingModelDescriptor = ownerComponentType.getMappingModelPart(); - final EmbeddableRepresentationStrategy ownerRepStrategy = ownerMappingModelDescriptor - .getEmbeddableTypeDescriptor() - .getRepresentationStrategy(); + final EmbeddableRepresentationStrategy ownerRepStrategy; + + if ( ownerMappingModelDescriptor == null ) { + // When an entity uses a type variable, bound by a mapped superclass, for an embedded id, + // we will not create a model part for the component, but we still need the representation strategy here, + // in order to discover the property members to expose on the JPA metamodel + ownerRepStrategy = ownerBootDescriptor.getBuildingContext() + .getBootstrapContext() + .getRepresentationStrategySelector() + .resolveStrategy( ownerBootDescriptor, null, metadataContext.getRuntimeModelCreationContext() ); + } + else { + ownerRepStrategy = ownerMappingModelDescriptor + .getEmbeddableTypeDescriptor() + .getRepresentationStrategy(); + } if ( ownerRepStrategy.getMode() == RepresentationMode.MAP ) { return new MapMember( 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 845bfd31e7..66bf385d3f 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,6 +24,7 @@ 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; @@ -428,6 +429,12 @@ public class MetadataContext { //noinspection unchecked rawtypes ( ( AttributeContainer) identifiableType ).getInFlightAccess().applyIdAttribute( idAttribute ); } + else if ( persistentClass.getIdentifier() instanceof Component + && persistentClass.getIdentifierProperty() != getSuperclassIdentifier( persistentClass ) ) { + // If the identifier is a generic component, we have to call buildIdAttribute anyway, + // as this will create and register the EmbeddableType for the subtype + attributeFactory.buildIdAttribute( identifiableType, persistentClass.getIdentifierProperty() ); + } } else { // we have a non-aggregated composite-id @@ -476,6 +483,34 @@ public class MetadataContext { } } + private Property getSuperclassIdentifier(PersistentClass persistentClass) { + final Property declaredIdentifierProperty = persistentClass.getDeclaredIdentifierProperty(); + if ( declaredIdentifierProperty != null ) { + return declaredIdentifierProperty; + } + if ( persistentClass.getSuperMappedSuperclass() != null ) { + return getSuperclassIdentifier( persistentClass.getSuperMappedSuperclass() ); + } + else if ( persistentClass.getSuperclass() != null ) { + return getSuperclassIdentifier( persistentClass.getSuperclass() ); + } + return null; + } + + private Property getSuperclassIdentifier(MappedSuperclass persistentClass) { + final Property declaredIdentifierProperty = persistentClass.getDeclaredIdentifierProperty(); + if ( declaredIdentifierProperty != null ) { + return declaredIdentifierProperty; + } + if ( persistentClass.getSuperMappedSuperclass() != null ) { + return getSuperclassIdentifier( persistentClass.getSuperMappedSuperclass() ); + } + else if ( persistentClass.getSuperPersistentClass() != null ) { + return getSuperclassIdentifier( persistentClass.getSuperPersistentClass() ); + } + return null; + } + private EmbeddableTypeImpl applyIdClassMetadata(Component idClassComponent) { final JavaTypeRegistry registry = getTypeConfiguration() .getJavaTypeRegistry();