HHH-15932 allow @XxxxToOne associations to target a secondary table

This commit is contained in:
Gavin 2022-12-24 18:52:38 +01:00 committed by Gavin King
parent c7bad70073
commit 014847f41b
20 changed files with 443 additions and 144 deletions

View File

@ -8,7 +8,6 @@ package org.hibernate.boot.internal;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -84,7 +83,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.generator.Generator; import org.hibernate.generator.Generator;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.Collection; import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
@ -117,6 +115,9 @@ import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.MapsId; 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 * The implementation of the {@linkplain InFlightMetadataCollector in-flight
* metadata collector contract}. * metadata collector contract}.
@ -1957,10 +1958,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
} }
} }
protected void secondPassCompileForeignKeys( protected void secondPassCompileForeignKeys(Table table, Set<ForeignKey> done, MetadataBuildingContext buildingContext)
final Table table, throws MappingException {
Set<ForeignKey> done,
final MetadataBuildingContext buildingContext) throws MappingException {
table.createForeignKeys(); table.createForeignKeys();
for ( ForeignKey foreignKey : table.getForeignKeys().values() ) { for ( ForeignKey foreignKey : table.getForeignKeys().values() ) {
@ -1968,34 +1967,27 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
done.add( foreignKey ); done.add( foreignKey );
final String referencedEntityName = foreignKey.getReferencedEntityName(); final String referencedEntityName = foreignKey.getReferencedEntityName();
if ( referencedEntityName == null ) { if ( referencedEntityName == null ) {
throw new MappingException( throw new MappingException( "An association from the table '" + foreignKey.getTable().getName() +
"An association from the table " + "' does not specify the referenced entity" );
foreignKey.getTable().getName() +
" does not specify the referenced entity"
);
} }
log.debugf( "Resolving reference to class: %s", referencedEntityName ); log.debugf( "Resolving reference to class: %s", referencedEntityName );
final PersistentClass referencedClass = getEntityBinding( referencedEntityName ); final PersistentClass referencedClass = getEntityBinding( referencedEntityName );
if ( referencedClass == null ) { if ( referencedClass == null ) {
throw new MappingException( throw new MappingException( "An association from the table '" + foreignKey.getTable().getName() +
"An association from the table " + "' refers to an unmapped class '" + referencedEntityName + "'" );
foreignKey.getTable().getName() +
" refers to an unmapped class: " +
referencedEntityName
);
} }
if ( referencedClass.isJoinedSubclass() ) { if ( referencedClass.isJoinedSubclass() ) {
secondPassCompileForeignKeys( referencedClass.getSuperclass().getTable(), done, buildingContext ); secondPassCompileForeignKeys( referencedClass.getSuperclass().getTable(), done, buildingContext );
} }
foreignKey.setReferencedTable( referencedClass.getTable() ); // the ForeignKeys created in the first pass did not have their referenced table initialized
if ( foreignKey.getReferencedTable() == null ) {
foreignKey.setReferencedTable( referencedClass.getTable() );
}
Identifier nameIdentifier; final Identifier nameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy()
nameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy()
.determineForeignKeyName( new ForeignKeyNameSource( foreignKey, table, buildingContext ) ); .determineForeignKeyName( new ForeignKeyNameSource( foreignKey, table, buildingContext ) );
foreignKey.setName( nameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ) ); foreignKey.setName( nameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ) );
foreignKey.alignColumns(); foreignKey.alignColumns();
@ -2005,10 +1997,10 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
private List<Identifier> toIdentifiers(String[] names) { private List<Identifier> toIdentifiers(String[] names) {
if ( names == null ) { 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 ) { for ( String name : names ) {
columnNames.add( getDatabase().toIdentifier( name ) ); columnNames.add( getDatabase().toIdentifier( name ) );
} }
@ -2018,10 +2010,10 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private List<Identifier> extractColumnNames(List columns) { private List<Identifier> extractColumnNames(List columns) {
if ( columns == null || columns.isEmpty() ) { 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 ) { for ( Column column : (List<Column>) columns ) {
columnNames.add( getDatabase().toIdentifier( column.getQuotedName() ) ); columnNames.add( getDatabase().toIdentifier( column.getQuotedName() ) );
} }

View File

@ -284,8 +284,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
* the primary key of the given {@link PersistentClass}, or whether they reference * the primary key of the given {@link PersistentClass}, or whether they reference
* some other combination of mapped columns. * some other combination of mapped columns.
*/ */
public ForeignKeyType getReferencedColumnsType( public ForeignKeyType getReferencedColumnsType(PersistentClass referencedEntity) {
PersistentClass referencedEntity) {
if ( columns.isEmpty() ) { if ( columns.isEmpty() ) {
return ForeignKeyType.IMPLICIT_PRIMARY_KEY_REFERENCE; //shortcut return ForeignKeyType.IMPLICIT_PRIMARY_KEY_REFERENCE; //shortcut
} }
@ -298,7 +297,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
+ firstColumn.getReferencedColumn() + "' but the target entity '" + firstColumn.getReferencedColumn() + "' but the target entity '"
+ referencedEntity.getEntityName() + "' has no property which maps to this column" ); + 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 // we throw a recoverable exception here in case this
// is merely an ordering issue, so that the SecondPass // is merely an ordering issue, so that the SecondPass
// will get reprocessed later // will get reprocessed later
@ -306,7 +305,10 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
} }
} }
final Table table = table( columnOwner ); 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; boolean explicitColumnReference = false;
for ( AnnotatedJoinColumn column : columns ) { for ( AnnotatedJoinColumn column : columns ) {
if ( !column.isReferenceImplicit() ) { if ( !column.isReferenceImplicit() ) {
@ -342,7 +344,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
return new Column( context.getMetadataCollector() return new Column( context.getMetadataCollector()
.getPhysicalColumnName( table, logicalReferencedColumnName ) ); .getPhysicalColumnName( table, logicalReferencedColumnName ) );
} }
catch (MappingException me) { catch (MappingException me ) {
throw new MappingException( "No column with logical name '" + logicalReferencedColumnName throw new MappingException( "No column with logical name '" + logicalReferencedColumnName
+ "' in table '" + table.getName() + "'" ); + "' in table '" + table.getName() + "'" );
} }

View File

@ -336,11 +336,11 @@ public class BinderHelper {
} }
embeddedComponent.sortProperties(); embeddedComponent.sortProperties();
final Property result = new SyntheticProperty(); final Property result = new SyntheticProperty();
result.setName(syntheticPropertyName); result.setName( syntheticPropertyName );
result.setPersistentClass(ownerEntity); result.setPersistentClass( ownerEntity );
result.setUpdateable( false ); result.setUpdateable( false );
result.setInsertable( false ); result.setInsertable( false );
result.setValue(embeddedComponent); result.setValue( embeddedComponent );
result.setPropertyAccessorName( "embedded" ); result.setPropertyAccessorName( "embedded" );
ownerEntity.addProperty( result ); ownerEntity.addProperty( result );
embeddedComponent.createUniqueKey(); //make it unique embeddedComponent.createUniqueKey(); //make it unique

View File

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

View File

@ -11,6 +11,7 @@ import java.util.List;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
import org.hibernate.boot.model.naming.EntityNaming; import org.hibernate.boot.model.naming.EntityNaming;
import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.ImplicitCollectionTableNameSource; import org.hibernate.boot.model.naming.ImplicitCollectionTableNameSource;
@ -27,6 +28,7 @@ import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component; import org.hibernate.mapping.Component;
import org.hibernate.mapping.DependantValue; import org.hibernate.mapping.DependantValue;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.JoinedSubclass; import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
@ -64,7 +66,6 @@ public class TableBinder {
private String name; private String name;
private boolean isAbstract; private boolean isAbstract;
private List<UniqueConstraintHolder> uniqueConstraints; private List<UniqueConstraintHolder> uniqueConstraints;
// private List<String[]> uniqueConstraints;
String constraints; String constraints;
private String ownerEntityTable; private String ownerEntityTable;
private String associatedEntityTable; private String associatedEntityTable;
@ -552,7 +553,7 @@ public class TableBinder {
else { else {
bindExplicitColumns( referencedEntity, joinColumns, value, buildingContext, associatedClass ); bindExplicitColumns( referencedEntity, joinColumns, value, buildingContext, associatedClass );
} }
value.createForeignKey(); value.createForeignKey( referencedEntity, joinColumns );
if ( unique ) { if ( unique ) {
value.createUniqueKey(); value.createUniqueKey();
} }
@ -616,35 +617,15 @@ public class TableBinder {
( (Component) key).sortProperties(); ( (Component) key).sortProperties();
} }
// works because the pk has to be on the primary table // works because the pk has to be on the primary table
final Dialect dialect = buildingContext.getMetadataCollector().getDatabase() final InFlightMetadataCollector metadataCollector = buildingContext.getMetadataCollector();
.getJdbcEnvironment().getDialect(); final Dialect dialect = metadataCollector.getDatabase().getJdbcEnvironment().getDialect();
for ( Column column: key.getColumns() ) { for ( int j = 0; j < key.getColumnSpan(); j++ ) {
boolean match = false; if ( !matchUpJoinColumnsWithKeyColumns( referencedEntity, joinColumns, value, metadataCollector, dialect, j ) ) {
// 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 ) {
// we can only get here if there's a dupe PK column in the @JoinColumns // we can only get here if there's a dupe PK column in the @JoinColumns
throw new AnnotationException( throw new AnnotationException(
"An association that targets entity '" + referencedEntity.getEntityName() "An association that targets entity '" + referencedEntity.getEntityName()
+ "' from entity '" + associatedClass.getEntityName() + "' from entity '" + associatedClass.getEntityName()
+ "' has no '@JoinColumn' referencing column '"+ column.getName() + "' has no '@JoinColumn' referencing column '" + key.getColumns().get(j).getName() + "'"
); );
} }
} }
@ -653,6 +634,56 @@ public class TableBinder {
} }
} }
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( private static void bindNonPrimaryKeyReference(
PersistentClass referencedEntity, PersistentClass referencedEntity,
AnnotatedJoinColumns joinColumns, AnnotatedJoinColumns joinColumns,

View File

@ -17,7 +17,6 @@ import java.util.List;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.Remove;
import org.hibernate.boot.model.relational.Exportable; import org.hibernate.boot.model.relational.Exportable;
import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
@ -165,7 +164,7 @@ public abstract class Constraint implements Exportable, Serializable {
/** /**
* @deprecated this method is no longer called * @deprecated this method is no longer called
*/ */
@Deprecated(since="6.2") @Remove @Deprecated(since="6.2", forRemoval = true)
public abstract String sqlConstraintString( public abstract String sqlConstraintString(
SqlStringGenerationContext context, SqlStringGenerationContext context,
String constraintName, String constraintName,

View File

@ -56,7 +56,7 @@ public class ForeignKey extends Constraint {
} }
} }
@Override @Deprecated(since="6.2") @Override @Deprecated(since="6.2", forRemoval = true)
public String sqlConstraintString( public String sqlConstraintString(
SqlStringGenerationContext context, SqlStringGenerationContext context,
String constraintName, String constraintName,
@ -76,17 +76,12 @@ public class ForeignKey extends Constraint {
referencedColumnNames[i] = referencedColumns.get(i).getQuotedName( dialect ); referencedColumnNames[i] = referencedColumns.get(i).getQuotedName( dialect );
} }
final String result = keyDefinition != null ? final String result = keyDefinition != null
dialect.getAddForeignKeyConstraintString( ? dialect.getAddForeignKeyConstraintString( constraintName, keyDefinition )
constraintName, : dialect.getAddForeignKeyConstraintString(
keyDefinition
) :
dialect.getAddForeignKeyConstraintString(
constraintName, constraintName,
columnNames, columnNames,
referencedTable.getQualifiedName( referencedTable.getQualifiedName( context ),
context
),
referencedColumnNames, referencedColumnNames,
isReferenceToPrimaryKey() isReferenceToPrimaryKey()
); );
@ -111,8 +106,6 @@ public class ForeignKey extends Constraint {
} }
public void setReferencedTable(Table referencedTable) throws MappingException { 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; this.referencedTable = referencedTable;
} }

View File

@ -59,15 +59,6 @@ public class ManyToOne extends ToOne {
return resolvedType; return resolvedType;
} }
public void createForeignKey() {
// Ensure properties are sorted before we create a foreign key
sortProperties();
// the case of a foreign key to something other than the pk is handled in createPropertyRefConstraints
if ( isForeignKeyEnabled() && referencedPropertyName==null && !hasFormula() ) {
createForeignKeyOfEntity( ( (EntityType) getType() ).getAssociatedEntityName() );
}
}
@Override @Override
public void createUniqueKey() { public void createUniqueKey() {
if ( !hasFormula() ) { if ( !hasFormula() ) {
@ -75,21 +66,32 @@ public class ManyToOne extends ToOne {
} }
} }
/**
* Creates a {@linkplain ForeignKey foreign key constraint} in the
* case that the foreign key of this association does not reference
* the primary key of the referenced table, but instead some other
* unique key.
* <p>
* We depend here on having a property of the referenced entity
* that does hold the referenced unique key. We might have created
* a "synthetic" composite property for this purpose.
*/
public void createPropertyRefConstraints(Map<String, PersistentClass> persistentClasses) { public void createPropertyRefConstraints(Map<String, PersistentClass> persistentClasses) {
if ( referencedPropertyName != null ) { if ( referencedPropertyName != null ) {
// Ensure properties are sorted before we create a foreign key // Ensure properties are sorted before we create a foreign key
sortProperties(); sortProperties();
PersistentClass pc = persistentClasses.get( getReferencedEntityName() );
final String referencedEntityName = getReferencedEntityName();
Property property = pc.getReferencedProperty( getReferencedPropertyName() ); final String referencedPropertyName = getReferencedPropertyName();
final PersistentClass referencedClass = persistentClasses.get( referencedEntityName );
if (property==null) { if ( referencedClass == null ) {
throw new MappingException( throw new MappingException( "Referenced entity '" + referencedEntityName + "' does not exist" );
"Could not find property " +
getReferencedPropertyName() + }
" on " + final Property property = referencedClass.getReferencedProperty( referencedPropertyName );
getReferencedEntityName() if ( property==null ) {
); throw new MappingException( "Referenced entity '" + referencedEntityName
+ "' has no property named '" + referencedPropertyName + "'" );
} }
else { else {
// Make sure synthetic properties are sorted // Make sure synthetic properties are sorted
@ -98,14 +100,16 @@ public class ManyToOne extends ToOne {
} }
// todo : if "none" another option is to create the ForeignKey object still but to set its #disableCreation flag // todo : if "none" another option is to create the ForeignKey object still but to set its #disableCreation flag
if ( isForeignKeyEnabled() && !hasFormula() ) { if ( isForeignKeyEnabled() && !hasFormula() ) {
ForeignKey fk = getTable().createForeignKey( final ForeignKey foreignKey = getTable().createForeignKey(
getForeignKeyName(), getForeignKeyName(),
getConstraintColumns(), getConstraintColumns(),
( (EntityType) getType() ).getAssociatedEntityName(), ( (EntityType) getType() ).getAssociatedEntityName(),
getForeignKeyDefinition(), getForeignKeyDefinition(),
new ArrayList<>( property.getColumns() ) new ArrayList<>( property.getColumns() )
); );
fk.setOnDeleteAction( getOnDeleteAction() ); //TODO: if the property belongs to a secondary table,
// need to call foreignKey.setReferencedTable(table)
foreignKey.setOnDeleteAction( getOnDeleteAction() );
} }
} }
} }

View File

@ -12,7 +12,6 @@ import java.util.Objects;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection; import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -94,16 +93,6 @@ public class OneToOne extends ToOne {
} }
} }
@Override
public void createForeignKey() throws MappingException {
// Ensure properties are sorted before we create a foreign key
sortProperties();
if ( isForeignKeyEnabled() && constrained && referencedPropertyName==null) {
//TODO: handle the case of a foreign key to something other than the pk
createForeignKeyOfEntity( ( (EntityType) getType() ).getAssociatedEntityName() );
}
}
@Override @Override
public void createUniqueKey() { public void createUniqueKey() {
if ( !hasFormula() && getColumnSpan()>0 ) { if ( !hasFormula() && getColumnSpan()>0 ) {

View File

@ -78,7 +78,7 @@ public class PrimaryKey extends Constraint {
return buf.append(')').toString(); return buf.append(')').toString();
} }
@Override @Deprecated(since="6.2") @Override @Deprecated(since="6.2", forRemoval = true)
public String sqlConstraintString(SqlStringGenerationContext context, String constraintName, String defaultCatalog, String defaultSchema) { public String sqlConstraintString(SqlStringGenerationContext context, String constraintName, String defaultCatalog, String defaultSchema) {
Dialect dialect = context.getDialect(); Dialect dialect = context.getDialect();
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();

View File

@ -29,6 +29,7 @@ import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext; import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext;
import org.hibernate.boot.model.internal.AnnotatedJoinColumns;
import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
@ -342,12 +343,19 @@ public abstract class SimpleValue implements KeyValue {
@Override @Override
public void createForeignKey() throws MappingException {} public void createForeignKey() throws MappingException {}
public void createForeignKey(PersistentClass referencedEntity, AnnotatedJoinColumns joinColumns) throws MappingException {}
@Override @Override
public ForeignKey createForeignKeyOfEntity(String entityName) { public ForeignKey createForeignKeyOfEntity(String entityName) {
if ( isConstrained() ) { if ( isConstrained() ) {
final ForeignKey fk = table.createForeignKey( getForeignKeyName(), getConstraintColumns(), entityName, getForeignKeyDefinition() ); final ForeignKey foreignKey = table.createForeignKey(
fk.setOnDeleteAction( onDeleteAction ); getForeignKeyName(),
return fk; getConstraintColumns(),
entityName,
getForeignKeyDefinition()
);
foreignKey.setOnDeleteAction( onDeleteAction );
return foreignKey;
} }
return null; return null;

View File

@ -554,7 +554,7 @@ public class Table implements Serializable, ContributableDatabaseObject {
foreignKey.setTable( this ); foreignKey.setTable( this );
foreignKey.setReferencedEntityName( referencedEntityName ); foreignKey.setReferencedEntityName( referencedEntityName );
foreignKey.setKeyDefinition( keyDefinition ); foreignKey.setKeyDefinition( keyDefinition );
for (Column keyColumn : keyColumns) { for ( Column keyColumn : keyColumns ) {
foreignKey.addColumn( keyColumn ); foreignKey.addColumn( keyColumn );
} }
if ( referencedColumns != null ) { if ( referencedColumns != null ) {

View File

@ -8,13 +8,18 @@ package org.hibernate.mapping;
import org.hibernate.FetchMode; import org.hibernate.FetchMode;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.boot.model.internal.AnnotatedJoinColumn;
import org.hibernate.boot.model.internal.AnnotatedJoinColumns;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.Mapping;
import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.type.EntityType;
import java.util.Objects; import java.util.Objects;
import static org.hibernate.boot.model.internal.BinderHelper.findReferencedColumnOwner;
/** /**
* A mapping model object representing an association where the target side has cardinality one. * A mapping model object representing an association where the target side has cardinality one.
* *
@ -165,29 +170,72 @@ public abstract class ToOne extends SimpleValue implements Fetchable, SortableVa
@Override @Override
public int[] sortProperties() { public int[] sortProperties() {
final PersistentClass entityBinding = getMetadata().getEntityBinding( getReferencedEntityName() ); final PersistentClass entityBinding = getMetadata().getEntityBinding( referencedEntityName );
if ( entityBinding == null ) { if ( entityBinding != null ) {
return null; final Value value = referencedPropertyName == null
} ? entityBinding.getIdentifier()
final Value value; : entityBinding.getRecursiveProperty( referencedPropertyName ).getValue();
if ( getReferencedPropertyName() == null ) { if ( value instanceof Component ) {
value = entityBinding.getIdentifier(); final Component component = (Component) value;
} final int[] originalPropertyOrder = component.sortProperties();
else { if ( !sorted ) {
value = entityBinding.getRecursiveProperty( getReferencedPropertyName() ).getValue(); if ( originalPropertyOrder != null ) {
} sortColumns( originalPropertyOrder );
if ( value instanceof Component ) { }
final Component component = (Component) value; sorted = true;
final int[] originalPropertyOrder = component.sortProperties();
if ( !sorted ) {
sorted = true;
if ( originalPropertyOrder != null ) {
sortColumns( originalPropertyOrder );
} }
return originalPropertyOrder;
}
else {
sorted = true;
} }
return originalPropertyOrder;
} }
sorted = true;
return null; return null;
} }
@Override
public void createForeignKey(PersistentClass referencedEntity, AnnotatedJoinColumns joinColumns) {
// Ensure properties are sorted before we create a foreign key
sortProperties();
if ( isForeignKeyEnabled() && referencedPropertyName==null && !hasFormula() ) {
if ( isConstrained() ) {
final AnnotatedJoinColumn firstColumn = joinColumns.getJoinColumns().get(0);
final Object owner = findReferencedColumnOwner( referencedEntity, firstColumn, getBuildingContext() );
if ( owner instanceof Join ) {
// Here we handle the case of a foreign key that refers to the
// primary key of a secondary table of the referenced entity
final Join join = (Join) owner;
final ForeignKey foreignKey = getTable().createForeignKey(
getForeignKeyName(),
getConstraintColumns(),
referencedEntity.getEntityName(),
getForeignKeyDefinition(),
join.getKey().getColumns()
);
foreignKey.setOnDeleteAction( getOnDeleteAction() );
foreignKey.setReferencedTable( join.getTable() );
}
else {
// it's just a reference to the primary key of the main table
createForeignKeyOfEntity( referencedEntity.getEntityName() );
}
}
}
}
@Override
public void createForeignKey() {
// Ensure properties are sorted before we create a foreign key
sortProperties();
// A non-null referencedPropertyName tells us that the foreign key
// does not reference the primary key, but some other unique key of
// the referenced table. We do not handle this case here:
// - For ManyToOne, the case of a foreign key to something other than
// the primary key is handled in createPropertyRefConstraints()
// - For OneToOne, we still need to add some similar logic somewhere
// (for now, no foreign key constraint is created)
if ( isForeignKeyEnabled() && referencedPropertyName==null && !hasFormula() ) {
createForeignKeyOfEntity( ( (EntityType) getType() ).getAssociatedEntityName() );
}
}
} }

View File

@ -22,7 +22,7 @@ public class UniqueKey extends Constraint {
private final Map<Column, String> columnOrderMap = new HashMap<>(); private final Map<Column, String> columnOrderMap = new HashMap<>();
private boolean nameExplicit; // true when the constraint name was explicitly specified by @UniqueConstraint annotation private boolean nameExplicit; // true when the constraint name was explicitly specified by @UniqueConstraint annotation
@Override @Deprecated(since="6.2") @Override @Deprecated(since="6.2", forRemoval = true)
public String sqlConstraintString( public String sqlConstraintString(
SqlStringGenerationContext context, SqlStringGenerationContext context,
String constraintName, String constraintName,

View File

@ -55,7 +55,7 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
MappingModelCreationProcess creationProcess) { MappingModelCreationProcess creationProcess) {
super( entityDescriptor, incomingDiscriminatorType, valueMappings, creationProcess ); super( entityDescriptor, incomingDiscriminatorType, valueMappings, creationProcess );
for ( int i = 0; i < discriminatorValues.length; i++ ) { for ( int i = 0; i < discriminatorValues.length; i++ ) {
if ( !discriminatorAbstract[i] ) { if ( !discriminatorAbstract[i] ) {
final String tableName = tableNames[notNullColumnTableNumbers[i]]; final String tableName = tableNames[notNullColumnTableNumbers[i]];
final String subEntityName = subEntityNameByTableName.get( tableName ); final String subEntityName = subEntityNameByTableName.get( tableName );

View File

@ -9,7 +9,6 @@ package org.hibernate.persister.entity;
import org.hibernate.Internal; import org.hibernate.Internal;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.MarkerObject; import org.hibernate.internal.util.MarkerObject;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
import org.hibernate.sql.InFragment; import org.hibernate.sql.InFragment;

View File

@ -754,6 +754,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
} }
} }
@Override
protected boolean needsDiscriminator() { protected boolean needsDiscriminator() {
return forceDiscriminator; return forceDiscriminator;
} }

View File

@ -63,12 +63,12 @@ public class StandardForeignKeyExporter implements Exporter<ForeignKey> {
final StringBuilder buffer = new StringBuilder( dialect.getAlterTableString( sourceTableName ) ) final StringBuilder buffer = new StringBuilder( dialect.getAlterTableString( sourceTableName ) )
.append( .append(
foreignKey.getKeyDefinition() != null ? foreignKey.getKeyDefinition() != null
dialect.getAddForeignKeyConstraintString( ? dialect.getAddForeignKeyConstraintString(
foreignKey.getName(), foreignKey.getName(),
foreignKey.getKeyDefinition() foreignKey.getKeyDefinition()
) : )
dialect.getAddForeignKeyConstraintString( : dialect.getAddForeignKeyConstraintString(
foreignKey.getName(), foreignKey.getName(),
columnNames, columnNames,
targetTableName, targetTableName,

View File

@ -0,0 +1,232 @@
package org.hibernate.orm.test.inheritance.repeatedtable;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.annotations.SecondaryRow;
import org.hibernate.cfg.Configuration;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import java.util.List;
import static jakarta.persistence.CascadeType.ALL;
import static jakarta.persistence.InheritanceType.SINGLE_TABLE;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.hibernate.cfg.AvailableSettings.FORMAT_SQL;
import static org.hibernate.cfg.AvailableSettings.SHOW_SQL;
public class AlternativeToRepeatedTableTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[]{
DataType.class,
ObjectType.class,
SimpleType.class,
Prop.class
};
}
@Override
protected void configure(Configuration configuration) {
super.configure(configuration);
configuration.setProperty(SHOW_SQL, Boolean.toString(true));
configuration.setProperty(FORMAT_SQL, Boolean.toString(true));
}
@Test
public void test_append_properties() {
Long id;
Long sId;
try (Session sess = openSession()) {
Transaction tx = sess.beginTransaction();
SimpleType simpleType = new SimpleType();
simpleType.setName("simple");
simpleType.setCount(69);
sess.persist(simpleType);
sId = simpleType.getId();
ObjectType objectType = new ObjectType();
objectType.setName("name");
sess.persist(objectType);
id = objectType.getId();
tx.commit();
}
try (Session sess = openSession()) {
Transaction tx = sess.beginTransaction();
ObjectType objectType = sess.find(ObjectType.class, id);
Prop property = new Prop();
property.setName("Prop1");
property.setObjectType(objectType);
objectType.getProperties().add(property);
tx.commit();
}
try (Session sess = openSession()) {
Transaction tx = sess.beginTransaction();
ObjectType objectType = sess.find(ObjectType.class, id);
assertEquals(1, objectType.getProperties().size());
tx.commit();
}
try (Session sess = openSession()) {
DataType dataType1 = sess.find(DataType.class, sId);
assertTrue( dataType1 instanceof SimpleType );
DataType dataType2 = sess.find(DataType.class, id);
assertTrue( dataType2 instanceof ObjectType );
}
try (Session sess = openSession()) {
SimpleType simpleType = sess.find(SimpleType.class, sId);
assertNotNull( simpleType );
SimpleType wrongType = sess.find(SimpleType.class, id);
assertNull( wrongType );
}
try (Session sess = openSession()) {
assertEquals( "Prop1",
sess.createQuery("select p.name from AlternativeToRepeatedTableTest$ObjectType ot join ot.properties p")
.getSingleResult() );
}
try (Session sess = openSession()) {
assertEquals( 2, sess.createQuery("from AlternativeToRepeatedTableTest$DataType").getResultList().size() );
assertEquals( 1, sess.createQuery("from AlternativeToRepeatedTableTest$ObjectType").getResultList().size() );
assertEquals( 1, sess.createQuery("from AlternativeToRepeatedTableTest$SimpleType").getResultList().size() );
}
}
@Entity
@Table(name = "DATA_TYPE")
@Inheritance(strategy = SINGLE_TABLE)
@DiscriminatorColumn(name = "supertype_id")
public static abstract class DataType {
private Long id;
private String name;
@Id
@Column(name = "ID")
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity
@DiscriminatorValue("8")
@SecondaryTable(name = "OBJ_TYPE",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "TYPE_ID", referencedColumnName = "ID"))
@SecondaryRow(optional = false)
public static class ObjectType extends DataType {
private String description;
private List<Prop> properties;
@Column(name = "descr", table = "OBJ_TYPE")
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@OneToMany(mappedBy = "objectType", cascade = ALL, orphanRemoval = true)
public List<Prop> getProperties() {
return properties;
}
public void setProperties(List<Prop> properties) {
this.properties = properties;
}
}
@Entity
@Table(name = "PROP")
public static class Prop {
private Long id;
private String name;
private ObjectType objectType;
@Id
@Column(name = "ID")
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// @JoinColumn(name = "OBJ_TYPE_ID")
// @JoinColumn(name = "OBJ_TYPE_ID", referencedColumnName = "ID")
@JoinColumn(name = "OBJ_TYPE_ID", referencedColumnName = "TYPE_ID")
@ManyToOne
public ObjectType getObjectType() {
return objectType;
}
public void setObjectType(ObjectType objectType) {
this.objectType = objectType;
}
}
@Entity
@DiscriminatorValue("2")
public static class SimpleType extends DataType {
Integer count;
@Column(name = "counter")
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
}

View File

@ -136,7 +136,7 @@ public class RepeatedTableTest extends BaseCoreFunctionalTestCase {
private String description; private String description;
private List<Prop> properties; private List<Prop> properties;
@Column(name = "desc") @Column(name = "descr")
public String getDescription() { public String getDescription() {
return description; return description;
} }
@ -200,6 +200,7 @@ public class RepeatedTableTest extends BaseCoreFunctionalTestCase {
public static class SimpleType extends DataType { public static class SimpleType extends DataType {
Integer count; Integer count;
@Column(name = "counter")
public Integer getCount() { public Integer getCount() {
return count; return count;
} }