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 59e9c4ca68..487e0d3925 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 @@ -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; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java index e2493e7b95..164588acfa 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java @@ -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 resolveCustomInstantiator( XProperty property, XClass propertyClass, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java index 1db84eb47d..a5b5665fdf 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java @@ -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 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java index 6f88a38327..5b03e337da 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java @@ -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 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 persistentClasses, OneToOne oneToOne, String propertyName, Property property) { + private void bindOwned( + Map 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); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java index 77dc9d1fc1..5af8304778 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java @@ -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, 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 6b37806733..be64fdfbbe 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 @@ -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 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(