avoid passing property-level information via the first AnnotatedJoinColumn

This commit is contained in:
Gavin King 2022-10-28 20:59:57 +02:00
parent d9392d6601
commit 8b3030aa8b
7 changed files with 106 additions and 69 deletions

View File

@ -46,31 +46,31 @@ public class AnnotatedDiscriminatorColumn extends AnnotatedColumn {
public static AnnotatedDiscriminatorColumn buildDiscriminatorColumn( public static AnnotatedDiscriminatorColumn buildDiscriminatorColumn(
DiscriminatorType type, DiscriminatorType type,
DiscriminatorColumn discAnn, DiscriminatorColumn discriminatorColumn,
DiscriminatorFormula discFormulaAnn, DiscriminatorFormula discriminatorFormula,
MetadataBuildingContext context) { MetadataBuildingContext context) {
final AnnotatedDiscriminatorColumn discriminatorColumn = new AnnotatedDiscriminatorColumn(); final AnnotatedDiscriminatorColumn column = new AnnotatedDiscriminatorColumn();
discriminatorColumn.setBuildingContext( context ); column.setBuildingContext( context );
if ( discFormulaAnn != null ) { if ( discriminatorFormula != null ) {
discriminatorColumn.setImplicit( false ); column.setImplicit( false );
discriminatorColumn.setFormula( discFormulaAnn.value() ); column.setFormula( discriminatorFormula.value() );
} }
else if ( discAnn != null ) { else if ( discriminatorColumn != null ) {
discriminatorColumn.setImplicit( false ); column.setImplicit( false );
if ( !isEmptyAnnotationValue( discAnn.columnDefinition() ) ) { if ( !isEmptyAnnotationValue( discriminatorColumn.columnDefinition() ) ) {
discriminatorColumn.setSqlType( discAnn.columnDefinition() ); column.setSqlType( discriminatorColumn.columnDefinition() );
} }
if ( !isEmptyAnnotationValue( discAnn.name() ) ) { if ( !isEmptyAnnotationValue( discriminatorColumn.name() ) ) {
discriminatorColumn.setLogicalColumnName( discAnn.name() ); column.setLogicalColumnName( discriminatorColumn.name() );
} }
discriminatorColumn.setNullable( false ); column.setNullable( false );
} }
else { else {
discriminatorColumn.setImplicit( true ); column.setImplicit( true );
} }
setDiscriminatorType( type, discAnn, discriminatorColumn ); setDiscriminatorType( type, discriminatorColumn, column );
discriminatorColumn.bind(); column.bind();
return discriminatorColumn; return column;
} }
private static void setDiscriminatorType( private static void setDiscriminatorType(

View File

@ -505,7 +505,7 @@ public final class AnnotationBinder {
} }
/** /**
* Bind a class having JSR175 annotations. Subclasses <b>have to</b> be bound after its parent class. * Bind an annotated class. A subclass must be bound <em>after</em> its superclass.
* *
* @param clazzToProcess entity to bind as {@code XClass} instance * @param clazzToProcess entity to bind as {@code XClass} instance
* @param inheritanceStatePerClass Metadata about the inheritance relationships for all mapped classes * @param inheritanceStatePerClass Metadata about the inheritance relationships for all mapped classes
@ -1048,10 +1048,9 @@ public final class AnnotationBinder {
|| element.isAnnotationPresent(EmbeddedId.class); || element.isAnnotationPresent(EmbeddedId.class);
} }
/* /**
* Process annotation of a particular property * Process annotation of a particular property or field.
*/ */
public static void processElementAnnotations( public static void processElementAnnotations(
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
Nullability nullability, Nullability nullability,

View File

@ -164,6 +164,7 @@ public class BinderHelper {
// the entity which declares the association (used for exception message) // the entity which declares the association (used for exception message)
PersistentClass associatedEntity, PersistentClass associatedEntity,
Value value, Value value,
String propertyName,
// true when we do the reverse side of a @ManyToMany // true when we do the reverse side of a @ManyToMany
boolean inverse, boolean inverse,
MetadataBuildingContext context) { MetadataBuildingContext context) {
@ -174,8 +175,6 @@ public class BinderHelper {
// Property instance // Property instance
final AnnotatedJoinColumn firstColumn = columns[0]; final AnnotatedJoinColumn firstColumn = columns[0];
if ( !firstColumn.isImplicit() if ( !firstColumn.isImplicit()
// only necessary for owning side of association
&& !firstColumn.hasMappedBy()
// not necessary for a primary key reference // not necessary for a primary key reference
&& checkReferencedColumnsType( columns, targetEntity, context ) == NON_PK_REFERENCE ) { && checkReferencedColumnsType( columns, targetEntity, context ) == NON_PK_REFERENCE ) {
@ -184,29 +183,21 @@ public class BinderHelper {
// for a PersistentClass or Join in the hierarchy of // for a PersistentClass or Join in the hierarchy of
// the target entity which has the first column // the target entity which has the first column
final Object columnOwner = findColumnOwner( targetEntity, firstColumn.getReferencedColumn(), context ); final Object columnOwner = findColumnOwner( targetEntity, firstColumn.getReferencedColumn(), context );
for ( AnnotatedJoinColumn col: columns ) { checkColumnInSameTable( columns, targetEntity, associatedEntity, context, columnOwner );
final Object owner = findColumnOwner( targetEntity, col.getReferencedColumn(), context );
if ( owner == null ) {
throw new AnnotationException( "A '@JoinColumn' for association "
+ associationMessage( associatedEntity, columns[0] )
+ " references a column named '" + col.getReferencedColumn()
+ "' which is not mapped by the target entity '"
+ targetEntity.getEntityName() + "'" );
}
if ( owner != columnOwner ) {
throw new AnnotationException( "The '@JoinColumn's for association "
+ associationMessage( associatedEntity, columns[0] )
+ " reference columns of different tables mapped by the target entity '"
+ targetEntity.getEntityName() + "' ('" + col.getReferencedColumn() +
"' belongs to a different table to '" + firstColumn.getReferencedColumn() + "'" );
}
}
// find all properties mapped to each column // find all properties mapped to each column
final List<Property> properties = findPropertiesByColumns( columnOwner, columns, associatedEntity, context ); final List<Property> properties = findPropertiesByColumns( columnOwner, columns, associatedEntity, context );
// create a Property along with the new synthetic // create a Property along with the new synthetic
// Component if necessary (or reuse the existing // Component if necessary (or reuse the existing
// Property that matches exactly) // Property that matches exactly)
final Property property = referencedProperty( targetEntity, inverse, columns, columnOwner, properties, context ); final Property property = referencedProperty(
targetEntity,
associatedEntity,
propertyName,
inverse,
columnOwner,
properties,
context
);
// register the mapping with the InFlightMetadataCollector // register the mapping with the InFlightMetadataCollector
registerSyntheticProperty( registerSyntheticProperty(
targetEntity, targetEntity,
@ -220,6 +211,41 @@ public class BinderHelper {
} }
} }
/**
* All the referenced columns must belong to the same table, that is
* {@link #findColumnOwner(PersistentClass, String, MetadataBuildingContext)}
* must return the same value for all of them.
*/
private static void checkColumnInSameTable(
AnnotatedJoinColumn[] columns,
PersistentClass targetEntity,
PersistentClass associatedEntity,
MetadataBuildingContext context,
Object columnOwner) {
final AnnotatedJoinColumn firstColumn = columns[0];
for ( AnnotatedJoinColumn column: columns) {
if ( column.hasMappedBy() ) {
// we should only get called for owning side of association
throw new AssertionFailure("no need to create synthetic properties for unowned collections");
}
final Object owner = findColumnOwner( targetEntity, column.getReferencedColumn(), context );
if ( owner == null ) {
throw new AnnotationException( "A '@JoinColumn' for association "
+ associationMessage(associatedEntity, firstColumn)
+ " references a column named '" + column.getReferencedColumn()
+ "' which is not mapped by the target entity '"
+ targetEntity.getEntityName() + "'" );
}
if ( owner != columnOwner) {
throw new AnnotationException( "The '@JoinColumn's for association "
+ associationMessage(associatedEntity, firstColumn)
+ " reference columns of different tables mapped by the target entity '"
+ targetEntity.getEntityName() + "' ('" + column.getReferencedColumn() +
"' belongs to a different table to '" + firstColumn.getReferencedColumn() + "'" );
}
}
}
/** /**
* If the referenced columns correspond to exactly one property * If the referenced columns correspond to exactly one property
* of the primary table of the exact target entity subclass, * of the primary table of the exact target entity subclass,
@ -232,8 +258,9 @@ public class BinderHelper {
*/ */
private static Property referencedProperty( private static Property referencedProperty(
PersistentClass ownerEntity, PersistentClass ownerEntity,
PersistentClass associatedEntity,
String propertyName,
boolean inverse, boolean inverse,
AnnotatedJoinColumn[] columns,
Object columnOwner, Object columnOwner,
List<Property> properties, List<Property> properties,
MetadataBuildingContext context) { MetadataBuildingContext context) {
@ -253,9 +280,7 @@ public class BinderHelper {
// mapped to the referenced columns. We need to shallow // mapped to the referenced columns. We need to shallow
// clone those properties to mark them as non-insertable // clone those properties to mark them as non-insertable
// and non-updatable // and non-updatable
final AnnotatedJoinColumn firstColumn = columns[0]; final String syntheticPropertyName = syntheticPropertyName( propertyName, inverse, associatedEntity );
final PersistentClass associatedClass = firstColumn.getPropertyHolder().getPersistentClass();
final String syntheticPropertyName = syntheticPropertyName( firstColumn.getPropertyName(), inverse, associatedClass );
return makeSyntheticComponentProperty( ownerEntity, columnOwner, context, syntheticPropertyName, properties ); return makeSyntheticComponentProperty( ownerEntity, columnOwner, context, syntheticPropertyName, properties );
} }
} }
@ -826,7 +851,7 @@ public class BinderHelper {
final GeneratedValue generatedValueAnn = idXProperty.getAnnotation( GeneratedValue.class ); final GeneratedValue generatedValueAnn = idXProperty.getAnnotation( GeneratedValue.class );
if ( generatedValueAnn == null ) { if ( generatedValueAnn == null ) {
// this should really never happen, but its easy to protect against it... // this should really never happen, but it's easy to protect against it...
return new IdentifierGeneratorDefinition( "assigned", "assigned" ); return new IdentifierGeneratorDefinition( "assigned", "assigned" );
} }

View File

@ -107,6 +107,7 @@ public class ToOneFkSecondPass extends FkSecondPass {
targetEntity, targetEntity,
persistentClass, persistentClass,
manyToOne, manyToOne,
path,
false, false,
buildingContext buildingContext
); );

View File

@ -94,7 +94,6 @@ import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.BootLogging; import org.hibernate.boot.BootLogging;
import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.model.TypeDefinition; import org.hibernate.boot.model.TypeDefinition;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.InFlightMetadataCollector.CollectionTypeRegistrationDescriptor; import org.hibernate.boot.spi.InFlightMetadataCollector.CollectionTypeRegistrationDescriptor;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
@ -245,6 +244,9 @@ public abstract class CollectionBinder {
this.buildingContext = buildingContext; this.buildingContext = buildingContext;
} }
/**
* The first pass at binding a collection.
*/
public static void bindCollection( public static void bindCollection(
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
Nullability nullability, Nullability nullability,
@ -1506,8 +1508,10 @@ public abstract class CollectionBinder {
return isNotEmpty( mappedBy ); return isNotEmpty( mappedBy );
} }
/**
* Bind a {@link OneToMany} association.
*/
protected void bindOneToManySecondPass(Map<String, PersistentClass> persistentClasses) { protected void bindOneToManySecondPass(Map<String, PersistentClass> persistentClasses) {
if ( property == null ) { if ( property == null ) {
throw new AssertionFailure( "null was passed for argument property" ); throw new AssertionFailure( "null was passed for argument property" );
} }
@ -1544,7 +1548,7 @@ public abstract class CollectionBinder {
bindFilters( false ); bindFilters( false );
handleWhere( false ); handleWhere( false );
PersistentClass targetEntity = persistentClasses.get( getElementType().getName() ); final PersistentClass targetEntity = persistentClasses.get( getElementType().getName() );
bindCollectionSecondPass( targetEntity, foreignJoinColumns, cascadeDeleteEnabled, buildingContext ); bindCollectionSecondPass( targetEntity, foreignJoinColumns, cascadeDeleteEnabled, buildingContext );
if ( !collection.isInverse() && !collection.getKey().isNullable() ) { if ( !collection.isInverse() && !collection.getKey().isNullable() ) {
@ -1955,14 +1959,16 @@ public abstract class CollectionBinder {
} }
} }
/**
* Bind a {@link ManyToMany} association or {@link ElementCollection}.
*/
private void bindManyToManySecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException { private void bindManyToManySecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
if ( property == null ) { if ( property == null ) {
throw new AssertionFailure( "null was passed for argument property" ); throw new AssertionFailure( "null was passed for argument property" );
} }
final XClass elementType = getElementType(); final XClass elementType = getElementType();
final PersistentClass targetEntity = persistentClasses.get( elementType.getName() ); final PersistentClass targetEntity = persistentClasses.get( elementType.getName() ); //null if this is an @ElementCollection
final String hqlOrderBy = extractHqlOrderBy( jpaOrderBy ); final String hqlOrderBy = extractHqlOrderBy( jpaOrderBy );
final boolean isCollectionOfEntities = targetEntity != null; final boolean isCollectionOfEntities = targetEntity != null;
@ -2482,14 +2488,17 @@ public abstract class CollectionBinder {
boolean cascadeDeleteEnabled, boolean cascadeDeleteEnabled,
MetadataBuildingContext context) { MetadataBuildingContext context) {
createSyntheticPropertyReference( if ( !hasMappedBy() ) {
joinColumns, createSyntheticPropertyReference(
collection.getOwner(), joinColumns,
collection.getOwner(), collection.getOwner(),
collection, collection.getOwner(),
false, collection,
context propertyName,
); false,
context
);
}
final DependantValue key = buildCollectionKey( final DependantValue key = buildCollectionKey(
collection, collection,
@ -2527,9 +2536,11 @@ public abstract class CollectionBinder {
/** /**
* Bind the inverse foreign key of a {@link ManyToMany}. * Bind the inverse foreign key of a {@link ManyToMany}, that is, the columns
* If we are in a mappedBy case, read the columns from the associated * specified by {@code @JoinTable(inverseJoinColumns=...)}, which are the
* collection element. Otherwise, delegate to the usual algorithm. * columns that reference the target entity of the many-to-many association.
* If we are in a {@code mappedBy} case, read the columns from the associated
* collection element in the target entity.
*/ */
public void bindManyToManyInverseForeignKey( public void bindManyToManyInverseForeignKey(
PersistentClass targetEntity, PersistentClass targetEntity,
@ -2560,6 +2571,7 @@ public abstract class CollectionBinder {
targetEntity, targetEntity,
collection.getOwner(), collection.getOwner(),
value, value,
propertyName,
true, true,
context context
); );

View File

@ -197,6 +197,9 @@ public class EntityBinder {
private boolean cacheLazyProperty; private boolean cacheLazyProperty;
private String naturalIdCacheRegion; private String naturalIdCacheRegion;
/**
* Bind an entity class. This can be done in a single pass.
*/
public static void bindEntityClass( public static void bindEntityClass(
XClass clazzToProcess, XClass clazzToProcess,
Map<XClass, InheritanceState> inheritanceStatePerClass, Map<XClass, InheritanceState> inheritanceStatePerClass,
@ -1555,7 +1558,6 @@ public class EntityBinder {
return new LocalCacheAnnotationStub( region, determineCacheConcurrencyStrategy( context ) ); return new LocalCacheAnnotationStub( region, determineCacheConcurrencyStrategy( context ) );
} }
@SuppressWarnings("ClassExplicitlyAnnotation")
private static class LocalCacheAnnotationStub implements Cache { private static class LocalCacheAnnotationStub implements Cache {
private final String region; private final String region;
private final CacheConcurrencyStrategy usage; private final CacheConcurrencyStrategy usage;

View File

@ -93,10 +93,8 @@ public class PropertyBinder {
this.entityBinder = entityBinder; this.entityBinder = entityBinder;
} }
/* // property can be null
* property can be null // prefer propertyName to property.getName() since some are overloaded
* prefer propertyName to property.getName() since some are overloaded
*/
private XProperty property; private XProperty property;
private XClass returnedClass; private XClass returnedClass;
private boolean isId; private boolean isId;
@ -234,7 +232,7 @@ public class PropertyBinder {
} }
private Property bind(Property prop) { private Property bind(Property prop) {
if (isId) { if ( isId ) {
final RootClass rootClass = ( RootClass ) holder.getPersistentClass(); final RootClass rootClass = ( RootClass ) holder.getPersistentClass();
//if an xToMany, it has to be wrapped today. //if an xToMany, it has to be wrapped today.
//FIXME this poses a problem as the PK is the class instead of the associated class which is not really compliant with the spec //FIXME this poses a problem as the PK is the class instead of the associated class which is not really compliant with the spec