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.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -84,7 +83,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
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;
@ -117,6 +115,9 @@ import jakarta.persistence.Embeddable;
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}.
@ -1957,10 +1958,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
}
}
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() ) {
@ -1968,34 +1967,27 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
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() );
// the ForeignKeys created in the first pass did not have their referenced table initialized
if ( foreignKey.getReferencedTable() == null ) {
foreignKey.setReferencedTable( referencedClass.getTable() );
}
Identifier nameIdentifier;
nameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy()
final Identifier nameIdentifier = getMetadataBuildingOptions().getImplicitNamingStrategy()
.determineForeignKeyName( new ForeignKeyNameSource( foreignKey, table, buildingContext ) );
foreignKey.setName( nameIdentifier.render( getDatabase().getJdbcEnvironment().getDialect() ) );
foreignKey.alignColumns();
@ -2005,10 +1997,10 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
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 ) );
}
@ -2018,10 +2010,10 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
@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() ) );
}

View File

@ -284,8 +284,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
* 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
}
@ -298,7 +297,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
+ 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
@ -306,7 +305,10 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
}
}
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() ) {
@ -342,7 +344,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
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() + "'" );
}

View File

@ -336,11 +336,11 @@ public class BinderHelper {
}
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

View File

@ -2551,7 +2551,7 @@ public abstract class CollectionBinder {
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 ) {
@ -2623,7 +2623,7 @@ public abstract class CollectionBinder {
AnnotatedJoinColumns joinColumns,
SimpleValue value,
boolean unique) {
if (isUnownedCollection()) {
if ( isUnownedCollection() ) {
bindUnownedManyToManyInverseForeignKey( targetEntity, joinColumns, value );
}
else {
@ -2663,7 +2663,7 @@ public abstract class CollectionBinder {
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);

View File

@ -11,6 +11,7 @@ import java.util.List;
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;
@ -27,6 +28,7 @@ import org.hibernate.mapping.Collection;
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;
@ -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;
@ -552,7 +553,7 @@ public class TableBinder {
else {
bindExplicitColumns( referencedEntity, joinColumns, value, buildingContext, associatedClass );
}
value.createForeignKey();
value.createForeignKey( referencedEntity, joinColumns );
if ( unique ) {
value.createUniqueKey();
}
@ -616,35 +617,15 @@ public class TableBinder {
( (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() + "'"
);
}
}
@ -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(
PersistentClass referencedEntity,
AnnotatedJoinColumns joinColumns,

View File

@ -17,7 +17,6 @@ import java.util.List;
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;
@ -165,7 +164,7 @@ public abstract class Constraint implements Exportable, Serializable {
/**
* @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,

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(
SqlStringGenerationContext context,
String constraintName,
@ -76,17 +76,12 @@ public class ForeignKey extends Constraint {
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()
);
@ -111,8 +106,6 @@ public class ForeignKey extends Constraint {
}
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;
}

View File

@ -59,15 +59,6 @@ public class ManyToOne extends ToOne {
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
public void createUniqueKey() {
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) {
if ( referencedPropertyName != null ) {
// Ensure properties are sorted before we create a foreign key
sortProperties();
PersistentClass pc = persistentClasses.get( getReferencedEntityName() );
Property property = pc.getReferencedProperty( getReferencedPropertyName() );
if (property==null) {
throw new MappingException(
"Could not find property " +
getReferencedPropertyName() +
" on " +
getReferencedEntityName()
);
final String referencedEntityName = getReferencedEntityName();
final String referencedPropertyName = getReferencedPropertyName();
final PersistentClass referencedClass = persistentClasses.get( referencedEntityName );
if ( referencedClass == null ) {
throw new MappingException( "Referenced entity '" + referencedEntityName + "' does not exist" );
}
final Property property = referencedClass.getReferencedProperty( referencedPropertyName );
if ( property==null ) {
throw new MappingException( "Referenced entity '" + referencedEntityName
+ "' has no property named '" + referencedPropertyName + "'" );
}
else {
// 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
if ( isForeignKeyEnabled() && !hasFormula() ) {
ForeignKey fk = getTable().createForeignKey(
final ForeignKey foreignKey = getTable().createForeignKey(
getForeignKeyName(),
getConstraintColumns(),
( (EntityType) getType() ).getAssociatedEntityName(),
getForeignKeyDefinition(),
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.boot.spi.MetadataBuildingContext;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
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
public void createUniqueKey() {
if ( !hasFormula() && getColumnSpan()>0 ) {

View File

@ -78,7 +78,7 @@ public class PrimaryKey extends Constraint {
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) {
Dialect dialect = context.getDialect();
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.spi.ConverterDescriptor;
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.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
@ -342,12 +343,19 @@ public abstract class SimpleValue implements KeyValue {
@Override
public void createForeignKey() throws MappingException {}
public void createForeignKey(PersistentClass referencedEntity, AnnotatedJoinColumns joinColumns) throws MappingException {}
@Override
public ForeignKey createForeignKeyOfEntity(String entityName) {
if ( isConstrained() ) {
final ForeignKey fk = table.createForeignKey( getForeignKeyName(), getConstraintColumns(), entityName, getForeignKeyDefinition() );
fk.setOnDeleteAction( onDeleteAction );
return fk;
final ForeignKey foreignKey = table.createForeignKey(
getForeignKeyName(),
getConstraintColumns(),
entityName,
getForeignKeyDefinition()
);
foreignKey.setOnDeleteAction( onDeleteAction );
return foreignKey;
}
return null;

View File

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

View File

@ -8,13 +8,18 @@ package org.hibernate.mapping;
import org.hibernate.FetchMode;
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.spi.MetadataBuildingContext;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.type.EntityType;
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.
*
@ -165,29 +170,72 @@ public abstract class ToOne extends SimpleValue implements Fetchable, SortableVa
@Override
public int[] sortProperties() {
final PersistentClass entityBinding = getMetadata().getEntityBinding( getReferencedEntityName() );
if ( entityBinding == null ) {
return null;
}
final Value value;
if ( getReferencedPropertyName() == null ) {
value = entityBinding.getIdentifier();
}
else {
value = entityBinding.getRecursiveProperty( getReferencedPropertyName() ).getValue();
}
if ( value instanceof Component ) {
final Component component = (Component) value;
final int[] originalPropertyOrder = component.sortProperties();
if ( !sorted ) {
sorted = true;
if ( originalPropertyOrder != null ) {
sortColumns( originalPropertyOrder );
final PersistentClass entityBinding = getMetadata().getEntityBinding( referencedEntityName );
if ( entityBinding != null ) {
final Value value = referencedPropertyName == null
? entityBinding.getIdentifier()
: entityBinding.getRecursiveProperty( referencedPropertyName ).getValue();
if ( value instanceof Component ) {
final Component component = (Component) value;
final int[] originalPropertyOrder = component.sortProperties();
if ( !sorted ) {
if ( originalPropertyOrder != null ) {
sortColumns( originalPropertyOrder );
}
sorted = true;
}
return originalPropertyOrder;
}
else {
sorted = true;
}
return originalPropertyOrder;
}
sorted = true;
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 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(
SqlStringGenerationContext context,
String constraintName,

View File

@ -55,7 +55,7 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
MappingModelCreationProcess 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] ) {
final String tableName = tableNames[notNullColumnTableNumbers[i]];
final String subEntityName = subEntityNameByTableName.get( tableName );

View File

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

View File

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

View File

@ -63,12 +63,12 @@ public class StandardForeignKeyExporter implements Exporter<ForeignKey> {
final StringBuilder buffer = new StringBuilder( dialect.getAlterTableString( sourceTableName ) )
.append(
foreignKey.getKeyDefinition() != null ?
dialect.getAddForeignKeyConstraintString(
foreignKey.getKeyDefinition() != null
? dialect.getAddForeignKeyConstraintString(
foreignKey.getName(),
foreignKey.getKeyDefinition()
) :
dialect.getAddForeignKeyConstraintString(
)
: dialect.getAddForeignKeyConstraintString(
foreignKey.getName(),
columnNames,
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 List<Prop> properties;
@Column(name = "desc")
@Column(name = "descr")
public String getDescription() {
return description;
}
@ -200,6 +200,7 @@ public class RepeatedTableTest extends BaseCoreFunctionalTestCase {
public static class SimpleType extends DataType {
Integer count;
@Column(name = "counter")
public Integer getCount() {
return count;
}