HHH-18840 validate collection mapping annotations
and fix tests which had ignored such annotations
This commit is contained in:
parent
ec4310a6de
commit
192058f2f6
|
@ -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<String, PersistentClass> persistentClasses) {
|
||||
final List<? extends Annotation> metaAnnotatedTargets = memberDetails.getMetaAnnotated(
|
||||
AttributeBinderType.class,
|
||||
getSourceModelContext()
|
||||
);
|
||||
final List<? extends Annotation> 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<? extends Annotation> 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<? extends Annotation> 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<? extends EmbeddableInstantiator> 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<? extends Annotation> 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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<UserConfEntity> confs = new HashSet<UserConfEntity>();
|
||||
|
||||
|
|
|
@ -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<SettlementExtension> extensions = new HashSet<>();
|
||||
|
||||
private transient Map<Class<?>, SettlementExtension> extensionMap;
|
||||
|
|
Loading…
Reference in New Issue