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 beb1feb426..694079b14c 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 @@ -6,10 +6,20 @@ package org.hibernate.boot.model.internal; import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandles; +import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.Map; +import jakarta.persistence.MapKey; +import jakarta.persistence.MapKeyClass; +import jakarta.persistence.MapKeyColumn; +import jakarta.persistence.MapKeyEnumerated; +import jakarta.persistence.MapKeyJoinColumn; +import jakarta.persistence.MapKeyJoinColumns; +import jakarta.persistence.MapKeyTemporal; +import jakarta.persistence.OrderBy; +import jakarta.persistence.OrderColumn; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.MappingException; @@ -34,8 +44,6 @@ import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.EventType; import org.hibernate.generator.EventTypeSets; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.mapping.Collection; import org.hibernate.mapping.Component; import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; @@ -49,6 +57,7 @@ import org.hibernate.mapping.Value; import org.hibernate.metamodel.spi.EmbeddableInstantiator; import org.hibernate.models.spi.AnnotationDescriptor; import org.hibernate.models.spi.AnnotationDescriptorRegistry; +import org.hibernate.models.spi.ArrayTypeDetails; import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.MemberDetails; import org.hibernate.models.spi.SourceModelBuildingContext; @@ -93,6 +102,7 @@ import static org.hibernate.boot.model.internal.ToOneBinder.bindManyToOne; import static org.hibernate.boot.model.internal.ToOneBinder.bindOneToOne; import static org.hibernate.id.IdentifierGeneratorHelper.getForeignId; import static org.hibernate.internal.util.StringHelper.qualify; +import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty; /** * A stateful binder responsible for creating {@link Property} objects. @@ -275,29 +285,29 @@ public class PropertyBinder { @SuppressWarnings({"rawtypes", "unchecked"}) private void callAttributeBinders(Property property, Map persistentClasses) { - final List metaAnnotatedTargets = memberDetails.getMetaAnnotated( - AttributeBinderType.class, - getSourceModelContext() - ); + final List metaAnnotatedTargets = + memberDetails.getMetaAnnotated( AttributeBinderType.class, getSourceModelContext() ); - if ( CollectionHelper.isEmpty( metaAnnotatedTargets ) ) { - return; - } - - final AnnotationDescriptorRegistry descriptorRegistry = getSourceModelContext().getAnnotationDescriptorRegistry(); - for ( int i = 0; i < metaAnnotatedTargets.size(); i++ ) { - final Annotation metaAnnotatedTarget = metaAnnotatedTargets.get( i ); - final AnnotationDescriptor metaAnnotatedDescriptor = descriptorRegistry.getDescriptor( metaAnnotatedTarget.annotationType() ); - final AttributeBinderType binderTypeAnn = metaAnnotatedDescriptor.getDirectAnnotationUsage( AttributeBinderType.class ); - try { - final AttributeBinder binder = binderTypeAnn.binder().getConstructor().newInstance(); - final PersistentClass persistentClass = entityBinder != null - ? entityBinder.getPersistentClass() - : persistentClasses.get( holder.getEntityName() ); - binder.bind( metaAnnotatedTarget, buildingContext, persistentClass, property ); - } - catch ( Exception e ) { - throw new AnnotationException( "error processing @AttributeBinderType annotation '" + metaAnnotatedDescriptor.getAnnotationType().getName() + "'", e ); + if ( !isEmpty( metaAnnotatedTargets ) ) { + final AnnotationDescriptorRegistry descriptorRegistry = + getSourceModelContext().getAnnotationDescriptorRegistry(); + for ( int i = 0; i < metaAnnotatedTargets.size(); i++ ) { + final Annotation metaAnnotatedTarget = metaAnnotatedTargets.get( i ); + final AnnotationDescriptor metaAnnotatedDescriptor = + descriptorRegistry.getDescriptor( metaAnnotatedTarget.annotationType() ); + final AttributeBinderType binderTypeAnn = + metaAnnotatedDescriptor.getDirectAnnotationUsage( AttributeBinderType.class ); + try { + final AttributeBinder binder = binderTypeAnn.binder().getConstructor().newInstance(); + final PersistentClass persistentClass = entityBinder != null + ? entityBinder.getPersistentClass() + : persistentClasses.get( holder.getEntityName() ); + binder.bind( metaAnnotatedTarget, buildingContext, persistentClass, property ); + } + catch ( Exception e ) { + throw new AnnotationException( "error processing @AttributeBinderType annotation '" + + metaAnnotatedDescriptor.getAnnotationType().getName() + "'", e ); + } } } } @@ -398,12 +408,14 @@ public class PropertyBinder { private Class resolveCustomInstantiator( MemberDetails property, ClassDetails embeddableClass) { - final org.hibernate.annotations.EmbeddableInstantiator onEmbedded = property.getDirectAnnotationUsage( org.hibernate.annotations.EmbeddableInstantiator.class ); + final org.hibernate.annotations.EmbeddableInstantiator onEmbedded = + property.getDirectAnnotationUsage( org.hibernate.annotations.EmbeddableInstantiator.class ); if ( onEmbedded != null ) { return onEmbedded.value(); } - final org.hibernate.annotations.EmbeddableInstantiator onEmbeddable = embeddableClass.getDirectAnnotationUsage( org.hibernate.annotations.EmbeddableInstantiator.class ); + final org.hibernate.annotations.EmbeddableInstantiator onEmbeddable = + embeddableClass.getDirectAnnotationUsage( org.hibernate.annotations.EmbeddableInstantiator.class ); if ( onEmbeddable != null ) { return onEmbeddable.value(); } @@ -414,6 +426,7 @@ public class PropertyBinder { //used when the value is provided and the binding is done elsewhere public Property makeProperty() { validateMake(); + validateAnnotationsAgainstType(); LOG.debugf( "Building property %s", name ); Property property = new Property(); property.setName( name ); @@ -505,7 +518,7 @@ public class PropertyBinder { private void inferOptimisticLocking(Property property) { // this is already handled for collections in CollectionBinder... - if ( value instanceof Collection collection ) { + if ( value instanceof org.hibernate.mapping.Collection collection ) { property.setOptimisticLocked( collection.isOptimisticLocked() ); } else if ( memberDetails != null && memberDetails.hasDirectAnnotationUsage( OptimisticLock.class ) ) { @@ -519,6 +532,37 @@ public class PropertyBinder { } } + private void validateAnnotationsAgainstType() { + if ( memberDetails != null ) { + if ( !(memberDetails.getType() instanceof ArrayTypeDetails) ) { + checkAnnotation( OrderColumn.class, List.class ); + if ( memberDetails.hasDirectAnnotationUsage( OrderBy.class ) + && !memberDetails.getType().isImplementor( Collection.class ) + && !memberDetails.getType().isImplementor( Map.class ) ) { + throw new AnnotationException( "Property '" + qualify( holder.getPath(), name ) + + "' is annotated '@OrderBy' but is not of type 'Collection' or 'Map'" ); + } + } + checkAnnotation( MapKey.class, Map.class ); + checkAnnotation( MapKeyColumn.class, Map.class ); + checkAnnotation( MapKeyClass.class, Map.class ); + checkAnnotation( MapKeyEnumerated.class, Map.class ); + checkAnnotation( MapKeyTemporal.class, Map.class ); + checkAnnotation( MapKeyColumn.class, Map.class ); + checkAnnotation( MapKeyJoinColumn.class, Map.class ); + checkAnnotation( MapKeyJoinColumns.class, Map.class ); + } + } + + private void checkAnnotation(Class annotationClass, Class propertyType) { + if ( memberDetails.hasDirectAnnotationUsage( annotationClass ) + && !memberDetails.getType().isImplementor( propertyType ) ) { + throw new AnnotationException( "Property '" + qualify( holder.getPath(), name ) + + "' is annotated '@" + annotationClass.getSimpleName() + + "' but is not of type '" + propertyType.getTypeName() + "'" ); + } + } + private void validateOptimisticLock(boolean excluded) { if ( excluded ) { if ( memberDetails.hasDirectAnnotationUsage( Version.class ) ) { @@ -587,7 +631,8 @@ public class PropertyBinder { inFlightPropertyDataList.add( 0, propertyAnnotatedElement ); handleIdProperty( propertyContainer, context, declaringClass, ownerType, element ); if ( hasToOneAnnotation( element ) ) { - context.getMetadataCollector().addToOneAndIdProperty( ownerType.determineRawClass(), propertyAnnotatedElement ); + context.getMetadataCollector() + .addToOneAndIdProperty( ownerType.determineRawClass(), propertyAnnotatedElement ); } idPropertyCounter++; } @@ -595,7 +640,8 @@ public class PropertyBinder { inFlightPropertyDataList.add( propertyAnnotatedElement ); } if ( element.hasDirectAnnotationUsage( MapsId.class ) ) { - context.getMetadataCollector().addPropertyAnnotatedWithMapsId( ownerType.determineRawClass(), propertyAnnotatedElement ); + context.getMetadataCollector() + .addPropertyAnnotatedWithMapsId( ownerType.determineRawClass(), propertyAnnotatedElement ); } return idPropertyCounter; @@ -603,13 +649,14 @@ public class PropertyBinder { private static void checkIdProperty(MemberDetails property, PropertyData propertyData) { final Id incomingIdProperty = property.getDirectAnnotationUsage( Id.class ); - final Id existingIdProperty = propertyData.getAttributeMember().getDirectAnnotationUsage( Id.class ); + final MemberDetails attributeMember = propertyData.getAttributeMember(); + final Id existingIdProperty = attributeMember.getDirectAnnotationUsage( Id.class ); if ( incomingIdProperty != null && existingIdProperty == null ) { throw new MappingException( String.format( "You cannot override the [%s] non-identifier property from the [%s] base class or @MappedSuperclass and make it an identifier in the [%s] subclass", - propertyData.getAttributeMember().getName(), - propertyData.getAttributeMember().getDeclaringType().getName(), + attributeMember.getName(), + attributeMember.getDeclaringType().getName(), property.getDeclaringType().getName() ) ); @@ -631,7 +678,8 @@ public class PropertyBinder { if ( element.hasDirectAnnotationUsage( Id.class ) && element.hasDirectAnnotationUsage( Column.class ) ) { final String columnName = element.getDirectAnnotationUsage( Column.class ).name(); declaringClass.forEachField( (index, fieldDetails) -> { - if ( !element.hasDirectAnnotationUsage( MapsId.class ) && isJoinColumnPresent( columnName, element, sourceModelContext ) ) { + if ( !element.hasDirectAnnotationUsage( MapsId.class ) + && isJoinColumnPresent( columnName, element, sourceModelContext ) ) { //create a PropertyData for the specJ property holding the mapping context.getMetadataCollector().addPropertyAnnotatedWithMapsIdSpecj( ownerType.determineRawClass(), @@ -1157,7 +1205,8 @@ public class PropertyBinder { MemberDetails property, ClassDetails returnedClass, PropertyData overridingProperty) { - final InheritanceState state = inheritanceStatePerClass.get( overridingProperty.getClassOrElementType().determineRawClass() ); + final InheritanceState state = + inheritanceStatePerClass.get( overridingProperty.getClassOrElementType().determineRawClass() ); return state != null ? state.hasIdClassOrEmbeddedId() : isEmbedded( property, returnedClass ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/ManyToManyMaxFetchDepth0Test.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/ManyToManyMaxFetchDepth0Test.java index 4974a0ac69..14f7b472a3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/ManyToManyMaxFetchDepth0Test.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/ManyToManyMaxFetchDepth0Test.java @@ -12,7 +12,6 @@ import org.hibernate.cfg.Environment; * * @author Gail Badner */ -@SuppressWarnings("unchecked") public class ManyToManyMaxFetchDepth0Test extends ManyToManyTest { @Override protected void configure(Configuration cfg) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/UserEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/UserEntity.java index af488f8f74..3d7699b8a6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/UserEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/UserEntity.java @@ -16,7 +16,6 @@ import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderColumn; import jakarta.persistence.Table; @Entity @@ -30,7 +29,6 @@ public class UserEntity implements Serializable{ @Column(name = "user_id") private Long id; - @OrderColumn(name = "cnf_order") @OneToMany(mappedBy="user", fetch = EAGER, cascade = ALL, orphanRemoval = true) private Set confs = new HashSet(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hhh12076/AnnotationMappingJoinClassTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hhh12076/AnnotationMappingJoinClassTest.java index b31c4d9114..50c3e1f407 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hhh12076/AnnotationMappingJoinClassTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hhh12076/AnnotationMappingJoinClassTest.java @@ -35,7 +35,6 @@ import jakarta.persistence.InheritanceType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderColumn; import jakarta.persistence.Table; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; @@ -720,7 +719,6 @@ public class AnnotationMappingJoinClassTest { private SettlementStatus status = SettlementStatus.RESERVED; @OneToMany(mappedBy = "settlement", cascade = CascadeType.ALL, orphanRemoval = true) - @OrderColumn(name = "orderindex") private Set extensions = new HashSet<>(); private transient Map, SettlementExtension> extensionMap;