diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index 431a9e3c46ed..b4be96f1b897 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -1928,7 +1928,7 @@ private void buildRecursiveOrderedFkSecondPasses( final Set dependencies = isADependencyOf.get( currentTable ); if ( dependencies != null ) { for ( FkSecondPass pass : dependencies ) { - String dependentTable = pass.getValue().getTable().getQualifiedTableName().render(); + final String dependentTable = pass.getValue().getTable().getQualifiedTableName().render(); if ( dependentTable.compareTo( startTable ) != 0 ) { buildRecursiveOrderedFkSecondPasses( orderedFkSecondPasses, isADependencyOf, startTable, dependentTable ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorBypassedImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorBypassedImpl.java index 976baa93eeac..7c2c015a77a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorBypassedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorBypassedImpl.java @@ -24,6 +24,11 @@ public class AutoApplicableConverterDescriptorBypassedImpl implements AutoApplic private AutoApplicableConverterDescriptorBypassedImpl() { } + @Override + public boolean isAutoApplicable() { + return false; + } + @Override public ConverterDescriptor getAutoAppliedConverterDescriptorForAttribute( MemberDetails memberDetails, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java index 3fcf5df3ff5a..99ceb10b4a3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AutoApplicableConverterDescriptorStandardImpl.java @@ -33,6 +33,11 @@ public AutoApplicableConverterDescriptorStandardImpl(ConverterDescriptor linkedC this.linkedConverterDescriptor = linkedConverterDescriptor; } + @Override + public boolean isAutoApplicable() { + return true; + } + @Override public ConverterDescriptor getAutoAppliedConverterDescriptorForAttribute( MemberDetails memberDetails, @@ -51,16 +56,16 @@ public ConverterDescriptor getAutoAppliedConverterDescriptorForCollectionElement final ResolvedMember collectionMember = resolveMember( memberDetails, context ); final ResolvedType elementType; - Class erasedType = collectionMember.getType().getErasedType(); + final Class erasedType = collectionMember.getType().getErasedType(); if ( Map.class.isAssignableFrom( erasedType ) ) { - List typeArguments = collectionMember.getType().typeParametersFor(Map.class); + final List typeArguments = collectionMember.getType().typeParametersFor(Map.class); if ( typeArguments.size() < 2 ) { return null; } elementType = typeArguments.get( 1 ); } else if ( Collection.class.isAssignableFrom( erasedType ) ) { - List typeArguments = collectionMember.getType().typeParametersFor(Collection.class); + final List typeArguments = collectionMember.getType().typeParametersFor(Collection.class); if ( typeArguments.isEmpty() ) { return null; } @@ -87,7 +92,7 @@ public ConverterDescriptor getAutoAppliedConverterDescriptorForMapKey( final ResolvedType keyType; if ( Map.class.isAssignableFrom( collectionMember.getType().getErasedType() ) ) { - List typeArguments = collectionMember.getType().typeParametersFor(Map.class); + final List typeArguments = collectionMember.getType().typeParametersFor(Map.class); if ( typeArguments.isEmpty() ) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/AutoApplicableConverterDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/AutoApplicableConverterDescriptor.java index bec878748685..07d899c0a648 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/AutoApplicableConverterDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/spi/AutoApplicableConverterDescriptor.java @@ -13,6 +13,7 @@ * @author Steve Ebersole */ public interface AutoApplicableConverterDescriptor { + boolean isAutoApplicable(); ConverterDescriptor getAutoAppliedConverterDescriptorForAttribute(MemberDetails memberDetails, MetadataBuildingContext context); ConverterDescriptor getAutoAppliedConverterDescriptorForCollectionElement(MemberDetails memberDetails, MetadataBuildingContext context); ConverterDescriptor getAutoAppliedConverterDescriptorForMapKey(MemberDetails memberDetails, MetadataBuildingContext context); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java index 6d2570095820..1fe123f1f6f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java @@ -87,7 +87,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder { @Override public ConverterDescriptor resolveAttributeConverterDescriptor(MemberDetails attributeMember) { - AttributeConversionInfo info = locateAttributeConversionInfo( attributeMember ); + final AttributeConversionInfo info = locateAttributeConversionInfo( attributeMember ); if ( info != null ) { if ( info.isConversionDisabled() ) { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java index 10bd5e15a2b0..af6ad4055e8e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java @@ -55,8 +55,8 @@ public static void processAggregate( component.setStructColumnNames( determineStructAttributeNames( inferredData, componentClassDetails ) ); // Determine the aggregate column - BasicValueBinder basicValueBinder = new BasicValueBinder( BasicValueBinder.Kind.ATTRIBUTE, component, context ); - basicValueBinder.setPropertyName( inferredData.getPropertyName() ); + final BasicValueBinder basicValueBinder = + new BasicValueBinder( BasicValueBinder.Kind.ATTRIBUTE, component, context ); basicValueBinder.setReturnedClassName( inferredData.getClassOrElementType().getName() ); basicValueBinder.setColumns( columns ); basicValueBinder.setPersistentClassName( propertyHolder.getClassName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java index ad3915975a06..953f5342c911 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java @@ -4,48 +4,24 @@ */ package org.hibernate.boot.model.internal; -import java.io.Serializable; import java.lang.annotation.Annotation; -import java.lang.reflect.ParameterizedType; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; +import jakarta.persistence.Embedded; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.MappingException; import org.hibernate.TimeZoneStorageStrategy; -import org.hibernate.annotations.AnyDiscriminator; -import org.hibernate.annotations.AnyKeyJavaClass; -import org.hibernate.annotations.AnyKeyJavaType; -import org.hibernate.annotations.AnyKeyJdbcType; -import org.hibernate.annotations.AnyKeyJdbcTypeCode; -import org.hibernate.annotations.CollectionId; -import org.hibernate.annotations.CollectionIdJavaType; -import org.hibernate.annotations.CollectionIdJdbcType; -import org.hibernate.annotations.CollectionIdJdbcTypeCode; -import org.hibernate.annotations.CollectionIdMutability; -import org.hibernate.annotations.CollectionIdType; -import org.hibernate.annotations.Immutable; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.annotations.ListIndexJavaType; -import org.hibernate.annotations.ListIndexJdbcType; -import org.hibernate.annotations.ListIndexJdbcTypeCode; -import org.hibernate.annotations.MapKeyJavaType; -import org.hibernate.annotations.MapKeyJdbcType; -import org.hibernate.annotations.MapKeyJdbcTypeCode; -import org.hibernate.annotations.MapKeyMutability; -import org.hibernate.annotations.MapKeyType; -import org.hibernate.annotations.Mutability; -import org.hibernate.annotations.Nationalized; -import org.hibernate.annotations.PartitionKey; -import org.hibernate.annotations.Target; -import org.hibernate.annotations.TimeZoneColumn; -import org.hibernate.annotations.TimeZoneStorage; -import org.hibernate.annotations.TimeZoneStorageType; -import org.hibernate.annotations.Type; +import org.hibernate.annotations.*; import org.hibernate.boot.internal.AnyKeyType; import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.spi.AccessType; @@ -57,6 +33,7 @@ import org.hibernate.internal.util.ReflectHelper; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Component; +import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.MemberDetails; @@ -92,6 +69,7 @@ import static java.util.Collections.emptyMap; import static org.hibernate.boot.model.internal.AnnotationHelper.extractParameterMap; +import static org.hibernate.boot.model.internal.TableBinder.linkJoinColumnWithValueOverridingNameIfImplicit; import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER; /** @@ -157,9 +135,8 @@ public enum Kind { private BasicValue basicValue; private String persistentClassName; - private String propertyName; private String returnedClassName; - private String referencedEntityName; + private String referencedEntityName; // only used for @MapsId or @IdClass public BasicValueBinder(Kind kind, MetadataBuildingContext buildingContext) { this( kind, null, buildingContext ); @@ -293,14 +270,10 @@ public void setVersion(boolean isVersion) { } } - public void setReferencedEntityName(String referencedEntityName) { + void setReferencedEntityName(String referencedEntityName) { this.referencedEntityName = referencedEntityName; } - public void setPropertyName(String propertyName) { - this.propertyName = propertyName; - } - public void setReturnedClassName(String returnedClassName) { this.returnedClassName = returnedClassName; } @@ -553,6 +526,7 @@ private void prepareMapKey( enumType = mapKeyEnumeratedAnn.value(); } + //noinspection deprecation final MapKeyTemporal mapKeyTemporalAnn = attribute.getDirectAnnotationUsage( MapKeyTemporal.class ); if ( mapKeyTemporalAnn != null ) { @@ -731,12 +705,14 @@ private void prepareCollectionElement( : explicitElementTypeDetails; final ClassDetails rawElementType = elementTypeDetails.determineRawClass(); final java.lang.reflect.Type javaType = rawElementType.toJavaClass(); - final Class javaTypeClass = ReflectHelper.getClass( javaType ); + final Class javaTypeClass = ReflectHelper.getClass( javaType ); implicitJavaTypeAccess = typeConfiguration -> javaType; + //noinspection deprecation final Temporal temporalAnn = attribute.getDirectAnnotationUsage( Temporal.class ); if ( temporalAnn != null ) { + //noinspection deprecation DEPRECATION_LOGGER.deprecatedAnnotation( Temporal.class, attribute.getName() ); temporalPrecision = temporalAnn.value(); if ( temporalPrecision == null ) { @@ -795,7 +771,7 @@ private void prepareBasicAttribute( String declaringClassName, MemberDetails attribute, TypeDetails attributeType) { - final Class javaTypeClass = attributeType.determineRawClass().toJavaClass(); + final Class javaTypeClass = attributeType.determineRawClass().toJavaClass(); implicitJavaTypeAccess = typeConfiguration -> { if ( attributeType.getTypeKind() == TypeDetails.Kind.PARAMETERIZED_TYPE ) { return ParameterizedTypeImpl.from( attributeType.asParameterizedType() ); @@ -806,7 +782,7 @@ private void prepareBasicAttribute( }; //noinspection deprecation - final var temporalAnn = attribute.getDirectAnnotationUsage( Temporal.class ); + final Temporal temporalAnn = attribute.getDirectAnnotationUsage( Temporal.class ); if ( temporalAnn != null ) { //noinspection deprecation DEPRECATION_LOGGER.deprecatedAnnotation( Temporal.class, @@ -852,7 +828,7 @@ private void prepareBasicAttribute( normalSupplementalDetails( attribute ); } - private boolean canUseEnumerated(TypeDetails javaType, Class javaTypeClass) { + private boolean canUseEnumerated(TypeDetails javaType, Class javaTypeClass) { if ( javaTypeClass.isEnum() || javaTypeClass.isArray() && javaTypeClass.getComponentType().isEnum() ) { return true; @@ -1152,6 +1128,7 @@ private void normalSupplementalDetails(MemberDetails attribute) { enumType = enumerated.value(); } + //noinspection deprecation final Temporal temporal = attribute.getDirectAnnotationUsage( Temporal.class ); if ( temporal != null ) { temporalPrecision = temporal.value(); @@ -1184,35 +1161,46 @@ public Dialect getDialect() { } private void applyJpaConverter(MemberDetails attribute, ConverterDescriptor attributeConverterDescriptor) { - disallowConverter( attribute, Id.class ); - disallowConverter( attribute, Version.class ); + final boolean autoApply = attributeConverterDescriptor.getAutoApplyDescriptor().isAutoApplicable(); + disallowConverter( attribute, Id.class, autoApply ); + disallowConverter( attribute, Version.class, autoApply ); if ( kind == Kind.MAP_KEY ) { - disallowConverter( attribute, MapKeyTemporal.class ); - disallowConverter( attribute, MapKeyEnumerated.class ); + //noinspection deprecation + disallowConverter( attribute, MapKeyTemporal.class, autoApply ); + disallowConverter( attribute, MapKeyEnumerated.class, autoApply ); } else { - disallowConverter( attribute, Temporal.class ); - disallowConverter( attribute, Enumerated.class ); - } - if ( isAssociation() ) { - throw new AnnotationException( "'AttributeConverter' not allowed for association '" + attribute.getName() + "'" ); - } + //noinspection deprecation + disallowConverter( attribute, Temporal.class, autoApply ); + disallowConverter( attribute, Enumerated.class, autoApply ); + disallowConverter( attribute, ManyToOne.class, autoApply ); + disallowConverter( attribute, OneToOne.class, autoApply ); + disallowConverter( attribute, OneToMany.class, autoApply ); + disallowConverter( attribute, ManyToMany.class, autoApply ); + // Note that @Convert is only allowed in conjunction with + // @Embedded if it specifies a field using attributeName + disallowConverter( attribute, Embedded.class, autoApply ); + disallowConverter( attribute, EmbeddedId.class, autoApply ); + } + // I assume that these do not work with converters (no tests) + disallowConverter( attribute, Struct.class, autoApply ); + disallowConverter( attribute, Array.class, autoApply ); + disallowConverter( attribute, Any.class, autoApply ); this.converterDescriptor = attributeConverterDescriptor; } - void disallowConverter(MemberDetails attribute, Class annotationType) { + void disallowConverter(MemberDetails attribute, Class annotationType, boolean autoApply) { + // NOTE: A really faithful reading of the JPA spec is that we should + // just silently ignore any auto-apply converter which matches + // one of the disallowed attribute types, but for now let's be + // a bit more fussy/helpful, and see how many people complain. if ( attribute.hasDirectAnnotationUsage( annotationType ) ) { throw new AnnotationException( "'AttributeConverter' not allowed for attribute '" + attribute.getName() - + "' annotated '@" + annotationType.getName() + "'" ); + + "' annotated '@" + annotationType.getName() + "'" + + ( autoApply ? " (use '@Convert(disableConversion=true)' to suppress this error)" : "" ) ); } } - private boolean isAssociation() { - // todo : this information is only known to caller(s), need to pass that information in somehow. - // or, is this enough? - return referencedEntityName != null; - } - public BasicValue make() { if ( basicValue != null ) { return basicValue; @@ -1227,7 +1215,8 @@ public BasicValue make() { basicValue = new BasicValue( buildingContext, table ); if ( columns.getPropertyHolder().isComponent() ) { - final ComponentPropertyHolder propertyHolder = (ComponentPropertyHolder) columns.getPropertyHolder(); + final ComponentPropertyHolder propertyHolder = + (ComponentPropertyHolder) columns.getPropertyHolder(); basicValue.setAggregateColumn( propertyHolder.getAggregateColumn() ); } @@ -1275,18 +1264,7 @@ private void linkWithValue() { final InFlightMetadataCollector collector = getMetadataCollector(); final AnnotatedColumn firstColumn = columns.getColumns().get(0); if ( !collector.isInSecondPass() && firstColumn.isNameDeferred() && referencedEntityName != null ) { - final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns(); - joinColumns.setBuildingContext( buildingContext ); - joinColumns.setPropertyHolder( columns.getPropertyHolder() ); - joinColumns.setPropertyName( columns.getPropertyName() ); - //TODO: resetting the parent here looks like a dangerous thing to do - // should we be cloning them first (the legacy code did not) - for ( AnnotatedColumn column : columns.getColumns() ) { - column.setParent( joinColumns ); - } - collector.addSecondPass( - new PkDrivenByDefaultMapsIdSecondPass( referencedEntityName, joinColumns, basicValue ) - ); + collector.addSecondPass( new OverriddenFkSecondPass( basicValue, referencedEntityName, columns ) ); } else if ( aggregateComponent != null ) { assert columns.getColumns().size() == 1; @@ -1311,7 +1289,8 @@ public void fillSimpleValue() { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - if ( explicitCustomType != null && DynamicParameterizedType.class.isAssignableFrom( explicitCustomType ) ) { + if ( explicitCustomType != null + && DynamicParameterizedType.class.isAssignableFrom( explicitCustomType ) ) { basicValue.setTypeParameters( createDynamicParameterizedTypeParameters() ); } @@ -1393,29 +1372,6 @@ private Map createDynamicParameterizedTypeParameters() { return parameters; } - private boolean isEnum() { - final Class clazz = getValueClass(); - return clazz != null && clazz.isEnum(); - } - - private boolean isSerializable() { - final Class clazz = getValueClass(); - return clazz != null && Serializable.class.isAssignableFrom( clazz ); - } - - private Class getValueClass() { - if ( implicitJavaTypeAccess != null ) { - java.lang.reflect.Type type = implicitJavaTypeAccess.apply( getTypeConfiguration() ); - if ( type instanceof ParameterizedType parameterizedType ) { - type = parameterizedType.getRawType(); - } - if ( type instanceof Class cl ) { - return cl; - } - } - return null; - } - /** * Access to detail of basic value mappings based on {@link Kind} */ @@ -1517,4 +1473,65 @@ public Map customTypeParameters(MemberDetails attribute, SourceMo return emptyMap(); } } + + private static AnnotatedJoinColumns convertToJoinColumns(AnnotatedColumns columns, MetadataBuildingContext context) { + final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns(); + joinColumns.setBuildingContext( context ); + joinColumns.setPropertyHolder( columns.getPropertyHolder() ); + joinColumns.setPropertyName( columns.getPropertyName() ); + //TODO: resetting the parent here looks like a dangerous thing to do + // should we be cloning them first (the legacy code did not) + for ( AnnotatedColumn column : columns.getColumns() ) { + column.setParent( joinColumns ); + } + return joinColumns; + } + + // used for resolving FK with @MapsId and @IdClass + private static class OverriddenFkSecondPass implements FkSecondPass { + private final AnnotatedJoinColumns joinColumns; + private final BasicValue value; + private final String referencedEntityName; + + public OverriddenFkSecondPass( + BasicValue value, + String referencedEntityName, + AnnotatedColumns columns) { + this.value = value; + this.referencedEntityName = referencedEntityName; + this.joinColumns = convertToJoinColumns( columns, value.getBuildingContext() ); + } + + @Override + public BasicValue getValue() { + return value; + } + + @Override + public String getReferencedEntityName() { + return referencedEntityName; + } + + @Override + public boolean isInPrimaryKey() { + // @MapsId is not itself in the primary key, + // so it's safe to simply process it after all the primary keys have been processed. + return true; + } + + @Override + public void doSecondPass(Map persistentClasses) { + final PersistentClass referencedEntity = persistentClasses.get( referencedEntityName ); + if ( referencedEntity == null ) { + // TODO: much better error message if this is something that can really happen! + throw new AnnotationException( "Unknown entity name '" + referencedEntityName + "'" ); + } + linkJoinColumnWithValueOverridingNameIfImplicit( + referencedEntity, + referencedEntity.getKey(), + joinColumns, + value + ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java index 9b6f2d5ab267..3041c8b4de06 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java @@ -882,34 +882,6 @@ public static String getPath(PropertyHolder holder, PropertyData property) { return qualify( holder.getPath(), property.getPropertyName() ); } - static PropertyData getPropertyOverriddenByMapperOrMapsId( - boolean isId, - PropertyHolder propertyHolder, - String propertyName, - MetadataBuildingContext buildingContext) { - final ClassDetailsRegistry classDetailsRegistry = buildingContext.getMetadataCollector() - .getSourceModelBuildingContext() - .getClassDetailsRegistry(); - final PersistentClass persistentClass = propertyHolder.getPersistentClass(); - final String name = - isEmpty( persistentClass.getClassName() ) - ? persistentClass.getEntityName() - : persistentClass.getClassName(); - final ClassDetails classDetails = classDetailsRegistry.resolveClassDetails( name ); - final InFlightMetadataCollector metadataCollector = buildingContext.getMetadataCollector(); - if ( propertyHolder.isInIdClass() ) { - final PropertyData data = metadataCollector.getPropertyAnnotatedWithIdAndToOne( classDetails, propertyName ); - if ( data != null ) { - return data; - } - // TODO: is this branch even necessary? - else if ( buildingContext.getBuildingOptions().isSpecjProprietarySyntaxEnabled() ) { - return metadataCollector.getPropertyAnnotatedWithMapsId( classDetails, propertyName ); - } - } - return metadataCollector.getPropertyAnnotatedWithMapsId( classDetails, isId ? "" : propertyName ); - } - public static Map toAliasTableMap(SqlFragmentAlias[] aliases){ final Map ret = new HashMap<>(); for ( SqlFragmentAlias aliasAnnotation : aliases ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ColumnsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ColumnsBuilder.java index c7ac86cc552e..1cb4a6e9ca75 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ColumnsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ColumnsBuilder.java @@ -34,7 +34,6 @@ import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnsFromAnnotations; import static org.hibernate.boot.model.internal.AnnotatedColumn.buildFormulaFromAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.getPath; -import static org.hibernate.boot.model.internal.BinderHelper.getPropertyOverriddenByMapperOrMapsId; import static org.hibernate.boot.model.internal.DialectOverridesAnnotationHelper.getOverridableAnnotation; import static org.hibernate.internal.util.StringHelper.nullIfEmpty; import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty; @@ -288,9 +287,7 @@ private JoinColumn[] getJoinColumnAnnotations(MemberDetails property, PropertyDa /** * Useful to override a column either by {@code @MapsId} or by {@code @IdClass} */ - AnnotatedColumns overrideColumnFromMapperOrMapsIdProperty(boolean isId) { - final PropertyData override = - getPropertyOverriddenByMapperOrMapsId( isId, propertyHolder, property.resolveAttributeName(), buildingContext ); + AnnotatedColumns overrideColumnFromMapperOrMapsIdProperty(PropertyData override) { if ( override != null ) { final AnnotatedJoinColumns joinColumns = buildExplicitJoinColumns( override.getAttributeMember(), override ); return joinColumns == null diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CopyIdentifierComponentSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CopyIdentifierComponentSecondPass.java deleted file mode 100644 index b8cc1932df84..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CopyIdentifierComponentSecondPass.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.boot.model.internal; - -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import org.hibernate.AnnotationException; -import org.hibernate.MappingException; -import org.hibernate.boot.model.relational.Database; -import org.hibernate.boot.spi.MetadataBuildingContext; -import org.hibernate.internal.util.MutableInteger; -import org.hibernate.mapping.BasicValue; -import org.hibernate.mapping.Column; -import org.hibernate.mapping.Component; -import org.hibernate.mapping.PersistentClass; -import org.hibernate.mapping.Property; -import org.hibernate.mapping.Selectable; -import org.hibernate.mapping.SimpleValue; - -import org.jboss.logging.Logger; - -import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize; - -/** - * @author Emmanuel Bernard - */ -public class CopyIdentifierComponentSecondPass extends FkSecondPass { - private static final Logger log = Logger.getLogger( CopyIdentifierComponentSecondPass.class ); - - private final String referencedEntityName; - private final String propertyName; - private final Component component; - private final MetadataBuildingContext buildingContext; - private final AnnotatedJoinColumns joinColumns; - - public CopyIdentifierComponentSecondPass( - Component comp, - String referencedEntityName, String propertyName, - AnnotatedJoinColumns joinColumns, - MetadataBuildingContext buildingContext) { - super( comp, joinColumns ); - this.component = comp; - this.referencedEntityName = referencedEntityName; - this.propertyName = propertyName; - this.buildingContext = buildingContext; - this.joinColumns = joinColumns; - } - - @Override - public String getReferencedEntityName() { - return referencedEntityName; - } - - @Override - public boolean isInPrimaryKey() { - // This second pass is apparently only ever used to initialize composite identifiers - return true; - } - - @Override - public void doSecondPass(Map persistentClasses) throws MappingException { - final PersistentClass referencedPersistentClass = persistentClasses.get( referencedEntityName ); - final Component referencedComponent = getReferencedComponent( referencedPersistentClass ); - - //prepare column name structure - boolean isExplicitReference = true; - final List columns = joinColumns.getJoinColumns(); - final Map columnByReferencedName = mapOfSize( columns.size() ); - for ( AnnotatedJoinColumn joinColumn : columns ) { - if ( !joinColumn.isReferenceImplicit() ) { - //JPA 2 requires referencedColumnNames to be case-insensitive - columnByReferencedName.put( joinColumn.getReferencedColumn().toLowerCase(Locale.ROOT), joinColumn ); - } - } - //try default column orientation - if ( columnByReferencedName.isEmpty() ) { - isExplicitReference = false; - for (int i = 0; i < columns.size(); i++ ) { - columnByReferencedName.put( String.valueOf( i ), columns.get(i) ); - } - } - - final MutableInteger index = new MutableInteger(); - for ( Property referencedProperty : referencedComponent.getProperties() ) { - final Property property; - if ( referencedProperty.isComposite() ) { - property = createComponentProperty( - isExplicitReference, - columnByReferencedName, - index, - referencedProperty - ); - } - else { - property = createSimpleProperty( - referencedPersistentClass, - isExplicitReference, - columnByReferencedName, - index, - referencedProperty - ); - } - component.addProperty( property ); - } - } - - private Component getReferencedComponent(PersistentClass referencedPersistentClass) { - if ( referencedPersistentClass == null ) { - // TODO: much better error message if this is something that can really happen! - throw new AnnotationException( "Unknown entity name '" + referencedEntityName + "'"); - } - if ( referencedPersistentClass.getIdentifier() instanceof Component id ) { - return id; - } - else { - // The entity with the @MapsId annotation has a composite - // id type, but the referenced entity has a basic-typed id. - // Therefore, the @MapsId annotation should have specified - // a property of the composite id that has the foreign key - throw new AnnotationException( - "Missing 'value' in '@MapsId' annotation of association '" + propertyName - + "' of entity '" + component.getOwner().getEntityName() - + "' with composite identifier type" - + " ('@MapsId' must specify a property of the '@EmbeddedId' class which has the foreign key of '" - + referencedEntityName + "')" - ); - } - } - - private Property createComponentProperty( - boolean isExplicitReference, - Map columnByReferencedName, - MutableInteger index, - Property referencedProperty ) { - final Property property = new Property(); - property.setName( referencedProperty.getName() ); - //FIXME set optional? - //property.setOptional( property.isOptional() ); - property.setPersistentClass( component.getOwner() ); - property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() ); - Component value = new Component( buildingContext, component.getOwner() ); - - property.setValue( value ); - final Component referencedValue = (Component) referencedProperty.getValue(); - value.setTypeName( referencedValue.getTypeName() ); - value.setTypeParameters( referencedValue.getTypeParameters() ); - value.setComponentClassName( referencedValue.getComponentClassName() ); - - - for ( Property referencedComponentProperty : referencedValue.getProperties() ) { - if ( referencedComponentProperty.isComposite() ) { - value.addProperty( createComponentProperty( - isExplicitReference, - columnByReferencedName, - index, - referencedComponentProperty - ) ); - } - else { - value.addProperty( createSimpleProperty( - referencedValue.getOwner(), - isExplicitReference, - columnByReferencedName, - index, - referencedComponentProperty - ) ); - } - } - - return property; - } - - - private Property createSimpleProperty( - PersistentClass referencedPersistentClass, - boolean isExplicitReference, - Map columnByReferencedName, - MutableInteger index, - Property referencedProperty ) { - final Property property = new Property(); - property.setName( referencedProperty.getName() ); - //FIXME set optional? - //property.setOptional( property.isOptional() ); - property.setPersistentClass( component.getOwner() ); - property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() ); - final SimpleValue value = new BasicValue( buildingContext, component.getTable() ); - property.setValue( value ); - final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue(); - value.copyTypeFrom( referencedValue ); - - //TODO: this bit is nasty, move up to AnnotatedJoinColumns - final AnnotatedJoinColumn firstColumn = joinColumns.getJoinColumns().get(0); - if ( firstColumn.isNameDeferred() ) { - firstColumn.copyReferencedStructureAndCreateDefaultJoinColumns( - referencedPersistentClass, - referencedValue, - value - ); - } - else { - for ( Selectable selectable : referencedValue.getSelectables() ) { - if ( selectable instanceof Column column ) { - final AnnotatedJoinColumn joinColumn; - final String logicalColumnName; - if ( isExplicitReference ) { - logicalColumnName = column.getName(); - //JPA 2 requires referencedColumnNames to be case-insensitive - joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase( Locale.ROOT ) ); - } - else { - logicalColumnName = null; - joinColumn = columnByReferencedName.get( String.valueOf( index.get() ) ); - index.getAndIncrement(); - } - if ( joinColumn == null && !firstColumn.isNameDeferred() ) { - throw new AnnotationException( - "Property '" + propertyName - + "' of entity '" + component.getOwner().getEntityName() - + "' must have a '@JoinColumn' which references the foreign key column '" - + logicalColumnName + "'" - ); - } - final String columnName = - joinColumn == null || joinColumn.isNameDeferred() - ? "tata_" + column.getName() - : joinColumn.getName(); - - final Database database = buildingContext.getMetadataCollector().getDatabase(); - final String physicalName = - buildingContext.getBuildingOptions().getPhysicalNamingStrategy() - .toPhysicalColumnName( database.toIdentifier( columnName ), - database.getJdbcEnvironment() ) - .render( database.getDialect() ); - value.addColumn( new Column( physicalName ) ); - if ( joinColumn != null ) { - applyComponentColumnSizeValueToJoinColumn( column, joinColumn ); - joinColumn.linkWithValue( value ); - } - column.setValue( value ); - } - else { - //FIXME take care of Formula - log.debug( "Encountered formula definition; skipping" ); - } - } - } - return property; - } - - private void applyComponentColumnSizeValueToJoinColumn(Column column, AnnotatedJoinColumn joinColumn) { - final Column mappingColumn = joinColumn.getMappingColumn(); - mappingColumn.setLength( column.getLength() ); - mappingColumn.setPrecision( column.getPrecision() ); - mappingColumn.setScale( column.getScale() ); - mappingColumn.setArrayLength( column.getArrayLength() ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CreateKeySecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CreateKeySecondPass.java index 0e370f0f1d12..c4940b02fa9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CreateKeySecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CreateKeySecondPass.java @@ -3,6 +3,7 @@ * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.boot.model.internal; + import java.util.Map; import org.hibernate.boot.spi.SecondPass; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java index 0314e0b43b21..5bf524d081e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java @@ -19,19 +19,26 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import org.hibernate.AnnotationException; +import org.hibernate.AssertionFailure; +import org.hibernate.MappingException; import org.hibernate.annotations.DiscriminatorFormula; import org.hibernate.annotations.Instantiator; import org.hibernate.annotations.TypeBinderType; import org.hibernate.binder.TypeBinder; +import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.spi.AccessType; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.PropertyData; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.MutableInteger; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Component; +import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.Selectable; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.SingleTableSubclass; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.spi.EmbeddableInstantiator; import org.hibernate.models.spi.ClassDetails; @@ -54,13 +61,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TreeMap; import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.DEFAULT_DISCRIMINATOR_COLUMN_NAME; import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn; import static org.hibernate.boot.model.internal.BinderHelper.getPath; -import static org.hibernate.boot.model.internal.BinderHelper.getPropertyOverriddenByMapperOrMapsId; import static org.hibernate.boot.model.internal.BinderHelper.getRelativePath; import static org.hibernate.boot.model.internal.BinderHelper.hasToOneAnnotation; import static org.hibernate.boot.model.internal.DialectOverridesAnnotationHelper.getOverridableAnnotation; @@ -73,6 +80,7 @@ import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.unqualify; import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty; +import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize; /** * A binder responsible for interpreting {@link Embeddable} classes and producing @@ -92,67 +100,128 @@ static PropertyBinder createCompositeBinder( MemberDetails property, AnnotatedColumns columns, ClassDetails returnedClass, - PropertyBinder propertyBinder, + boolean isId, boolean isOverridden, + PropertyData mapsIdProperty, Class> compositeUserType) { - final String referencedEntityName; - final String propertyName; - final AnnotatedJoinColumns actualColumns; - if ( isOverridden ) { - // careful: not always a @MapsId property, sometimes it's from an @IdClass - final PropertyData mapsIdProperty = getPropertyOverriddenByMapperOrMapsId( - propertyBinder.isId(), - propertyHolder, - property.resolveAttributeName(), - context - ); - referencedEntityName = mapsIdProperty.getClassOrElementName(); - propertyName = mapsIdProperty.getPropertyName(); - final AnnotatedJoinColumns parent = new AnnotatedJoinColumns(); - parent.setBuildingContext( context ); - parent.setPropertyHolder( propertyHolder ); - parent.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); - //TODO: resetting the parent here looks like a dangerous thing to do - // should we be cloning them first (the legacy code did not) - for ( AnnotatedColumn column : columns.getColumns() ) { - column.setParent( parent ); - } - actualColumns = parent; - } - else { - referencedEntityName = null; - propertyName = null; - actualColumns = null; - } - return createEmbeddedProperty( inferredData, propertyHolder, entityBinder, context, isComponentEmbedded, - propertyBinder.isId(), + isId, inheritanceStatePerClass, - bindEmbeddable( - inferredData, + createEmbeddable( propertyHolder, - entityBinder.getPropertyAccessor( property ), + inferredData, entityBinder, isIdentifierMapper, - context, isComponentEmbedded, - propertyBinder.isId(), + context, inheritanceStatePerClass, - referencedEntityName, - propertyName, - determineCustomInstantiator( property, returnedClass, context ), - compositeUserType, - actualColumns, - columns + property, + columns, + returnedClass, + isId, + isOverridden, + mapsIdProperty, + compositeUserType ) ); } + private static Component createEmbeddable( + PropertyHolder propertyHolder, + PropertyData inferredData, + EntityBinder entityBinder, + boolean isIdentifierMapper, + boolean isComponentEmbedded, + MetadataBuildingContext context, + Map inheritanceStatePerClass, + MemberDetails property, + AnnotatedColumns columns, + ClassDetails returnedClass, + boolean isId, + boolean isOverridden, + PropertyData mapsIdProperty, + Class> compositeUserType) { + if ( isOverridden ) { + if ( compositeUserType != null ) { + // I suppose this assertion is correct, but it might not be + // Perhaps it was OK that we were just ignoring the CUT + throw new AssertionFailure( "CompositeUserType not allowed with @MapsId" ); + } + return bindOverriddenEmbeddable( + propertyHolder, + inferredData, + isIdentifierMapper, + isComponentEmbedded, + context, + inheritanceStatePerClass, + property, + columns, + returnedClass, + isId, + mapsIdProperty + ); + } + else { + return bindEmbeddable( + inferredData, + propertyHolder, + entityBinder.getPropertyAccessor( property ), + entityBinder, + isIdentifierMapper, + context, + isComponentEmbedded, + isId, + inheritanceStatePerClass, + determineCustomInstantiator( property, returnedClass, context ), + compositeUserType, + columns + ); + } + } + + private static Component bindOverriddenEmbeddable( + PropertyHolder propertyHolder, + PropertyData inferredData, + boolean isIdentifierMapper, + boolean isComponentEmbedded, + MetadataBuildingContext context, + Map inheritanceStatePerClass, + MemberDetails property, + AnnotatedColumns columns, + ClassDetails returnedClass, + boolean isId, + PropertyData mapsIdProperty) { + // careful: not always a @MapsId property, sometimes it's from an @IdClass + final String propertyName = mapsIdProperty.getPropertyName(); + final AnnotatedJoinColumns actualColumns = new AnnotatedJoinColumns(); + actualColumns.setBuildingContext( context ); + actualColumns.setPropertyHolder( propertyHolder ); + actualColumns.setPropertyName( getRelativePath( propertyHolder, propertyName ) ); + //TODO: resetting the parent here looks like a dangerous thing to do + // should we be cloning them first (the legacy code did not) + for ( AnnotatedColumn column : columns.getColumns() ) { + column.setParent( actualColumns ); + } + return bindOverriddenEmbeddable( + inferredData, + propertyHolder, + isIdentifierMapper, + context, + isComponentEmbedded, + isId, + inheritanceStatePerClass, + mapsIdProperty.getClassOrElementName(), + propertyName, + determineCustomInstantiator( property, returnedClass, context ), + actualColumns + ); + } + static boolean isEmbedded(MemberDetails property, ClassDetails returnedClass) { return property.hasDirectAnnotationUsage( Embedded.class ) || property.hasDirectAnnotationUsage( EmbeddedId.class ) @@ -163,13 +232,51 @@ static boolean isEmbedded(MemberDetails property, TypeDetails returnedClass) { if ( property.hasDirectAnnotationUsage( Embedded.class ) || property.hasDirectAnnotationUsage( EmbeddedId.class ) ) { return true; } - - final ClassDetails returnClassDetails = returnedClass.determineRawClass(); - return returnClassDetails.hasDirectAnnotationUsage( Embeddable.class ) + else { + final ClassDetails returnClassDetails = returnedClass.determineRawClass(); + return returnClassDetails.hasDirectAnnotationUsage( Embeddable.class ) && !property.hasDirectAnnotationUsage( Convert.class ); + } } - public static Component bindEmbeddable( + private static Component bindOverriddenEmbeddable( + PropertyData inferredData, + PropertyHolder propertyHolder, + boolean isIdentifierMapper, + MetadataBuildingContext context, + boolean isComponentEmbedded, + boolean isId, // is an identifier + Map inheritanceStatePerClass, + String referencedEntityName, // is a component which is overridden by a @MapsId + String propertyName, + Class customInstantiatorImpl, + AnnotatedJoinColumns annotatedJoinColumns) { + final Component component = createEmbeddable( + propertyHolder, + inferredData, + isComponentEmbedded, + isIdentifierMapper, + customInstantiatorImpl, + context + ); + context.getMetadataCollector() + .addSecondPass( new CopyIdentifierComponentSecondPass( + component, + referencedEntityName, + propertyName, + annotatedJoinColumns, + context + ) ); + + if ( isId ) { + component.setKey( true ); + checkEmbeddedId( inferredData, propertyHolder, referencedEntityName, component ); + } + callTypeBinders( component, context, inferredData.getPropertyType() ); + return component; + } + + static Component bindEmbeddable( PropertyData inferredData, PropertyHolder propertyHolder, AccessType propertyAccessor, @@ -179,50 +286,27 @@ public static Component bindEmbeddable( boolean isComponentEmbedded, boolean isId, //is an identifier Map inheritanceStatePerClass, - String referencedEntityName, //is a component who is overridden by a @MapsId - String propertyName, Class customInstantiatorImpl, Class> compositeUserTypeClass, - AnnotatedJoinColumns columns, AnnotatedColumns annotatedColumns) { - final Component component; - if ( referencedEntityName != null ) { - component = createEmbeddable( - propertyHolder, - inferredData, - isComponentEmbedded, - isIdentifierMapper, - customInstantiatorImpl, - context - ); - context.getMetadataCollector().addSecondPass( new CopyIdentifierComponentSecondPass( - component, - referencedEntityName, - propertyName, - columns, - context - ) ); - } - else { - component = fillEmbeddable( - propertyHolder, - inferredData, - propertyAccessor, - !isId, - entityBinder, - isComponentEmbedded, - isIdentifierMapper, - context.getMetadataCollector().isInSecondPass(), - customInstantiatorImpl, - compositeUserTypeClass, - annotatedColumns, - context, - inheritanceStatePerClass - ); - } + final Component component = fillEmbeddable( + propertyHolder, + inferredData, + propertyAccessor, + !isId, + entityBinder, + isComponentEmbedded, + isIdentifierMapper, + context.getMetadataCollector().isInSecondPass(), + customInstantiatorImpl, + compositeUserTypeClass, + annotatedColumns, + context, + inheritanceStatePerClass + ); if ( isId ) { component.setKey( true ); - checkEmbeddedId( inferredData, propertyHolder, referencedEntityName, component ); + checkEmbeddedId( inferredData, propertyHolder, null, component ); } callTypeBinders( component, context, inferredData.getPropertyType() ); return component; @@ -966,4 +1050,236 @@ public static Class determineCustomInstantiato return null; } + private static class CopyIdentifierComponentSecondPass implements FkSecondPass { + private final String referencedEntityName; + private final String propertyName; + private final Component component; + private final MetadataBuildingContext buildingContext; + private final AnnotatedJoinColumns joinColumns; + + private CopyIdentifierComponentSecondPass( + Component component, + String referencedEntityName, String propertyName, + AnnotatedJoinColumns joinColumns, + MetadataBuildingContext buildingContext) { + this.component = component; + this.referencedEntityName = referencedEntityName; + this.propertyName = propertyName; + this.buildingContext = buildingContext; + this.joinColumns = joinColumns; + } + + @Override + public Value getValue() { + return component; + } + + @Override + public String getReferencedEntityName() { + return referencedEntityName; + } + + @Override + public boolean isInPrimaryKey() { + // This second pass is apparently only ever used to initialize composite identifiers + return true; + } + + @Override + public void doSecondPass(Map persistentClasses) throws MappingException { + final PersistentClass referencedPersistentClass = persistentClasses.get( referencedEntityName ); + final Component referencedComponent = getReferencedComponent( referencedPersistentClass ); + + //prepare column name structure + boolean isExplicitReference = true; + final List columns = joinColumns.getJoinColumns(); + final Map columnByReferencedName = mapOfSize( columns.size() ); + for ( AnnotatedJoinColumn joinColumn : columns ) { + if ( !joinColumn.isReferenceImplicit() ) { + //JPA 2 requires referencedColumnNames to be case-insensitive + columnByReferencedName.put( joinColumn.getReferencedColumn().toLowerCase( Locale.ROOT), joinColumn ); + } + } + //try default column orientation + if ( columnByReferencedName.isEmpty() ) { + isExplicitReference = false; + for (int i = 0; i < columns.size(); i++ ) { + columnByReferencedName.put( String.valueOf( i ), columns.get(i) ); + } + } + + final MutableInteger index = new MutableInteger(); + for ( Property referencedProperty : referencedComponent.getProperties() ) { + final Property property; + if ( referencedProperty.isComposite() ) { + property = createComponentProperty( + isExplicitReference, + columnByReferencedName, + index, + referencedProperty + ); + } + else { + property = createSimpleProperty( + referencedPersistentClass, + isExplicitReference, + columnByReferencedName, + index, + referencedProperty + ); + } + component.addProperty( property ); + } + } + + private Component getReferencedComponent(PersistentClass referencedPersistentClass) { + if ( referencedPersistentClass == null ) { + // TODO: much better error message if this is something that can really happen! + throw new AnnotationException( "Unknown entity name '" + referencedEntityName + "'"); + } + if ( referencedPersistentClass.getIdentifier() instanceof Component id ) { + return id; + } + else { + // The entity with the @MapsId annotation has a composite + // id type, but the referenced entity has a basic-typed id. + // Therefore, the @MapsId annotation should have specified + // a property of the composite id that has the foreign key + throw new AnnotationException( + "Missing 'value' in '@MapsId' annotation of association '" + propertyName + + "' of entity '" + component.getOwner().getEntityName() + + "' with composite identifier type" + + " ('@MapsId' must specify a property of the '@EmbeddedId' class which has the foreign key of '" + + referencedEntityName + "')" + ); + } + } + + private Property createComponentProperty( + boolean isExplicitReference, + Map columnByReferencedName, + MutableInteger index, + Property referencedProperty ) { + final Property property = new Property(); + property.setName( referencedProperty.getName() ); + //FIXME set optional? + //property.setOptional( property.isOptional() ); + property.setPersistentClass( component.getOwner() ); + property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() ); + Component value = new Component( buildingContext, component.getOwner() ); + + property.setValue( value ); + final Component referencedValue = (Component) referencedProperty.getValue(); + value.setTypeName( referencedValue.getTypeName() ); + value.setTypeParameters( referencedValue.getTypeParameters() ); + value.setComponentClassName( referencedValue.getComponentClassName() ); + + + for ( Property referencedComponentProperty : referencedValue.getProperties() ) { + if ( referencedComponentProperty.isComposite() ) { + value.addProperty( createComponentProperty( + isExplicitReference, + columnByReferencedName, + index, + referencedComponentProperty + ) ); + } + else { + value.addProperty( createSimpleProperty( + referencedValue.getOwner(), + isExplicitReference, + columnByReferencedName, + index, + referencedComponentProperty + ) ); + } + } + + return property; + } + + + private Property createSimpleProperty( + PersistentClass referencedPersistentClass, + boolean isExplicitReference, + Map columnByReferencedName, + MutableInteger index, + Property referencedProperty ) { + final Property property = new Property(); + property.setName( referencedProperty.getName() ); + //FIXME set optional? + //property.setOptional( property.isOptional() ); + property.setPersistentClass( component.getOwner() ); + property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() ); + final SimpleValue value = new BasicValue( buildingContext, component.getTable() ); + property.setValue( value ); + final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue(); + value.copyTypeFrom( referencedValue ); + + //TODO: this bit is nasty, move up to AnnotatedJoinColumns + final AnnotatedJoinColumn firstColumn = joinColumns.getJoinColumns().get(0); + if ( firstColumn.isNameDeferred() ) { + firstColumn.copyReferencedStructureAndCreateDefaultJoinColumns( + referencedPersistentClass, + referencedValue, + value + ); + } + else { + for ( Selectable selectable : referencedValue.getSelectables() ) { + if ( selectable instanceof org.hibernate.mapping.Column column ) { + final AnnotatedJoinColumn joinColumn; + final String logicalColumnName; + if ( isExplicitReference ) { + logicalColumnName = column.getName(); + //JPA 2 requires referencedColumnNames to be case-insensitive + joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase( Locale.ROOT ) ); + } + else { + logicalColumnName = null; + joinColumn = columnByReferencedName.get( String.valueOf( index.get() ) ); + index.getAndIncrement(); + } + if ( joinColumn == null && !firstColumn.isNameDeferred() ) { + throw new AnnotationException( + "Property '" + propertyName + + "' of entity '" + component.getOwner().getEntityName() + + "' must have a '@JoinColumn' which references the foreign key column '" + + logicalColumnName + "'" + ); + } + final String columnName = + joinColumn == null || joinColumn.isNameDeferred() + ? "tata_" + column.getName() + : joinColumn.getName(); + + final Database database = buildingContext.getMetadataCollector().getDatabase(); + final String physicalName = + buildingContext.getBuildingOptions().getPhysicalNamingStrategy() + .toPhysicalColumnName( database.toIdentifier( columnName ), + database.getJdbcEnvironment() ) + .render( database.getDialect() ); + value.addColumn( new org.hibernate.mapping.Column( physicalName ) ); + if ( joinColumn != null ) { + applyComponentColumnSizeValueToJoinColumn( column, joinColumn ); + joinColumn.linkWithValue( value ); + } + column.setValue( value ); + } + else { + //FIXME take care of Formula + } + } + } + return property; + } + + private void applyComponentColumnSizeValueToJoinColumn(org.hibernate.mapping.Column column, AnnotatedJoinColumn joinColumn) { + final org.hibernate.mapping.Column mappingColumn = joinColumn.getMappingColumn(); + mappingColumn.setLength( column.getLength() ); + mappingColumn.setPrecision( column.getPrecision() ); + mappingColumn.setScale( column.getScale() ); + mappingColumn.setArrayLength( column.getArrayLength() ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java index 108525f14167..9bcec914bcff 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java @@ -858,16 +858,17 @@ private void singleTableInheritance(InheritanceState inheritanceState, PropertyH private void joinedInheritance(InheritanceState state, PersistentClass superEntity, PropertyHolder holder) { if ( state.hasParents() ) { final AnnotatedJoinColumns joinColumns = subclassJoinColumns( annotatedClass, superEntity, context ); - final JoinedSubclass jsc = (JoinedSubclass) persistentClass; - final DependantValue key = new DependantValue( context, jsc.getTable(), jsc.getIdentifier() ); - jsc.setKey( key ); + final JoinedSubclass joinedSubclass = (JoinedSubclass) persistentClass; + final DependantValue key = + new DependantValue( context, joinedSubclass.getTable(), joinedSubclass.getIdentifier() ); + joinedSubclass.setKey( key ); handleForeignKeys( annotatedClass, context, key ); final OnDelete onDelete = annotatedClass.getAnnotationUsage( OnDelete.class, getSourceModelContext() ); key.setOnDeleteAction( onDelete == null ? null : onDelete.action() ); //we are never in a second pass at that stage, so queue it final InFlightMetadataCollector metadataCollector = getMetadataCollector(); - metadataCollector.addSecondPass( new JoinedSubclassFkSecondPass( jsc, joinColumns, key, context) ); - metadataCollector.addSecondPass( new CreateKeySecondPass( jsc ) ); + metadataCollector.addSecondPass( new JoinedSubclassFkSecondPass( joinedSubclass, joinColumns, key, context) ); + metadataCollector.addSecondPass( new CreateKeySecondPass( joinedSubclass ) ); } final AnnotatedDiscriminatorColumn discriminatorColumn = processJoinedDiscriminatorProperties( state ); @@ -2307,4 +2308,42 @@ private void bindFilters(AnnotationTarget element) { this.filters.add( filter ); } } + + private static class JoinedSubclassFkSecondPass implements FkSecondPass { + private final JoinedSubclass entity; + private final MetadataBuildingContext buildingContext; + private final SimpleValue key; + private final AnnotatedJoinColumns columns; + + private JoinedSubclassFkSecondPass( + JoinedSubclass entity, + AnnotatedJoinColumns inheritanceJoinedColumns, + SimpleValue key, + MetadataBuildingContext buildingContext) { + this.entity = entity; + this.buildingContext = buildingContext; + this.key = key; + this.columns = inheritanceJoinedColumns; + } + + @Override + public Value getValue() { + return key; + } + + @Override + public String getReferencedEntityName() { + return entity.getSuperclass().getEntityName(); + } + + @Override + public boolean isInPrimaryKey() { + return true; + } + + @Override + public void doSecondPass(Map persistentClasses) { + bindForeignKey( entity.getSuperclass(), entity, columns, key, false, buildingContext ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FkSecondPass.java index e08c3d8e947b..9fb4b4b9834f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FkSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/FkSecondPass.java @@ -3,56 +3,15 @@ * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.boot.model.internal; -import java.util.concurrent.atomic.AtomicInteger; import org.hibernate.boot.spi.SecondPass; -import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Value; /** * @author Emmanuel Bernard */ -public abstract class FkSecondPass implements SecondPass { - protected SimpleValue value; - protected AnnotatedJoinColumns columns; - /** - * unique counter is needed to differentiate 2 instances of FKSecondPass - * as they are compared. - * Fairly hacky but IBM VM sometimes returns the same hashCode for 2 different objects - * TODO is it doable to rely on the Ejb3JoinColumn names? Not sure as they could be inferred - */ - private final int uniqueCounter; - private static final AtomicInteger globalCounter = new AtomicInteger(); - - public FkSecondPass(SimpleValue value, AnnotatedJoinColumns columns) { - this.value = value; - this.columns = columns; - this.uniqueCounter = globalCounter.getAndIncrement(); - } - - public Value getValue() { - return value; - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !( o instanceof FkSecondPass ) ) { - return false; - } - - final FkSecondPass that = (FkSecondPass) o; - return uniqueCounter == that.uniqueCounter; - } - - @Override - public int hashCode() { - return uniqueCounter; - } - - public abstract String getReferencedEntityName(); - - public abstract boolean isInPrimaryKey(); +public interface FkSecondPass extends SecondPass { + Value getValue(); + String getReferencedEntityName(); + boolean isInPrimaryKey(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/JoinedSubclassFkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/JoinedSubclassFkSecondPass.java deleted file mode 100644 index 85492bf61d20..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/JoinedSubclassFkSecondPass.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.boot.model.internal; - -import java.util.Map; - -import org.hibernate.MappingException; -import org.hibernate.boot.spi.MetadataBuildingContext; -import org.hibernate.mapping.JoinedSubclass; -import org.hibernate.mapping.PersistentClass; -import org.hibernate.mapping.SimpleValue; - -/** - * @author Emmanuel Bernard - */ -public class JoinedSubclassFkSecondPass extends FkSecondPass { - private final JoinedSubclass entity; - private final MetadataBuildingContext buildingContext; - - public JoinedSubclassFkSecondPass( - JoinedSubclass entity, - AnnotatedJoinColumns inheritanceJoinedColumns, - SimpleValue key, - MetadataBuildingContext buildingContext) { - super( key, inheritanceJoinedColumns ); - this.entity = entity; - this.buildingContext = buildingContext; - } - - @Override - public String getReferencedEntityName() { - return entity.getSuperclass().getEntityName(); - } - - @Override - public boolean isInPrimaryKey() { - return true; - } - - @Override - public void doSecondPass(Map persistentClasses) throws MappingException { - TableBinder.bindForeignKey( entity.getSuperclass(), entity, columns, value, false, buildingContext ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PkDrivenByDefaultMapsIdSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PkDrivenByDefaultMapsIdSecondPass.java deleted file mode 100644 index c219b0204c3c..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PkDrivenByDefaultMapsIdSecondPass.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.boot.model.internal; -import java.util.Map; - -import org.hibernate.AnnotationException; -import org.hibernate.MappingException; -import org.hibernate.mapping.PersistentClass; -import org.hibernate.mapping.SimpleValue; - -/** - * @author Emmanuel Bernard - */ -public class PkDrivenByDefaultMapsIdSecondPass extends FkSecondPass { - private final String referencedEntityName; - private final AnnotatedJoinColumns columns; - private final SimpleValue value; - - public PkDrivenByDefaultMapsIdSecondPass(String referencedEntityName, AnnotatedJoinColumns columns, SimpleValue value) { - super( value, columns ); - this.referencedEntityName = referencedEntityName; - this.columns = columns; - this.value = value; - } - - @Override - public String getReferencedEntityName() { - return referencedEntityName; - } - - @Override - public boolean isInPrimaryKey() { - // @MapsId is not itself in the primary key, - // so it's safe to simply process it after all the primary keys have been processed. - return true; - } - - @Override - public void doSecondPass(Map persistentClasses) throws MappingException { - PersistentClass referencedEntity = persistentClasses.get( referencedEntityName ); - if ( referencedEntity == null ) { - // TODO: much better error message if this is something that can really happen! - throw new AnnotationException( "Unknown entity name '" + referencedEntityName + "'" ); - } - TableBinder.linkJoinColumnWithValueOverridingNameIfImplicit( - referencedEntity, - referencedEntity.getKey(), - columns, - value); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java index 8ff10f2cadfa..4960a2fc92d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java @@ -42,6 +42,7 @@ import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.EventType; import org.hibernate.generator.EventTypeSets; +import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.Component; import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; @@ -57,6 +58,7 @@ import org.hibernate.models.spi.AnnotationDescriptorRegistry; import org.hibernate.models.spi.ArrayTypeDetails; import org.hibernate.models.spi.ClassDetails; +import org.hibernate.models.spi.ClassDetailsRegistry; import org.hibernate.models.spi.MemberDetails; import org.hibernate.models.spi.SourceModelBuildingContext; import org.hibernate.models.spi.TypeDetails; @@ -82,11 +84,11 @@ import static org.hibernate.boot.model.internal.AnyBinder.bindAny; import static org.hibernate.boot.model.internal.BinderHelper.getMappedSuperclassOrNull; import static org.hibernate.boot.model.internal.BinderHelper.getPath; -import static org.hibernate.boot.model.internal.BinderHelper.getPropertyOverriddenByMapperOrMapsId; import static org.hibernate.boot.model.internal.BinderHelper.hasToOneAnnotation; import static org.hibernate.boot.model.internal.ClassPropertyHolder.handleGenericComponentProperty; import static org.hibernate.boot.model.internal.ClassPropertyHolder.prepareActualProperty; import static org.hibernate.boot.model.internal.CollectionBinder.bindCollection; +import static org.hibernate.boot.model.internal.EmbeddableBinder.bindEmbeddable; import static org.hibernate.boot.model.internal.EmbeddableBinder.createCompositeBinder; import static org.hibernate.boot.model.internal.EmbeddableBinder.createEmbeddable; import static org.hibernate.boot.model.internal.EmbeddableBinder.determineCustomInstantiator; @@ -128,13 +130,13 @@ public class PropertyBinder { private boolean embedded; private EntityBinder entityBinder; private boolean toMany; - private String referencedEntityName; + private String referencedEntityName; // only used for @MapsId or @IdClass protected SourceModelBuildingContext getSourceModelContext() { return buildingContext.getMetadataCollector().getSourceModelBuildingContext(); } - public void setReferencedEntityName(String referencedEntityName) { + private void setReferencedEntityName(String referencedEntityName) { this.referencedEntityName = referencedEntityName; } @@ -260,7 +262,6 @@ private Property makePropertyAndValue() { holder.startingProperty( memberDetails ); basicValueBinder = new BasicValueBinder( BasicValueBinder.Kind.ATTRIBUTE, buildingContext ); - basicValueBinder.setPropertyName( name ); basicValueBinder.setReturnedClassName( returnedClassName ); basicValueBinder.setColumns( columns ); basicValueBinder.setPersistentClassName( containerClassName ); @@ -782,22 +783,16 @@ private static void buildProperty( new ColumnsBuilder( propertyHolder, nullability, property, inferredData, entityBinder, context ) .extractMetadata(); - final PropertyBinder propertyBinder = new PropertyBinder(); - propertyBinder.setName( inferredData.getPropertyName() ); - propertyBinder.setReturnedClassName( inferredData.getTypeName() ); - propertyBinder.setAccessType( inferredData.getDefaultAccess() ); - propertyBinder.setHolder( propertyHolder ); - propertyBinder.setMemberDetails( property ); - propertyBinder.setReturnedClass( attributeTypeDetails ); - propertyBinder.setBuildingContext( context ); - if ( isIdentifierMapper ) { - propertyBinder.setInsertable( false ); - propertyBinder.setUpdatable( false ); - } - propertyBinder.setDeclaringClass( inferredData.getDeclaringClass() ); - propertyBinder.setEntityBinder( entityBinder ); - propertyBinder.setInheritanceStatePerClass( inheritanceStatePerClass ); - propertyBinder.setId( !entityBinder.isIgnoreIdAnnotations() && hasIdAnnotation( property ) ); + final PropertyBinder propertyBinder = propertyBinder( + propertyHolder, + inferredData, + entityBinder, + isIdentifierMapper, + context, + inheritanceStatePerClass, + property, + attributeTypeDetails + ); if ( isPropertyOfRegularEmbeddable( propertyHolder, isComponentEmbedded ) && property.hasDirectAnnotationUsage(Id.class)) { @@ -811,7 +806,7 @@ private static void buildProperty( } final AnnotatedJoinColumns joinColumns = columnsBuilder.getJoinColumns(); - final AnnotatedColumns columns = bindProperty( + final AnnotatedColumns columns = propertyBinder.bindProperty( propertyHolder, nullability, inferredData, @@ -819,23 +814,48 @@ private static void buildProperty( isIdentifierMapper, isComponentEmbedded, inSecondPass, - context, - inheritanceStatePerClass, property, attributeClassDetails, - columnsBuilder, - propertyBinder + columnsBuilder ); addNaturalIds( inSecondPass, property, columns, joinColumns, context ); } + private static PropertyBinder propertyBinder( + PropertyHolder propertyHolder, + PropertyData inferredData, + EntityBinder entityBinder, + boolean isIdentifierMapper, + MetadataBuildingContext context, + Map inheritanceStatePerClass, + MemberDetails property, + TypeDetails attributeTypeDetails) { + final PropertyBinder propertyBinder = new PropertyBinder(); + propertyBinder.setName( inferredData.getPropertyName() ); + propertyBinder.setReturnedClassName( inferredData.getTypeName() ); + propertyBinder.setAccessType( inferredData.getDefaultAccess() ); + propertyBinder.setHolder( propertyHolder ); + propertyBinder.setMemberDetails( property ); + propertyBinder.setReturnedClass( attributeTypeDetails ); + propertyBinder.setBuildingContext( context ); + if ( isIdentifierMapper ) { + propertyBinder.setInsertable( false ); + propertyBinder.setUpdatable( false ); + } + propertyBinder.setDeclaringClass( inferredData.getDeclaringClass() ); + propertyBinder.setEntityBinder( entityBinder ); + propertyBinder.setInheritanceStatePerClass( inheritanceStatePerClass ); + propertyBinder.setId( !entityBinder.isIgnoreIdAnnotations() && hasIdAnnotation( property ) ); + return propertyBinder; + } + private static boolean isPropertyOfRegularEmbeddable(PropertyHolder propertyHolder, boolean isComponentEmbedded) { return propertyHolder.isComponent() // it's a field of some sort of composite value && !propertyHolder.isInIdClass() // it's not a field of an id class && !isComponentEmbedded; // it's not an entity field matching a field of the id class } - private static AnnotatedColumns bindProperty( + private AnnotatedColumns bindProperty( PropertyHolder propertyHolder, Nullability nullability, PropertyData inferredData, @@ -843,21 +863,15 @@ private static AnnotatedColumns bindProperty( boolean isIdentifierMapper, boolean isComponentEmbedded, boolean inSecondPass, - MetadataBuildingContext context, - Map inheritanceStatePerClass, MemberDetails property, ClassDetails returnedClass, - ColumnsBuilder columnsBuilder, - PropertyBinder propertyBinder) { + ColumnsBuilder columnsBuilder) { if ( isVersion( property ) ) { bindVersionProperty( propertyHolder, inferredData, isIdentifierMapper, - context, - inheritanceStatePerClass, - columnsBuilder.getColumns(), - propertyBinder + columnsBuilder.getColumns() ); } else if ( isManyToOne( property ) ) { @@ -866,10 +880,10 @@ else if ( isManyToOne( property ) ) { inferredData, isIdentifierMapper, inSecondPass, - context, + buildingContext, property, columnsBuilder.getJoinColumns(), - propertyBinder + this ); } else if ( isOneToOne( property ) ) { @@ -878,10 +892,10 @@ else if ( isOneToOne( property ) ) { inferredData, isIdentifierMapper, inSecondPass, - context, + buildingContext, property, columnsBuilder.getJoinColumns(), - propertyBinder + this ); } else if ( isAny( property ) ) { @@ -891,7 +905,7 @@ else if ( isAny( property ) ) { inferredData, entityBinder, isIdentifierMapper, - context, + buildingContext, property, columnsBuilder.getJoinColumns() ); @@ -903,29 +917,26 @@ else if ( isCollection( property ) ) { inferredData, entityBinder, isIdentifierMapper, - context, + buildingContext, inheritanceStatePerClass, property, columnsBuilder.getJoinColumns() ); } //Either a regular property or a basic @Id or @EmbeddedId while not ignoring id annotations - else if ( !propertyBinder.isId() || !entityBinder.isIgnoreIdAnnotations() ) { + else if ( !isId() || !entityBinder.isIgnoreIdAnnotations() ) { // returns overridden columns - return bindBasic( + return bindBasicOrComposite( propertyHolder, nullability, inferredData, entityBinder, isIdentifierMapper, isComponentEmbedded, - context, - inheritanceStatePerClass, property, columnsBuilder, columnsBuilder.getColumns(), - returnedClass, - propertyBinder + returnedClass ); } return columnsBuilder.getColumns(); @@ -954,29 +965,26 @@ private static boolean isCollection(MemberDetails property) { || property.hasDirectAnnotationUsage( ManyToAny.class ); } - private static void bindVersionProperty( + private void bindVersionProperty( PropertyHolder propertyHolder, PropertyData inferredData, boolean isIdentifierMapper, - MetadataBuildingContext context, - Map inheritanceStatePerClass, - AnnotatedColumns columns, - PropertyBinder propertyBinder) { + AnnotatedColumns columns) { checkVersionProperty( propertyHolder, isIdentifierMapper ); final RootClass rootClass = (RootClass) propertyHolder.getPersistentClass(); - propertyBinder.setColumns( columns ); - final Property property = propertyBinder.makePropertyValueAndBind(); - propertyBinder.getBasicValueBinder().setVersion( true ); + setColumns( columns ); + final Property property = makePropertyValueAndBind(); + getBasicValueBinder().setVersion( true ); rootClass.setVersion( property ); //If version is on a mapped superclass, update the mapping final ClassDetails declaringClass = inferredData.getDeclaringClass(); - final org.hibernate.mapping.MappedSuperclass superclass = - getMappedSuperclassOrNull( declaringClass, inheritanceStatePerClass, context ); - if ( superclass != null ) { + final MappedSuperclass mappedSuperclass = + getMappedSuperclassOrNull( declaringClass, inheritanceStatePerClass, buildingContext ); + if ( mappedSuperclass != null ) { // Don't overwrite an existing version property - if ( superclass.getDeclaredVersion() == null ) { - superclass.setDeclaredVersion( property ); + if ( mappedSuperclass.getDeclaredVersion() == null ) { + mappedSuperclass.setDeclaredVersion( property ); } } else { @@ -1003,149 +1011,169 @@ private static void checkVersionProperty(PropertyHolder propertyHolder, boolean } } - private static AnnotatedColumns bindBasic( + private AnnotatedColumns bindBasicOrComposite( PropertyHolder propertyHolder, Nullability nullability, PropertyData inferredData, EntityBinder entityBinder, boolean isIdentifierMapper, boolean isComponentEmbedded, - MetadataBuildingContext context, - Map inheritanceStatePerClass, MemberDetails property, ColumnsBuilder columnsBuilder, AnnotatedColumns columns, - ClassDetails returnedClass, - PropertyBinder propertyBinder) { + ClassDetails returnedClass) { // overrides from @MapsId or @IdClass if needed - final boolean isComposite; - final boolean isOverridden; + final PropertyData overridingProperty = + overridingProperty( propertyHolder, isIdentifierMapper, property ); final AnnotatedColumns actualColumns; + final boolean isComposite; + if ( overridingProperty != null ) { + setReferencedEntityName( overridingProperty.getClassOrElementName() ); + actualColumns = columnsBuilder.overrideColumnFromMapperOrMapsIdProperty( overridingProperty ); + isComposite = isComposite( property, returnedClass, overridingProperty ); + } + else { + actualColumns = columns; + isComposite = isEmbedded( property, returnedClass ); + } + + final PropertyBinder propertyBinder = propertyBinder( + propertyHolder, + nullability, + inferredData, + entityBinder, + isComposite, + isIdentifierMapper, + isComponentEmbedded, + property, + columns, + returnedClass, + actualColumns, + overridingProperty + ); + propertyBinder.handleGenerators( + propertyHolder, + inferredData, + isIdentifierMapper, + overridingProperty != null, + overridingProperty + ); + return actualColumns; + } + + private PropertyData overridingProperty( + PropertyHolder propertyHolder, + boolean isIdentifierMapper, + MemberDetails property) { if ( isIdentifierMapper - || propertyBinder.isId() - || propertyHolder.isOrWithinEmbeddedId() - || propertyHolder.isInIdClass() ) { + || isId + || propertyHolder.isOrWithinEmbeddedId() + || propertyHolder.isInIdClass() ) { // the associated entity could be using an @IdClass making the overridden property a component - final PropertyData overridingProperty = getPropertyOverriddenByMapperOrMapsId( - propertyBinder.isId(), + return getPropertyOverriddenByMapperOrMapsId( + isId, propertyHolder, property.resolveAttributeName(), - context + buildingContext ); - if ( overridingProperty != null ) { - isOverridden = true; - isComposite = isComposite( inheritanceStatePerClass, property, returnedClass, overridingProperty ); - //Get the new column - actualColumns = columnsBuilder.overrideColumnFromMapperOrMapsIdProperty( propertyBinder.isId() ); - } - else { - isOverridden = false; - isComposite = isEmbedded( property, returnedClass ); - actualColumns = columns; - } } else { - isOverridden = false; - isComposite = isEmbedded( property, returnedClass ); - actualColumns = columns; + return null; } + } + + private PropertyBinder propertyBinder( + PropertyHolder propertyHolder, + Nullability nullability, + PropertyData inferredData, + EntityBinder entityBinder, + boolean isComposite, + boolean isIdentifierMapper, + boolean isComponentEmbedded, + MemberDetails property, + AnnotatedColumns columns, + ClassDetails returnedClass, + AnnotatedColumns actualColumns, + PropertyData overridingProperty) { final Class> compositeUserType = - resolveCompositeUserType( inferredData, context ); + resolveCompositeUserType( inferredData, buildingContext ); if ( isComposite || compositeUserType != null ) { if ( property.isArray() && property.getElementType() != null && isEmbedded( property, property.getElementType() ) ) { // This is a special kind of basic aggregate component array type - propertyBinder.setComponentElement( - EmbeddableBinder.bindEmbeddable( - inferredData, - propertyHolder, - entityBinder.getPropertyAccessor( property ), - entityBinder, - isIdentifierMapper, - context, - isComponentEmbedded, - propertyBinder.isId(), - inheritanceStatePerClass, - null, - null, - determineCustomInstantiator( property, returnedClass, context ), - compositeUserType, - null, - columns - ) + aggregateBinder( + propertyHolder, + inferredData, + entityBinder, + isIdentifierMapper, + isComponentEmbedded, + property, + columns, + returnedClass, + compositeUserType, + actualColumns ); - propertyBinder.setColumns( actualColumns ); - propertyBinder.makePropertyValueAndBind(); } else { - propertyBinder = createCompositeBinder( + return createCompositeBinder( propertyHolder, inferredData, entityBinder, isIdentifierMapper, isComponentEmbedded, - context, + buildingContext, inheritanceStatePerClass, property, actualColumns, returnedClass, - propertyBinder, - isOverridden, + isId(), + overridingProperty != null, + overridingProperty, compositeUserType ); } } - else if ( property.isPlural() - && property.getElementType() != null - && isEmbedded( property, property.getElementType() ) ) { + else if ( property.isPlural() && property.getElementType() != null + && isEmbedded( property, property.getElementType() ) ) { // This is a special kind of basic aggregate component array type - propertyBinder.setComponentElement( - EmbeddableBinder.bindEmbeddable( - inferredData, - propertyHolder, - entityBinder.getPropertyAccessor( property ), - entityBinder, - isIdentifierMapper, - context, - isComponentEmbedded, - propertyBinder.isId(), - inheritanceStatePerClass, - null, - null, - determineCustomInstantiator( property, property.getElementType().determineRawClass(), context ), - compositeUserType, - null, - columns - ) + aggregateBinder( + propertyHolder, + inferredData, + entityBinder, + isIdentifierMapper, + isComponentEmbedded, + property, + columns, + property.getElementType().determineRawClass(), + null, + actualColumns ); - propertyBinder.setColumns( actualColumns ); - propertyBinder.makePropertyValueAndBind(); } else { - createBasicBinder( + basicBinder( propertyHolder, inferredData, nullability, - context, property, - actualColumns, - propertyBinder, - isOverridden + actualColumns ); } + return this; + } + + private void handleGenerators( + PropertyHolder propertyHolder, + PropertyData inferredData, + boolean isIdentifierMapper, + boolean isOverridden, + PropertyData overridingProperty) { if ( isOverridden ) { - handleGeneratorsForOverriddenId( - propertyHolder, -// classGenerators, - context, - property, - propertyBinder - ); + handleGeneratorsForOverriddenId( propertyHolder, overridingProperty ); } - else if ( propertyBinder.isId() ) { + else if ( isId() ) { if ( isIdentifierMapper ) { throw new AnnotationException( "Property '"+ getPath( propertyHolder, inferredData ) + "' belongs to an '@IdClass' and may not be annotated '@Id' or '@EmbeddedId'" ); @@ -1154,15 +1182,45 @@ else if ( propertyBinder.isId() ) { createIdGeneratorsFromGeneratorAnnotations( propertyHolder, inferredData, - (SimpleValue) propertyBinder.getValue(), - context + (SimpleValue) getValue(), + buildingContext ); } - return actualColumns; } - private static boolean isComposite( - Map inheritanceStatePerClass, + private void aggregateBinder( + PropertyHolder propertyHolder, + PropertyData inferredData, + EntityBinder entityBinder, + boolean isIdentifierMapper, + boolean isComponentEmbedded, + MemberDetails property, + AnnotatedColumns columns, + ClassDetails returnedClass, + Class> compositeUserType, + AnnotatedColumns actualColumns) { + // This is a special kind of basic aggregate component array type + setComponentElement( + bindEmbeddable( + inferredData, + propertyHolder, + entityBinder.getPropertyAccessor( property ), + entityBinder, + isIdentifierMapper, + buildingContext, + isComponentEmbedded, + isId(), + inheritanceStatePerClass, + determineCustomInstantiator( property, returnedClass, buildingContext ), + compositeUserType, + columns + ) + ); + setColumns( actualColumns ); + makePropertyValueAndBind(); + } + + private boolean isComposite( MemberDetails property, ClassDetails returnedClass, PropertyData overridingProperty) { @@ -1171,19 +1229,10 @@ private static boolean isComposite( return state != null ? state.hasIdClassOrEmbeddedId() : isEmbedded( property, returnedClass ); } - private static void handleGeneratorsForOverriddenId( + private void handleGeneratorsForOverriddenId( PropertyHolder propertyHolder, -// Map classGenerators, - MetadataBuildingContext context, - MemberDetails property, - PropertyBinder propertyBinder) { - final PropertyData mapsIdProperty = getPropertyOverriddenByMapperOrMapsId( - propertyBinder.isId(), - propertyHolder, - property.resolveAttributeName(), - context - ); - final SimpleValue idValue = (SimpleValue) propertyBinder.getValue(); + PropertyData mapsIdProperty) { + final SimpleValue idValue = (SimpleValue) getValue(); final RootClass rootClass = propertyHolder.getPersistentClass().getRootClass(); final String propertyName = mapsIdProperty.getPropertyName(); final String entityName = rootClass.getEntityName(); @@ -1209,41 +1258,27 @@ public boolean allowAssignedIdentifiers() { ); } - private static void createBasicBinder( + private void basicBinder( PropertyHolder propertyHolder, PropertyData inferredData, Nullability nullability, - MetadataBuildingContext context, MemberDetails property, - AnnotatedColumns columns, - PropertyBinder propertyBinder, - boolean isOverridden) { - if ( shouldForceNotNull( nullability, propertyBinder, isExplicitlyOptional( property ) ) ) { - forceColumnsNotNull( propertyHolder, inferredData, columns, propertyBinder ); + AnnotatedColumns columns) { + if ( shouldForceNotNull( nullability, isExplicitlyOptional( property ) ) ) { + forceColumnsNotNull( propertyHolder, inferredData, columns ); } - propertyBinder.setLazy( isLazy( property ) ); - propertyBinder.setColumns( columns ); - if ( isOverridden ) { - final PropertyData mapsIdProperty = getPropertyOverriddenByMapperOrMapsId( - propertyBinder.isId(), - propertyHolder, - property.resolveAttributeName(), - context - ); - propertyBinder.setReferencedEntityName( mapsIdProperty.getClassOrElementName() ); - } - - propertyBinder.makePropertyValueAndBind(); + setLazy( isLazy( property ) ); + setColumns( columns ); + makePropertyValueAndBind(); } - private static void forceColumnsNotNull( + private void forceColumnsNotNull( PropertyHolder holder, PropertyData data, - AnnotatedColumns columns, - PropertyBinder binder) { + AnnotatedColumns columns) { for ( AnnotatedColumn column : columns.getColumns() ) { - if ( binder.isId() && column.isFormula() ) { + if ( isId() && column.isFormula() ) { throw new CannotForceNonNullableException( "Identifier property '" + getPath( holder, data ) + "' cannot map to a '@Formula'" ); } @@ -1251,8 +1286,8 @@ private static void forceColumnsNotNull( } } - private static boolean shouldForceNotNull(Nullability nullability, PropertyBinder binder, boolean optional) { - return binder.isId() + private boolean shouldForceNotNull(Nullability nullability, boolean optional) { + return isId() || !optional && nullability != Nullability.FORCED_NULL; } @@ -1331,4 +1366,34 @@ private static Class> resolveCompositeUserType( return null; } + + private static PropertyData getPropertyOverriddenByMapperOrMapsId( + boolean isId, + PropertyHolder propertyHolder, + String propertyName, + MetadataBuildingContext buildingContext) { + final ClassDetailsRegistry classDetailsRegistry = + buildingContext.getMetadataCollector().getSourceModelBuildingContext() + .getClassDetailsRegistry(); + final PersistentClass persistentClass = propertyHolder.getPersistentClass(); + final String name = + StringHelper.isEmpty( persistentClass.getClassName() ) + ? persistentClass.getEntityName() + : persistentClass.getClassName(); + final ClassDetails classDetails = classDetailsRegistry.resolveClassDetails( name ); + final InFlightMetadataCollector metadataCollector = buildingContext.getMetadataCollector(); + if ( propertyHolder.isInIdClass() ) { + final PropertyData data = + metadataCollector.getPropertyAnnotatedWithIdAndToOne( classDetails, propertyName ); + if ( data != null ) { + return data; + } + // TODO: is this branch even necessary? + else if ( buildingContext.getBuildingOptions().isSpecjProprietarySyntaxEnabled() ) { + return metadataCollector.getPropertyAnnotatedWithMapsId( classDetails, propertyName ); + } + } + return metadataCollector.getPropertyAnnotatedWithMapsId( classDetails, isId ? "" : propertyName ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/SimpleToOneFkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/SimpleToOneFkSecondPass.java deleted file mode 100644 index 634b774897c7..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/SimpleToOneFkSecondPass.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.boot.model.internal; - -import org.hibernate.MappingException; -import org.hibernate.mapping.PersistentClass; -import org.hibernate.mapping.ToOne; - -/** - * A simple second pass that just creates the foreign key - * - * @author Christian Beikov - */ -public class SimpleToOneFkSecondPass extends FkSecondPass { - - public SimpleToOneFkSecondPass(ToOne value) { - super( value, null ); - } - - @Override - public String getReferencedEntityName() { - return ( (ToOne) value ).getReferencedEntityName(); - } - - @Override - public boolean isInPrimaryKey() { - return false; - } - - @Override - public void doSecondPass(java.util.Map persistentClasses) throws MappingException { - value.createForeignKey(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneFkSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneFkSecondPass.java index 4a0d350c829f..63b4d2d3ef8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneFkSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneFkSecondPass.java @@ -14,6 +14,7 @@ import org.hibernate.mapping.OneToOne; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.ToOne; import static org.hibernate.boot.model.internal.BinderHelper.createSyntheticPropertyReference; @@ -26,15 +27,17 @@ * * @author Emmanuel Bernard */ -public class ToOneFkSecondPass extends FkSecondPass { +class ToOneFkSecondPass implements FkSecondPass { private final PersistentClass persistentClass; private final MetadataBuildingContext buildingContext; private final boolean unique; private final String path; private final String entityClassName; private final boolean annotatedEntity; + private final ToOne value; + private final AnnotatedJoinColumns columns; - public ToOneFkSecondPass( + ToOneFkSecondPass( ToOne value, AnnotatedJoinColumns columns, boolean unique, @@ -42,7 +45,8 @@ public ToOneFkSecondPass( PersistentClass persistentClass, String path, MetadataBuildingContext buildingContext) { - super( value, columns ); + this.value = value; + this.columns = columns; this.persistentClass = persistentClass; this.buildingContext = buildingContext; this.unique = unique; @@ -51,9 +55,14 @@ public ToOneFkSecondPass( this.annotatedEntity = annotatedEntity; } + @Override + public SimpleValue getValue() { + return value; + } + @Override public String getReferencedEntityName() { - return ( (ToOne) value ).getReferencedEntityName(); + return value.getReferencedEntityName(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index 17de0741647a..6ff58c421e3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -27,7 +27,6 @@ import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; import org.hibernate.boot.model.internal.FkSecondPass; -import org.hibernate.boot.model.internal.SimpleToOneFkSecondPass; import org.hibernate.boot.model.naming.EntityNaming; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.ImplicitBasicColumnNameSource; @@ -1841,17 +1840,12 @@ private Property createOneToOneAttribute( // Defer the creation of the foreign key as we need the associated entity persister to be initialized // so that we can observe the properties/columns of a possible component in the correct order - metadataBuildingContext.getMetadataCollector().addSecondPass( new SimpleToOneFkSecondPass( oneToOneBinding ) ); + metadataBuildingContext.getMetadataCollector().addSecondPass( new OneToOneFkSecondPass( oneToOneBinding ) ); - Property prop = new Property(); - prop.setValue( oneToOneBinding ); - bindProperty( - sourceDocument, - oneToOneSource, - prop - ); - - return prop; + final Property property = new Property(); + property.setValue( oneToOneBinding ); + bindProperty( sourceDocument, oneToOneSource, property ); + return property; } private void handlePropertyReference( @@ -3003,7 +2997,6 @@ public void process(InFlightMetadataCollector metadataCollector) { } } - private abstract class AbstractPluralAttributeSecondPass implements SecondPass { private final MappingDocument mappingDocument; private final PluralAttributeSource pluralAttributeSource; @@ -3935,21 +3928,18 @@ public MetadataBuildingContext getBuildingContext() { } } - private static class ManyToOneFkSecondPass extends FkSecondPass { + private static class ManyToOneFkSecondPass implements FkSecondPass { private final MappingDocument mappingDocument; private final ManyToOne manyToOneBinding; private final String referencedEntityName; private final String referencedEntityAttributeName; - - public ManyToOneFkSecondPass( + private ManyToOneFkSecondPass( MappingDocument mappingDocument, SingularAttributeSourceManyToOne manyToOneSource, ManyToOne manyToOneBinding, String referencedEntityName) { - super( manyToOneBinding, null ); - if ( referencedEntityName == null ) { throw new MappingException( "entity name referenced by many-to-one required [" + manyToOneSource.getAttributeRole().getFullPath() + "]", @@ -3959,10 +3949,14 @@ public ManyToOneFkSecondPass( this.mappingDocument = mappingDocument; this.manyToOneBinding = manyToOneBinding; this.referencedEntityName = referencedEntityName; - this.referencedEntityAttributeName = manyToOneSource.getReferencedEntityAttributeName(); } + @Override + public Value getValue() { + return manyToOneBinding; + } + @Override public String getReferencedEntityName() { return referencedEntityName; @@ -3996,6 +3990,34 @@ public boolean canProcessImmediately() { } } + private static class OneToOneFkSecondPass implements FkSecondPass { + private final OneToOne oneToOneBinding; + + private OneToOneFkSecondPass(OneToOne oneToOneBinding) { + this.oneToOneBinding = oneToOneBinding; + } + + @Override + public Value getValue() { + return oneToOneBinding; + } + + @Override + public String getReferencedEntityName() { + return oneToOneBinding.getReferencedEntityName(); + } + + @Override + public boolean isInPrimaryKey() { + return false; + } + + @Override + public void doSecondPass(Map persistentClasses) { + oneToOneBinding.createForeignKey(); + } + } + private static class NaturalIdUniqueKeyBinderImpl implements NaturalIdUniqueKeyBinder { private final MappingDocument mappingDocument; private final PersistentClass entityBinding;