Skip to content

Commit

Permalink
HHH-15932 allow @XxxxToOne associations to target a secondary table
Browse files Browse the repository at this point in the history
  • Loading branch information
gavinking committed Dec 26, 2022
1 parent c7bad70 commit 014847f
Show file tree
Hide file tree
Showing 20 changed files with 443 additions and 144 deletions.
Expand Up @@ -8,7 +8,6 @@

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -84,7 +83,6 @@
import org.hibernate.generator.Generator;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
Expand Down Expand Up @@ -117,6 +115,9 @@
import jakarta.persistence.Entity;
import jakarta.persistence.MapsId;

import static java.util.Collections.emptyList;
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;

/**
* The implementation of the {@linkplain InFlightMetadataCollector in-flight
* metadata collector contract}.
Expand Down Expand Up @@ -1957,45 +1958,36 @@ private void secondPassCompileForeignKeys(MetadataBuildingContext buildingContex
}
}

protected void secondPassCompileForeignKeys(
final Table table,
Set<ForeignKey> done,
final MetadataBuildingContext buildingContext) throws MappingException {
protected void secondPassCompileForeignKeys(Table table, Set<ForeignKey> done, MetadataBuildingContext buildingContext)
throws MappingException {
table.createForeignKeys();

for ( ForeignKey foreignKey : table.getForeignKeys().values() ) {
if ( !done.contains( foreignKey ) ) {
done.add( foreignKey );
final String referencedEntityName = foreignKey.getReferencedEntityName();
if ( referencedEntityName == null ) {
throw new MappingException(
"An association from the table " +
foreignKey.getTable().getName() +
" does not specify the referenced entity"
);
throw new MappingException( "An association from the table '" + foreignKey.getTable().getName() +
"' does not specify the referenced entity" );
}

log.debugf( "Resolving reference to class: %s", referencedEntityName );
final PersistentClass referencedClass = getEntityBinding( referencedEntityName );
if ( referencedClass == null ) {
throw new MappingException(
"An association from the table " +
foreignKey.getTable().getName() +
" refers to an unmapped class: " +
referencedEntityName
);
throw new MappingException( "An association from the table '" + foreignKey.getTable().getName() +
"' refers to an unmapped class '" + referencedEntityName + "'" );
}
if ( referencedClass.isJoinedSubclass() ) {
secondPassCompileForeignKeys( referencedClass.getSuperclass().getTable(), done, buildingContext );
}

foreignKey.setReferencedTable( referencedClass.getTable() );

Identifier nameIdentifier;
// the ForeignKeys created in the first pass did not have their referenced table initialized
if ( foreignKey.getReferencedTable() == null ) {
foreignKey.setReferencedTable( referencedClass.getTable() );
}

nameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy()
final Identifier nameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy()
.determineForeignKeyName( new ForeignKeyNameSource( foreignKey, table, buildingContext ) );

foreignKey.setName( nameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ) );

foreignKey.alignColumns();
Expand All @@ -2005,10 +1997,10 @@ protected void secondPassCompileForeignKeys(

private List<Identifier> toIdentifiers(String[] names) {
if ( names == null ) {
return Collections.emptyList();
return emptyList();
}

final List<Identifier> columnNames = CollectionHelper.arrayList( names.length );
final List<Identifier> columnNames = arrayList( names.length );
for ( String name : names ) {
columnNames.add( getDatabase().toIdentifier( name ) );
}
Expand All @@ -2018,10 +2010,10 @@ private List<Identifier> toIdentifiers(String[] names) {
@SuppressWarnings("unchecked")
private List<Identifier> extractColumnNames(List columns) {
if ( columns == null || columns.isEmpty() ) {
return Collections.emptyList();
return emptyList();
}

final List<Identifier> columnNames = CollectionHelper.arrayList( columns.size() );
final List<Identifier> columnNames = arrayList( columns.size() );
for ( Column column : (List<Column>) columns ) {
columnNames.add( getDatabase().toIdentifier( column.getQuotedName() ) );
}
Expand Down
Expand Up @@ -284,8 +284,7 @@ public void setMappedBy(String entityName, String logicalTableName, String mappe
* the primary key of the given {@link PersistentClass}, or whether they reference
* some other combination of mapped columns.
*/
public ForeignKeyType getReferencedColumnsType(
PersistentClass referencedEntity) {
public ForeignKeyType getReferencedColumnsType(PersistentClass referencedEntity) {
if ( columns.isEmpty() ) {
return ForeignKeyType.IMPLICIT_PRIMARY_KEY_REFERENCE; //shortcut
}
Expand All @@ -298,15 +297,18 @@ public ForeignKeyType getReferencedColumnsType(
+ firstColumn.getReferencedColumn() + "' but the target entity '"
+ referencedEntity.getEntityName() + "' has no property which maps to this column" );
}
catch (MappingException me) {
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();
// final List<Selectable> keyColumns = referencedEntity.getKey().getSelectables();
final List<? extends Selectable> keyColumns = table.getPrimaryKey() == null
? referencedEntity.getKey().getSelectables()
: table.getPrimaryKey().getColumns();
boolean explicitColumnReference = false;
for ( AnnotatedJoinColumn column : columns ) {
if ( !column.isReferenceImplicit() ) {
Expand Down Expand Up @@ -342,7 +344,7 @@ private static Column column(MetadataBuildingContext context, Table table, Strin
return new Column( context.getMetadataCollector()
.getPhysicalColumnName( table, logicalReferencedColumnName ) );
}
catch (MappingException me) {
catch (MappingException me ) {
throw new MappingException( "No column with logical name '" + logicalReferencedColumnName
+ "' in table '" + table.getName() + "'" );
}
Expand Down
Expand Up @@ -336,11 +336,11 @@ private static Property makeSyntheticComponentProperty(
}
embeddedComponent.sortProperties();
final Property result = new SyntheticProperty();
result.setName(syntheticPropertyName);
result.setPersistentClass(ownerEntity);
result.setName( syntheticPropertyName );
result.setPersistentClass( ownerEntity );
result.setUpdateable( false );
result.setInsertable( false );
result.setValue(embeddedComponent);
result.setValue( embeddedComponent );
result.setPropertyAccessorName( "embedded" );
ownerEntity.addProperty( result );
embeddedComponent.createUniqueKey(); //make it unique
Expand Down
Expand Up @@ -2551,7 +2551,7 @@ private String extractHqlOrderBy(OrderBy jpaOrderBy) {

private static void checkFilterConditions(Collection collection) {
//for now it can't happen, but sometime soon...
if ( ( collection.getFilters().size() != 0 || isNotEmpty( collection.getWhere() ) )
if ( ( !collection.getFilters().isEmpty() || isNotEmpty( collection.getWhere() ) )
&& collection.getFetchMode() == FetchMode.JOIN
&& !( collection.getElement() instanceof SimpleValue ) //SimpleValue (CollectionOfElements) are always SELECT but it does not matter
&& collection.getElement().getFetchMode() != FetchMode.JOIN ) {
Expand Down Expand Up @@ -2623,7 +2623,7 @@ public void bindManyToManyInverseForeignKey(
AnnotatedJoinColumns joinColumns,
SimpleValue value,
boolean unique) {
if (isUnownedCollection()) {
if ( isUnownedCollection() ) {
bindUnownedManyToManyInverseForeignKey( targetEntity, joinColumns, value );
}
else {
Expand Down Expand Up @@ -2663,7 +2663,7 @@ private void bindUnownedManyToManyInverseForeignKey(
AnnotatedJoinColumns joinColumns,
SimpleValue value) {
final Property property = targetEntity.getRecursiveProperty( mappedBy );
final List<Selectable> mappedByColumns = mappedByColumns(targetEntity, property );
final List<Selectable> mappedByColumns = mappedByColumns( targetEntity, property );
final AnnotatedJoinColumn firstColumn = joinColumns.getJoinColumns().get(0);
for ( Selectable selectable: mappedByColumns ) {
firstColumn.linkValueUsingAColumnCopy( (Column) selectable, value);
Expand Down
Expand Up @@ -11,6 +11,7 @@

import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
import org.hibernate.boot.model.naming.EntityNaming;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.ImplicitCollectionTableNameSource;
Expand All @@ -27,6 +28,7 @@
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.DependantValue;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.PersistentClass;
Expand Down Expand Up @@ -64,7 +66,6 @@ public class TableBinder {
private String name;
private boolean isAbstract;
private List<UniqueConstraintHolder> uniqueConstraints;
// private List<String[]> uniqueConstraints;
String constraints;
private String ownerEntityTable;
private String associatedEntityTable;
Expand Down Expand Up @@ -552,7 +553,7 @@ else if ( firstColumn.isImplicit() ) {
else {
bindExplicitColumns( referencedEntity, joinColumns, value, buildingContext, associatedClass );
}
value.createForeignKey();
value.createForeignKey( referencedEntity, joinColumns );
if ( unique ) {
value.createUniqueKey();
}
Expand Down Expand Up @@ -616,35 +617,15 @@ private static void bindPrimaryKeyReference(
( (Component) key).sortProperties();
}
// works because the pk has to be on the primary table
final Dialect dialect = buildingContext.getMetadataCollector().getDatabase()
.getJdbcEnvironment().getDialect();
for ( Column column: key.getColumns() ) {
boolean match = false;
// for each PK column, find the associated FK column.
final String quotedName = column.getQuotedName( dialect );
for ( AnnotatedJoinColumn joinColumn : joinColumns.getJoinColumns() ) {
final String referencedColumn = buildingContext.getMetadataCollector()
.getPhysicalColumnName( referencedEntity.getTable(), joinColumn.getReferencedColumn() );
// in JPA 2 referencedColumnName is case-insensitive
if ( referencedColumn.equalsIgnoreCase( quotedName ) ) {
// correct join column
if ( joinColumn.isNameDeferred() ) {
joinColumn.linkValueUsingDefaultColumnNaming( column, referencedEntity, value );
}
else {
joinColumn.linkWithValue( value );
}
joinColumn.overrideFromReferencedColumnIfNecessary( column );
match = true;
break;
}
}
if ( !match ) {
final InFlightMetadataCollector metadataCollector = buildingContext.getMetadataCollector();
final Dialect dialect = metadataCollector.getDatabase().getJdbcEnvironment().getDialect();
for ( int j = 0; j < key.getColumnSpan(); j++ ) {
if ( !matchUpJoinColumnsWithKeyColumns( referencedEntity, joinColumns, value, metadataCollector, dialect, j ) ) {
// we can only get here if there's a dupe PK column in the @JoinColumns
throw new AnnotationException(
"An association that targets entity '" + referencedEntity.getEntityName()
+ "' from entity '" + associatedClass.getEntityName()
+ "' has no '@JoinColumn' referencing column '"+ column.getName()
+ "' has no '@JoinColumn' referencing column '" + key.getColumns().get(j).getName() + "'"
);
}
}
Expand All @@ -653,6 +634,56 @@ private static void bindPrimaryKeyReference(
}
}

private static boolean matchUpJoinColumnsWithKeyColumns(
PersistentClass referencedEntity,
AnnotatedJoinColumns joinColumns,
SimpleValue value,
InFlightMetadataCollector metadataCollector,
Dialect dialect,
int index) {
// for each PK column, find the associated FK column.
for ( AnnotatedJoinColumn joinColumn : joinColumns.getJoinColumns() ) {
final String referencedNamed = joinColumn.getReferencedColumn();
String referencedColumn = null;
List<Column> columns = null;
try {
final Table referencedTable = referencedEntity.getTable();
referencedColumn = metadataCollector.getPhysicalColumnName( referencedTable, referencedNamed );
columns = referencedEntity.getKey().getColumns();
}
catch ( MappingException me ) {
for ( Join join : referencedEntity.getJoins() ) {
try {
final Table referencedTable = join.getTable();
referencedColumn = metadataCollector.getPhysicalColumnName( referencedTable, referencedNamed );
columns = referencedTable.getPrimaryKey().getColumns();
break;
}
catch ( MappingException i ) {
}
}
if ( referencedColumn == null ) {
throw me;
}
}
final Column column = columns.get( index );
final String quotedName = column.getQuotedName( dialect );
// in JPA 2 referencedColumnName is case-insensitive
if ( referencedColumn.equalsIgnoreCase( quotedName ) ) {
// correct join column
if ( joinColumn.isNameDeferred() ) {
joinColumn.linkValueUsingDefaultColumnNaming( column, referencedEntity, value );
}
else {
joinColumn.linkWithValue( value );
}
joinColumn.overrideFromReferencedColumnIfNecessary( column );
return true;
}
}
return false;
}

private static void bindNonPrimaryKeyReference(
PersistentClass referencedEntity,
AnnotatedJoinColumns joinColumns,
Expand Down
Expand Up @@ -17,7 +17,6 @@

import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.Remove;
import org.hibernate.boot.model.relational.Exportable;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect;
Expand Down Expand Up @@ -165,7 +164,7 @@ public List<Column> getColumns() {
/**
* @deprecated this method is no longer called
*/
@Deprecated(since="6.2") @Remove
@Deprecated(since="6.2", forRemoval = true)
public abstract String sqlConstraintString(
SqlStringGenerationContext context,
String constraintName,
Expand Down
17 changes: 5 additions & 12 deletions hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java
Expand Up @@ -56,7 +56,7 @@ public void setName(String name) {
}
}

@Override @Deprecated(since="6.2")
@Override @Deprecated(since="6.2", forRemoval = true)
public String sqlConstraintString(
SqlStringGenerationContext context,
String constraintName,
Expand All @@ -76,17 +76,12 @@ public String sqlConstraintString(
referencedColumnNames[i] = referencedColumns.get(i).getQuotedName( dialect );
}

final String result = keyDefinition != null ?
dialect.getAddForeignKeyConstraintString(
constraintName,
keyDefinition
) :
dialect.getAddForeignKeyConstraintString(
final String result = keyDefinition != null
? dialect.getAddForeignKeyConstraintString( constraintName, keyDefinition )
: dialect.getAddForeignKeyConstraintString(
constraintName,
columnNames,
referencedTable.getQualifiedName(
context
),
referencedTable.getQualifiedName( context ),
referencedColumnNames,
isReferenceToPrimaryKey()
);
Expand All @@ -111,8 +106,6 @@ private void appendColumns(StringBuilder buf, Iterator<Column> columns) {
}

public void setReferencedTable(Table referencedTable) throws MappingException {
//if( isReferenceToPrimaryKey() ) alignColumns(referencedTable); // TODO: possibly remove to allow more piecemal building of a foreignkey.

this.referencedTable = referencedTable;
}

Expand Down

0 comments on commit 014847f

Please sign in to comment.