introduce ForeignKeyType and AnnotationJoinColumns.getReferencedColumnsType()

This commit is contained in:
Gavin King 2022-10-31 15:56:02 +01:00
parent 849246e3cd
commit e2f42cd0bc
8 changed files with 117 additions and 124 deletions

View File

@ -406,9 +406,7 @@ public class AnnotatedColumn {
}
else {
final Table table = value.getTable();
if ( getParent() != null ) {
getParent().setTableInternal( table );
}
getMappingColumn().setValue( value );
value.addColumn( getMappingColumn(), insertable, updatable );
table.addColumn( getMappingColumn() );

View File

@ -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<AnnotatedJoinColumn> 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<Selectable> 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.
*

View File

@ -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<String, Join> joins,
Map<XClass, InheritanceState> 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<Selectable> 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();

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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
}

View File

@ -79,7 +79,8 @@ public final class PropertyHolderBuilder {
}
/**
* must only be used on second level phases (<join> 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,

View File

@ -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,7 +1539,12 @@ public abstract class CollectionBinder {
oneToMany.setAssociatedClass( associatedClass );
final Map<String, Join> joins = buildingContext.getMetadataCollector().getJoins( referencedEntityName );
foreignJoinColumns.setPersistentClass( associatedClass, joins, inheritanceStatePerClass );
foreignJoinColumns.setPropertyHolder( buildPropertyHolder(
associatedClass,
joins,
foreignJoinColumns.getBuildingContext(),
inheritanceStatePerClass
) );
foreignJoinColumns.setJoins( joins);
collection.setCollectionTable( foreignJoinColumns.getTable() );
if ( LOG.isDebugEnabled() ) {

View File

@ -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,17 +757,17 @@ 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<JPAIndexHolder> buildJpaIndexHolder(Index[] indexes) {