improve error messages when association targets non-entity type
This commit is contained in:
parent
34374c0c70
commit
e4632107d7
|
@ -44,7 +44,6 @@ import org.hibernate.boot.spi.MetadataBuildingContext;
|
|||
import org.hibernate.boot.spi.PropertyData;
|
||||
import org.hibernate.dialect.DatabaseVersion;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.mapping.Any;
|
||||
import org.hibernate.mapping.AttributeContainer;
|
||||
import org.hibernate.mapping.BasicValue;
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.util.Map;
|
|||
import java.util.Properties;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import org.hibernate.AnnotationException;
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.FetchMode;
|
||||
|
@ -97,7 +98,6 @@ import org.hibernate.internal.CoreMessageLogger;
|
|||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.mapping.Any;
|
||||
import org.hibernate.mapping.Backref;
|
||||
import org.hibernate.mapping.BasicValue;
|
||||
import org.hibernate.mapping.CheckConstraint;
|
||||
import org.hibernate.mapping.Collection;
|
||||
import org.hibernate.mapping.Column;
|
||||
|
@ -2556,8 +2556,8 @@ public abstract class CollectionBinder {
|
|||
boolean isCollectionOfEntities) {
|
||||
|
||||
if ( !isCollectionOfEntities) {
|
||||
throw new AnnotationException( "Association '" + safeCollectionRole()
|
||||
+ "' targets the type '" + elementType.getName() + "' which is not an '@Entity' type" );
|
||||
throw new AnnotationException( "Association '" + safeCollectionRole() + "'"
|
||||
+ targetEntityMessage( elementType ) );
|
||||
}
|
||||
|
||||
joinColumns.setManyToManyOwnerSideEntityName( collectionEntity.getEntityName() );
|
||||
|
@ -2594,8 +2594,8 @@ public abstract class CollectionBinder {
|
|||
|
||||
if ( !isCollectionOfEntities) {
|
||||
if ( property.isAnnotationPresent( ManyToMany.class ) || property.isAnnotationPresent( OneToMany.class ) ) {
|
||||
throw new AnnotationException( "Association '" + safeCollectionRole()
|
||||
+ "' targets the type '" + elementType.getName() + "' which is not an '@Entity' type" );
|
||||
throw new AnnotationException( "Association '" + safeCollectionRole() + "'"
|
||||
+ targetEntityMessage( elementType ) );
|
||||
}
|
||||
else if (isManyToAny) {
|
||||
if ( propertyHolder.getJoinTable( property ) == null ) {
|
||||
|
@ -2607,13 +2607,20 @@ public abstract class CollectionBinder {
|
|||
final JoinTable joinTableAnn = propertyHolder.getJoinTable( property );
|
||||
if ( joinTableAnn != null && joinTableAnn.inverseJoinColumns().length > 0 ) {
|
||||
throw new AnnotationException( "Association '" + safeCollectionRole()
|
||||
+ " has a '@JoinTable' with 'inverseJoinColumns' and targets the type '"
|
||||
+ elementType.getName() + "' which is not an '@Entity' type" );
|
||||
+ " has a '@JoinTable' with 'inverseJoinColumns' and"
|
||||
+ targetEntityMessage( elementType ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String targetEntityMessage(XClass elementType) {
|
||||
final String problem = elementType.isAnnotationPresent( Entity.class )
|
||||
? " which does not belong to the same persistence unit"
|
||||
: " which is not an '@Entity' type";
|
||||
return " targets the type '" + elementType.getName() + "'" + problem;
|
||||
}
|
||||
|
||||
private Class<? extends EmbeddableInstantiator> resolveCustomInstantiator(
|
||||
XProperty property,
|
||||
XClass propertyClass,
|
||||
|
|
|
@ -246,8 +246,8 @@ public class MapBinder extends CollectionBinder {
|
|||
String mapKeyPropertyName) {
|
||||
final PersistentClass associatedClass = persistentClasses.get( elementType.getName() );
|
||||
if ( associatedClass == null ) {
|
||||
throw new AnnotationException( "Association '" + safeCollectionRole()
|
||||
+ "' targets the type '" + elementType.getName() + "' which is not an '@Entity' type" );
|
||||
throw new AnnotationException( "Association '" + safeCollectionRole() + "'"
|
||||
+ targetEntityMessage( elementType ) );
|
||||
}
|
||||
final Property mapProperty = findPropertyByName( associatedClass, mapKeyPropertyName );
|
||||
if ( mapProperty == null ) {
|
||||
|
|
|
@ -13,7 +13,6 @@ import org.hibernate.MappingException;
|
|||
import org.hibernate.annotations.LazyGroup;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
import org.hibernate.annotations.OnDeleteAction;
|
||||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
import org.hibernate.annotations.common.reflection.XProperty;
|
||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||
import org.hibernate.boot.spi.PropertyData;
|
||||
|
@ -37,7 +36,6 @@ import static org.hibernate.boot.model.internal.BinderHelper.findPropertyByName;
|
|||
import static org.hibernate.boot.model.internal.BinderHelper.getPath;
|
||||
import static org.hibernate.boot.model.internal.ToOneBinder.bindForeignKeyNameAndDefinition;
|
||||
import static org.hibernate.boot.model.internal.ToOneBinder.defineFetchingStrategy;
|
||||
import static org.hibernate.boot.model.internal.ToOneBinder.getReferenceEntityName;
|
||||
import static org.hibernate.internal.util.StringHelper.qualify;
|
||||
import static org.hibernate.type.ForeignKeyDirection.FROM_PARENT;
|
||||
import static org.hibernate.type.ForeignKeyDirection.TO_PARENT;
|
||||
|
@ -53,19 +51,20 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
private final PropertyHolder propertyHolder;
|
||||
private final NotFoundAction notFoundAction;
|
||||
private final PropertyData inferredData;
|
||||
private final XClass targetEntity;
|
||||
private final OnDeleteAction onDeleteAction;
|
||||
private final boolean optional;
|
||||
private final String cascadeStrategy;
|
||||
private final AnnotatedJoinColumns joinColumns;
|
||||
private final String referencedEntityName;
|
||||
private final boolean annotatedEntity;
|
||||
|
||||
public OneToOneSecondPass(
|
||||
String mappedBy,
|
||||
String ownerEntity,
|
||||
@SuppressWarnings("unused") String ownerProperty,
|
||||
PropertyHolder propertyHolder,
|
||||
PropertyData inferredData,
|
||||
XClass targetEntity,
|
||||
String referencedEntityName,
|
||||
boolean annotatedEntity,
|
||||
NotFoundAction notFoundAction,
|
||||
OnDeleteAction onDeleteAction,
|
||||
boolean optional,
|
||||
|
@ -75,10 +74,11 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
this.ownerEntity = ownerEntity;
|
||||
this.mappedBy = mappedBy;
|
||||
this.propertyHolder = propertyHolder;
|
||||
this.referencedEntityName = referencedEntityName;
|
||||
this.buildingContext = buildingContext;
|
||||
this.notFoundAction = notFoundAction;
|
||||
this.inferredData = inferredData;
|
||||
this.targetEntity = targetEntity;
|
||||
this.annotatedEntity = annotatedEntity;
|
||||
this.onDeleteAction = onDeleteAction;
|
||||
this.optional = optional;
|
||||
this.cascadeStrategy = cascadeStrategy;
|
||||
|
@ -94,7 +94,6 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
);
|
||||
final String propertyName = inferredData.getPropertyName();
|
||||
value.setPropertyName( propertyName );
|
||||
final String referencedEntityName = getReferenceEntityName( inferredData, targetEntity, buildingContext );
|
||||
value.setReferencedEntityName( referencedEntityName );
|
||||
XProperty property = inferredData.getProperty();
|
||||
defineFetchingStrategy( value, property, inferredData, propertyHolder );
|
||||
|
@ -136,10 +135,14 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
|
||||
private void bindUnowned(Map<String, PersistentClass> persistentClasses, OneToOne oneToOne, Property property) {
|
||||
oneToOne.setMappedByProperty( mappedBy );
|
||||
final PersistentClass targetEntity = persistentClasses.get( oneToOne.getReferencedEntityName() );
|
||||
final String targetEntityName = oneToOne.getReferencedEntityName();
|
||||
final PersistentClass targetEntity = persistentClasses.get( targetEntityName );
|
||||
if ( targetEntity == null ) {
|
||||
final String problem = annotatedEntity
|
||||
? " which does not belong to the same persistence unit"
|
||||
: " which is not an '@Entity' type";
|
||||
throw new MappingException( "Association '" + getPath( propertyHolder, inferredData )
|
||||
+ "' targets unknown entity type '" + oneToOne.getReferencedEntityName() + "'" );
|
||||
+ "' targets the type '" + targetEntityName + "'" + problem );
|
||||
}
|
||||
final Property targetProperty = targetProperty( oneToOne, targetEntity );
|
||||
if ( targetProperty.getValue() instanceof OneToOne ) {
|
||||
|
@ -151,7 +154,7 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
else {
|
||||
throw new AnnotationException( "Association '" + getPath( propertyHolder, inferredData )
|
||||
+ "' is 'mappedBy' a property named '" + mappedBy
|
||||
+ "' of the target entity type '" + oneToOne.getReferencedEntityName()
|
||||
+ "' of the target entity type '" + targetEntityName
|
||||
+ "' which is not a '@OneToOne' or '@ManyToOne' association" );
|
||||
}
|
||||
checkMappedByType(
|
||||
|
@ -235,13 +238,18 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
+ "' which does not exist in the target entity type '" + oneToOne.getReferencedEntityName() + "'" );
|
||||
}
|
||||
|
||||
private void bindOwned(Map<String, PersistentClass> persistentClasses, OneToOne oneToOne, String propertyName, Property property) {
|
||||
private void bindOwned(
|
||||
Map<String, PersistentClass> persistentClasses,
|
||||
OneToOne oneToOne,
|
||||
String propertyName,
|
||||
Property property) {
|
||||
final ToOneFkSecondPass secondPass = new ToOneFkSecondPass(
|
||||
oneToOne,
|
||||
joinColumns,
|
||||
true,
|
||||
annotatedEntity,
|
||||
propertyHolder.getPersistentClass(),
|
||||
qualify( propertyHolder.getPath(), propertyName),
|
||||
qualify( propertyHolder.getPath(), propertyName ),
|
||||
buildingContext
|
||||
);
|
||||
secondPass.doSecondPass(persistentClasses);
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.boot.model.internal;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import org.hibernate.AnnotationException;
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.FetchMode;
|
||||
|
@ -36,7 +37,6 @@ import org.hibernate.mapping.SimpleValue;
|
|||
import org.hibernate.mapping.ToOne;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ConstraintMode;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.Id;
|
||||
|
@ -49,8 +49,6 @@ import jakarta.persistence.OneToOne;
|
|||
import jakarta.persistence.PrimaryKeyJoinColumn;
|
||||
import jakarta.persistence.PrimaryKeyJoinColumns;
|
||||
|
||||
import static jakarta.persistence.ConstraintMode.NO_CONSTRAINT;
|
||||
import static jakarta.persistence.ConstraintMode.PROVIDER_DEFAULT;
|
||||
import static jakarta.persistence.FetchType.EAGER;
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy;
|
||||
|
@ -229,6 +227,7 @@ public class ToOneBinder {
|
|||
value,
|
||||
joinColumns,
|
||||
unique,
|
||||
isTargetAnnotatedEntity( targetEntity, property, context ),
|
||||
propertyHolder.getPersistentClass(),
|
||||
fullPath,
|
||||
context
|
||||
|
@ -254,6 +253,11 @@ public class ToOneBinder {
|
|||
);
|
||||
}
|
||||
|
||||
static boolean isTargetAnnotatedEntity(XClass targetEntity, XProperty property, MetadataBuildingContext context) {
|
||||
final XClass target = isDefault( targetEntity, context ) ? property.getType() : targetEntity;
|
||||
return target.isAnnotationPresent( Entity.class );
|
||||
}
|
||||
|
||||
private static boolean handleSpecjSyntax(
|
||||
AnnotatedJoinColumns columns,
|
||||
PropertyData inferredData,
|
||||
|
@ -490,6 +494,7 @@ public class ToOneBinder {
|
|||
notFoundAction,
|
||||
onDelete == null ? null : onDelete.action(),
|
||||
getTargetEntity( inferredData, context ),
|
||||
property,
|
||||
propertyHolder,
|
||||
inferredData,
|
||||
nullIfEmpty( oneToOne.mappedBy() ),
|
||||
|
@ -510,6 +515,7 @@ public class ToOneBinder {
|
|||
NotFoundAction notFoundAction,
|
||||
OnDeleteAction cascadeOnDelete,
|
||||
XClass targetEntity,
|
||||
XProperty annotatedProperty,
|
||||
PropertyHolder propertyHolder,
|
||||
PropertyData inferredData,
|
||||
String mappedBy,
|
||||
|
@ -528,10 +534,10 @@ public class ToOneBinder {
|
|||
final OneToOneSecondPass secondPass = new OneToOneSecondPass(
|
||||
mappedBy,
|
||||
propertyHolder.getEntityName(),
|
||||
propertyName,
|
||||
propertyHolder,
|
||||
inferredData,
|
||||
targetEntity,
|
||||
getReferenceEntityName( inferredData, targetEntity, context ),
|
||||
isTargetAnnotatedEntity( targetEntity, annotatedProperty, context ),
|
||||
notFoundAction,
|
||||
cascadeOnDelete,
|
||||
optional,
|
||||
|
|
|
@ -34,11 +34,13 @@ public class ToOneFkSecondPass extends FkSecondPass {
|
|||
private final boolean unique;
|
||||
private final String path;
|
||||
private final String entityClassName;
|
||||
private final boolean annotatedEntity;
|
||||
|
||||
public ToOneFkSecondPass(
|
||||
ToOne value,
|
||||
AnnotatedJoinColumns columns,
|
||||
boolean unique,
|
||||
boolean annotatedEntity,
|
||||
PersistentClass persistentClass,
|
||||
String path,
|
||||
MetadataBuildingContext buildingContext) {
|
||||
|
@ -48,6 +50,7 @@ public class ToOneFkSecondPass extends FkSecondPass {
|
|||
this.unique = unique;
|
||||
this.entityClassName = persistentClass.getClassName();
|
||||
this.path = entityClassName != null ? path.substring( entityClassName.length() + 1 ) : path;
|
||||
this.annotatedEntity = annotatedEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,13 +98,17 @@ public class ToOneFkSecondPass extends FkSecondPass {
|
|||
@Override
|
||||
public void doSecondPass(java.util.Map<String, PersistentClass> persistentClasses) throws MappingException {
|
||||
if ( value instanceof ManyToOne ) {
|
||||
//TODO: move this validation logic to a separate ManyToOnSecondPass
|
||||
//TODO: move this validation logic to a separate ManyToOneSecondPass
|
||||
// for consistency with how this is handled for OneToOnes
|
||||
final ManyToOne manyToOne = (ManyToOne) value;
|
||||
final PersistentClass targetEntity = persistentClasses.get( manyToOne.getReferencedEntityName() );
|
||||
final String targetEntityName = manyToOne.getReferencedEntityName();
|
||||
final PersistentClass targetEntity = persistentClasses.get( targetEntityName );
|
||||
if ( targetEntity == null ) {
|
||||
final String problem = annotatedEntity
|
||||
? " which does not belong to the same persistence unit"
|
||||
: " which is not an '@Entity' type";
|
||||
throw new AnnotationException( "Association '" + qualify( entityClassName, path )
|
||||
+ "' targets an unknown entity named '" + manyToOne.getReferencedEntityName() + "'" );
|
||||
+ "' targets the type '" + targetEntityName + "'" + problem );
|
||||
}
|
||||
manyToOne.setPropertyName( path );
|
||||
createSyntheticPropertyReference(
|
||||
|
|
Loading…
Reference in New Issue