HHH-15932 allow @XxxxToOne associations to target a secondary table
This commit is contained in:
parent
c7bad70073
commit
014847f41b
|
@ -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() ) );
|
||||
}
|
||||
|
|
|
@ -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() + "'" );
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -754,6 +754,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean needsDiscriminator() {
|
||||
return forceDiscriminator;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue