From e2f42cd0bc1b8e1940fdb98df104d035536ee1e3 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 31 Oct 2022 15:56:02 +0100 Subject: [PATCH] introduce ForeignKeyType and AnnotationJoinColumns.getReferencedColumnsType() --- .../org/hibernate/cfg/AnnotatedColumn.java | 4 +- .../hibernate/cfg/AnnotatedJoinColumn.java | 80 ---------------- .../hibernate/cfg/AnnotatedJoinColumns.java | 93 ++++++++++++++----- .../java/org/hibernate/cfg/BinderHelper.java | 4 +- .../org/hibernate/cfg/ForeignKeyType.java | 23 +++++ .../hibernate/cfg/PropertyHolderBuilder.java | 3 +- .../cfg/annotations/CollectionBinder.java | 10 +- .../cfg/annotations/TableBinder.java | 24 +++-- 8 files changed, 117 insertions(+), 124 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/cfg/ForeignKeyType.java diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedColumn.java index 7b192fc534..cf3058d925 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedColumn.java @@ -406,9 +406,7 @@ public class AnnotatedColumn { } else { final Table table = value.getTable(); - if ( getParent() != null ) { - getParent().setTableInternal( table ); - } + getParent().setTableInternal( table ); getMappingColumn().setValue( value ); value.addColumn( getMappingColumn(), insertable, updatable ); table.addColumn( getMappingColumn() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java index 643fc421cc..49521ab674 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java @@ -6,13 +6,11 @@ */ package org.hibernate.cfg; -import java.util.List; import jakarta.persistence.JoinColumn; import jakarta.persistence.PrimaryKeyJoinColumn; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; -import org.hibernate.MappingException; import org.hibernate.annotations.Comment; import org.hibernate.annotations.JoinFormula; import org.hibernate.boot.model.naming.Identifier; @@ -21,14 +19,10 @@ import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.mapping.Column; -import org.hibernate.mapping.Join; import org.hibernate.mapping.PersistentClass; -import org.hibernate.mapping.Selectable; import org.hibernate.mapping.SimpleValue; -import org.hibernate.mapping.Table; import org.hibernate.mapping.Value; -import static org.hibernate.cfg.BinderHelper.findReferencedColumnOwner; import static org.hibernate.cfg.BinderHelper.getRelativePath; import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue; import static org.hibernate.cfg.BinderHelper.isEmptyOrNullAnnotationValue; @@ -377,80 +371,6 @@ public class AnnotatedJoinColumn extends AnnotatedColumn { } } - //keep it JDK 1.4 compliant - //implicit way - public static final int NO_REFERENCE = 0; - //reference to the pk in an explicit order - public static final int PK_REFERENCE = 1; - //reference to non pk columns - public static final int NON_PK_REFERENCE = 2; - - public static int checkReferencedColumnsType( - AnnotatedJoinColumns joinColumns, - PersistentClass referencedEntity, - MetadataBuildingContext context) { - final List columns = joinColumns.getJoinColumns(); - if ( columns.size() == 0 ) { - return NO_REFERENCE; //shortcut - } - - final AnnotatedJoinColumn firstColumn = columns.get(0); - final Object columnOwner = findReferencedColumnOwner( referencedEntity, firstColumn, context ); - if ( columnOwner == null ) { - try { - throw new MappingException( "A '@JoinColumn' references a column named '" - + firstColumn.getReferencedColumn() + "' but the target entity '" - + referencedEntity.getEntityName() + "' has no property which maps to this column" ); - } - catch (MappingException me) { - // we throw a recoverable exception here in case this - // is merely an ordering issue, so that the SecondPass - // will get reprocessed later - throw new RecoverableException( me.getMessage(), me ); - } - } - final Table table = getTable( columnOwner ); - final List keyColumns = referencedEntity.getKey().getSelectables(); - boolean explicitColumnReference = false; - for ( AnnotatedJoinColumn column : columns ) { - if ( !column.isReferenceImplicit() ) { - explicitColumnReference = true; - if ( !keyColumns.contains( column( context, table, column.getReferencedColumn() ) ) ) { - // we have a column which does not belong to the PK - return NON_PK_REFERENCE; - } - } - } - if ( explicitColumnReference ) { - // if we got to here, all the columns belong to the PK - return keyColumns.size() == columns.size() - // we have all the PK columns - ? PK_REFERENCE - // we have a subset of the PK columns - : NON_PK_REFERENCE; - } - else { - // there were no nonempty referencedColumnNames - return NO_REFERENCE; - } - } - - private static Table getTable(Object persistentClassOrJoin) { - return persistentClassOrJoin instanceof PersistentClass - ? ( (PersistentClass) persistentClassOrJoin ).getTable() - : ( (Join) persistentClassOrJoin ).getTable(); - } - - private static Column column(MetadataBuildingContext context, Table table, String logicalReferencedColumnName) { - try { - return new Column( context.getMetadataCollector().getPhysicalColumnName( table, logicalReferencedColumnName ) ); - } - catch (MappingException me) { - throw new MappingException( "No column with logical name '" + logicalReferencedColumnName - + "' in table '" + table.getName() + "'" ); - } - } - /** * Called to apply column definitions from the referenced FK column to this column. * diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumns.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumns.java index f68e0d6855..45a2456795 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumns.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumns.java @@ -3,10 +3,10 @@ package org.hibernate.cfg; import jakarta.persistence.JoinColumn; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; +import org.hibernate.MappingException; import org.hibernate.annotations.Comment; import org.hibernate.annotations.JoinColumnOrFormula; import org.hibernate.annotations.JoinFormula; -import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.boot.model.naming.EntityNaming; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.ImplicitJoinColumnNameSource; @@ -25,12 +25,14 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.SimpleValue; +import org.hibernate.mapping.Table; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; +import static org.hibernate.cfg.BinderHelper.findReferencedColumnOwner; import static org.hibernate.cfg.BinderHelper.getRelativePath; import static org.hibernate.cfg.BinderHelper.isEmptyOrNullAnnotationValue; import static org.hibernate.cfg.PropertyHolderBuilder.buildPropertyHolder; @@ -238,27 +240,6 @@ public class AnnotatedJoinColumns extends AnnotatedColumns { return mappedByTableName; } - /** - * Override persistent class on oneToMany Cases for late settings - * Must only be used on second level pass binding - */ - public void setPersistentClass( - PersistentClass persistentClass, - Map joins, - Map inheritanceStatePerClass) { - // TODO shouldn't we deduce the class name from the persistentClass? - final PropertyHolder propertyHolder = buildPropertyHolder( - persistentClass, - joins, - getBuildingContext(), - inheritanceStatePerClass - ); - setPropertyHolder( propertyHolder ); -// for ( AnnotatedJoinColumn column : columns ) { -// column.setPropertyHolder( propertyHolder ); -// } - } - public boolean isElementCollection() { return elementCollection; } @@ -281,6 +262,74 @@ public class AnnotatedJoinColumns extends AnnotatedColumns { mappedByPropertyName = mappedByProperty; } + /** + * Determine if the given {@link AnnotatedJoinColumns} represent a reference to + * the primary key of the given {@link PersistentClass}, or whether they reference + * some other combination of mapped columns. + */ + public ForeignKeyType getReferencedColumnsType( + PersistentClass referencedEntity) { + if ( columns.isEmpty() ) { + return ForeignKeyType.IMPLICIT_PRIMARY_KEY_REFERENCE; //shortcut + } + + final AnnotatedJoinColumn firstColumn = columns.get(0); + final Object columnOwner = findReferencedColumnOwner( referencedEntity, firstColumn, getBuildingContext() ); + if ( columnOwner == null ) { + try { + throw new MappingException( "A '@JoinColumn' references a column named '" + + firstColumn.getReferencedColumn() + "' but the target entity '" + + referencedEntity.getEntityName() + "' has no property which maps to this column" ); + } + catch (MappingException me) { + // we throw a recoverable exception here in case this + // is merely an ordering issue, so that the SecondPass + // will get reprocessed later + throw new RecoverableException( me.getMessage(), me ); + } + } + final Table table = table( columnOwner ); + final List keyColumns = referencedEntity.getKey().getSelectables(); + boolean explicitColumnReference = false; + for ( AnnotatedJoinColumn column : columns ) { + if ( !column.isReferenceImplicit() ) { + explicitColumnReference = true; + if ( !keyColumns.contains( column( getBuildingContext(), table, column.getReferencedColumn() ) ) ) { + // we have a column which does not belong to the PK + return ForeignKeyType.NON_PRIMARY_KEY_REFERENCE; + } + } + } + if ( explicitColumnReference ) { + // if we got to here, all the columns belong to the PK + return keyColumns.size() == columns.size() + // we have all the PK columns + ? ForeignKeyType.EXPLICIT_PRIMARY_KEY_REFERENCE + // we have a subset of the PK columns + : ForeignKeyType.NON_PRIMARY_KEY_REFERENCE; + } + else { + // there were no nonempty referencedColumnNames + return ForeignKeyType.IMPLICIT_PRIMARY_KEY_REFERENCE; + } + } + + private static Table table(Object persistentClassOrJoin) { + return persistentClassOrJoin instanceof PersistentClass + ? ( (PersistentClass) persistentClassOrJoin ).getTable() + : ( (Join) persistentClassOrJoin ).getTable(); + } + + private static Column column(MetadataBuildingContext context, Table table, String logicalReferencedColumnName) { + try { + return new Column( context.getMetadataCollector().getPhysicalColumnName( table, logicalReferencedColumnName ) ); + } + catch (MappingException me) { + throw new MappingException( "No column with logical name '" + logicalReferencedColumnName + + "' in table '" + table.getName() + "'" ); + } + } + String buildDefaultColumnName(PersistentClass referencedEntity, String logicalReferencedColumn) { final MetadataBuildingOptions options = getBuildingContext().getBuildingOptions(); final ImplicitNamingStrategy implicitNamingStrategy = options.getImplicitNamingStrategy(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java index e0cdd5c8df..362b77f2c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java @@ -81,8 +81,6 @@ import jakarta.persistence.UniqueConstraint; import static org.hibernate.cfg.AnnotatedColumn.buildColumnOrFormulaFromAnnotation; import static org.hibernate.internal.util.StringHelper.isNotEmpty; -import static org.hibernate.cfg.AnnotatedJoinColumn.NON_PK_REFERENCE; -import static org.hibernate.cfg.AnnotatedJoinColumn.checkReferencedColumnsType; import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.EMBEDDED; import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.NOOP; @@ -173,7 +171,7 @@ public class BinderHelper { boolean inverse, MetadataBuildingContext context) { // this work is not necessary for a primary key reference - if ( checkReferencedColumnsType( joinColumns, targetEntity, context ) == NON_PK_REFERENCE ) { // && !firstColumn.isImplicit() + if ( joinColumns.getReferencedColumnsType( targetEntity ) == ForeignKeyType.NON_PRIMARY_KEY_REFERENCE ) { // && !firstColumn.isImplicit() // all the columns have to belong to the same table; // figure out which table has the columns by looking // for a PersistentClass or Join in the hierarchy of diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ForeignKeyType.java b/hibernate-core/src/main/java/org/hibernate/cfg/ForeignKeyType.java new file mode 100644 index 0000000000..707279587b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ForeignKeyType.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cfg; + +import org.hibernate.mapping.PersistentClass; + +/** + * Specifies the sort of foreign key reference given in a + * {@link jakarta.persistence.JoinColumn} annotation. + * + * @see AnnotatedJoinColumns#getReferencedColumnsType(PersistentClass) + * + * @author Gavin King + */ +public enum ForeignKeyType { + EXPLICIT_PRIMARY_KEY_REFERENCE, + IMPLICIT_PRIMARY_KEY_REFERENCE, + NON_PRIMARY_KEY_REFERENCE +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolderBuilder.java b/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolderBuilder.java index 0a0b788f1c..5218fed364 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/PropertyHolderBuilder.java @@ -79,7 +79,8 @@ public final class PropertyHolderBuilder { } /** - * must only be used on second level phases ( has to be settled already) + * May only be called during the second pass phase. + * (The join must have already been set.) */ public static PropertyHolder buildPropertyHolder( PersistentClass persistentClass, diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index 5d91b57b64..f456c21f82 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -167,6 +167,7 @@ import static org.hibernate.cfg.BinderHelper.getPath; import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue; import static org.hibernate.cfg.BinderHelper.toAliasEntityMap; import static org.hibernate.cfg.BinderHelper.toAliasTableMap; +import static org.hibernate.cfg.PropertyHolderBuilder.buildPropertyHolder; import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromExternalName; import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty; import static org.hibernate.internal.util.StringHelper.isEmpty; @@ -1538,8 +1539,13 @@ public abstract class CollectionBinder { oneToMany.setAssociatedClass( associatedClass ); final Map joins = buildingContext.getMetadataCollector().getJoins( referencedEntityName ); - foreignJoinColumns.setPersistentClass( associatedClass, joins, inheritanceStatePerClass ); - foreignJoinColumns.setJoins( joins ); + foreignJoinColumns.setPropertyHolder( buildPropertyHolder( + associatedClass, + joins, + foreignJoinColumns.getBuildingContext(), + inheritanceStatePerClass + ) ); + foreignJoinColumns.setJoins( joins); collection.setCollectionTable( foreignJoinColumns.getTable() ); if ( LOG.isDebugEnabled() ) { LOG.debugf( "Mapping collection: %s -> %s", collection.getRole(), collection.getCollectionTable().getName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java index 8e5a602bdd..88cc373d7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/TableBinder.java @@ -49,9 +49,6 @@ import org.hibernate.mapping.Value; import org.jboss.logging.Logger; -import static org.hibernate.cfg.AnnotatedJoinColumn.NON_PK_REFERENCE; -import static org.hibernate.cfg.AnnotatedJoinColumn.NO_REFERENCE; -import static org.hibernate.cfg.AnnotatedJoinColumn.checkReferencedColumnsType; import static org.hibernate.cfg.BinderHelper.isEmptyOrNullAnnotationValue; import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.isQuoted; @@ -574,15 +571,16 @@ public class TableBinder { SimpleValue value, MetadataBuildingContext buildingContext, PersistentClass associatedClass) { - switch ( checkReferencedColumnsType( joinColumns, referencedEntity, buildingContext ) ) { - case NON_PK_REFERENCE: + switch ( joinColumns.getReferencedColumnsType( referencedEntity ) ) { + case NON_PRIMARY_KEY_REFERENCE: bindNonPrimaryKeyReference( referencedEntity, joinColumns, value ); break; - case NO_REFERENCE: + case IMPLICIT_PRIMARY_KEY_REFERENCE: bindImplicitPrimaryKeyReference( referencedEntity, joinColumns, value, associatedClass ); break; - default: + case EXPLICIT_PRIMARY_KEY_REFERENCE: bindPrimaryKeyReference( referencedEntity, joinColumns, value, associatedClass, buildingContext ); + break; } } @@ -759,20 +757,20 @@ public class TableBinder { } } - public static void addIndexes(Table hibTable, org.hibernate.annotations.Index[] indexes, MetadataBuildingContext buildingContext) { + public static void addIndexes(Table table, org.hibernate.annotations.Index[] indexes, MetadataBuildingContext context) { for ( org.hibernate.annotations.Index index : indexes ) { //no need to handle inSecondPass here since it is only called from EntityBinder - buildingContext.getMetadataCollector().addSecondPass( - new IndexOrUniqueKeySecondPass( hibTable, index.name(), index.columnNames(), buildingContext ) + context.getMetadataCollector().addSecondPass( + new IndexOrUniqueKeySecondPass( table, index.name(), index.columnNames(), context ) ); } } - public static void addIndexes(Table hibTable, Index[] indexes, MetadataBuildingContext buildingContext) { - buildingContext.getMetadataCollector().addJpaIndexHolders( hibTable, buildJpaIndexHolder( indexes ) ); + public static void addIndexes(Table table, Index[] indexes, MetadataBuildingContext context) { + context.getMetadataCollector().addJpaIndexHolders( table, buildJpaIndexHolder( indexes ) ); } - public static List buildJpaIndexHolder(Index[] indexes){ + public static List buildJpaIndexHolder(Index[] indexes) { List holders = new ArrayList<>( indexes.length ); for ( Index index : indexes ) { holders.add( new JPAIndexHolder( index ) );