mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-03-01 15:29:11 +00:00
HHH-7124 : Bind many-to-one foreign key
This commit is contained in:
parent
ebbaf80b5f
commit
35081296d3
@ -353,6 +353,7 @@ private void bindCollectionKey(
|
||||
StringHelper.isEmpty( keySource.getExplicitForeignKeyName() )
|
||||
? null
|
||||
: quotedIdentifier( keySource.getExplicitForeignKeyName() );
|
||||
// TODO: deal with secondary tables...
|
||||
final TableSpecification table = attributeBinding.getContainer().seekEntityBinding().getPrimaryTable();
|
||||
keyBinding.prepareForeignKey( foreignKeyName, collectionTable, table );
|
||||
keyBinding.getForeignKey().setDeleteRule( keySource.getOnDeleteAction() );
|
||||
@ -506,6 +507,110 @@ private CompositeAttributeBinding bindComponentAttribute(
|
||||
return attributeBinding;
|
||||
}
|
||||
|
||||
private ForeignKey createOrLocateForeignKey(
|
||||
String foreignKeyName,
|
||||
SingularAttributeBinding sourceAttributeBinding,
|
||||
SingularAttributeBinding targetAttributeBinding) {
|
||||
if ( sourceAttributeBinding.getRelationalValueBindings().isEmpty() ) {
|
||||
throw new MappingException(
|
||||
String.format(
|
||||
"Cannot create foreign key for attribute (%s) because it has no columns/derived values configured.",
|
||||
sourceAttributeBinding.getAttribute().getName()
|
||||
),
|
||||
bindingContexts.peek().getOrigin()
|
||||
);
|
||||
}
|
||||
if ( targetAttributeBinding.getRelationalValueBindings().isEmpty() ) {
|
||||
throw new MappingException(
|
||||
String.format(
|
||||
"Cannot create foreign key for attribute (%s) because the target attribute (%s) has no columns/derived values configured.",
|
||||
sourceAttributeBinding.getAttribute().getName(),
|
||||
targetAttributeBinding.getAttribute().getName()
|
||||
),
|
||||
bindingContexts.peek().getOrigin()
|
||||
);
|
||||
}
|
||||
// TODO: deal with secondary tables
|
||||
// for now just use the the EntityBinding primary table.
|
||||
return createOrLocateForeignKey(
|
||||
foreignKeyName,
|
||||
sourceAttributeBinding.getContainer().seekEntityBinding().getPrimaryTable(),
|
||||
targetAttributeBinding.getContainer().seekEntityBinding().getPrimaryTable()
|
||||
);
|
||||
}
|
||||
|
||||
private ForeignKey createOrLocateForeignKey(
|
||||
String foreignKeyName,
|
||||
TableSpecification sourceTable,
|
||||
TableSpecification targetTable) {
|
||||
ForeignKey foreignKey = null;
|
||||
if ( foreignKeyName == null ) {
|
||||
// todo: for now lets assume we have to create it, but eventually we should look through the
|
||||
// candidate foreign keys referencing targetTable also...
|
||||
foreignKey = sourceTable.createForeignKey( targetTable, null );
|
||||
} else {
|
||||
foreignKey = sourceTable.locateForeignKey( foreignKeyName );
|
||||
if ( foreignKey == null ) {
|
||||
foreignKey = sourceTable.createForeignKey( targetTable, foreignKeyName );
|
||||
}
|
||||
}
|
||||
return foreignKey;
|
||||
}
|
||||
|
||||
private void bindForeignKeyColumns(
|
||||
ForeignKey foreignKey,
|
||||
SingularAttributeBinding sourceAttributeBinding,
|
||||
SingularAttributeBinding targetAttributeBinding) {
|
||||
final List<RelationalValueBinding> sourceValues = sourceAttributeBinding.getRelationalValueBindings();
|
||||
final List<RelationalValueBinding> targetValues = targetAttributeBinding.getRelationalValueBindings();
|
||||
if ( sourceValues.size() != targetValues.size() ) {
|
||||
throw new MappingException(
|
||||
String.format(
|
||||
"Cannot create foreign key because number of columns in source attribute (%s) is not the same as the number of columns in the target attribute (%s).",
|
||||
sourceAttributeBinding.getAttribute().getName(),
|
||||
targetAttributeBinding.getAttribute().getName()
|
||||
),
|
||||
bindingContexts.peek().getOrigin()
|
||||
);
|
||||
}
|
||||
for ( int i = 0 ; i < sourceValues.size() ; i++ ) {
|
||||
final Value sourceValue = sourceValues.get( i ).getValue();
|
||||
final Value targetValue = targetValues.get( i ).getValue();
|
||||
if ( Column.class.isInstance( sourceValue ) ) {
|
||||
Column sourceColumn = Column.class.cast( sourceValue );
|
||||
if ( !Column.class.isInstance( targetValue ) ) {
|
||||
throw new MappingException(
|
||||
String.format(
|
||||
"Cannot create foreign key for (%s) because the source value is a column (%s) and the corresponding value in the target attribute (%s) is a derived value",
|
||||
sourceAttributeBinding.getAttribute().getName(),
|
||||
sourceColumn.getColumnName(),
|
||||
targetAttributeBinding.getAttribute().getName()
|
||||
),
|
||||
bindingContexts.peek().getOrigin()
|
||||
);
|
||||
}
|
||||
foreignKey.addColumnMapping( Column.class.cast( sourceValue ), Column.class.cast( targetValue ) );
|
||||
}
|
||||
else {
|
||||
if ( Column.class.isInstance( targetValue ) ) {
|
||||
Column targetColumn = Column.class.cast( targetValue );
|
||||
throw new MappingException(
|
||||
String.format(
|
||||
"Cannot create foreign key for (%s) because the source value is a derived value and the corresponding value in the target attribute (%s) is a column (%s).",
|
||||
sourceAttributeBinding.getAttribute().getName(),
|
||||
targetAttributeBinding.getAttribute().getName(),
|
||||
targetColumn.getColumnName()
|
||||
),
|
||||
bindingContexts.peek().getOrigin()
|
||||
);
|
||||
}
|
||||
throw new NotYetImplementedException(
|
||||
"Derived values are not supported when creating a foreign key that targets attribute columns."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bindDiscriminator( final EntityBinding rootEntityBinding, final RootEntitySource rootEntitySource ) {
|
||||
final DiscriminatorSource discriminatorSource = rootEntitySource.getDiscriminatorSource();
|
||||
if ( discriminatorSource == null ) {
|
||||
@ -714,12 +819,14 @@ private ManyToOneAttributeBinding bindManyToOneAttribute(
|
||||
attribute = createSingularAttribute( attributeBindingContainer, attributeSource );
|
||||
}
|
||||
// TODO: figure out which table is used (could be secondary table...)
|
||||
TableSpecification table = attributeBindingContainer.seekEntityBinding().getPrimaryTable();
|
||||
final List< RelationalValueBinding > relationalValueBindings =
|
||||
bindValues(
|
||||
attributeBindingContainer,
|
||||
attributeSource,
|
||||
attribute,
|
||||
attributeBindingContainer.seekEntityBinding().getPrimaryTable() );
|
||||
table
|
||||
);
|
||||
org.hibernate.internal.util.Value< Class< ? >> referencedJavaTypeValue = createSingularAttributeJavaType( attribute );
|
||||
final String referencedEntityName =
|
||||
attributeSource.getReferencedEntityName() != null
|
||||
@ -730,6 +837,17 @@ private ManyToOneAttributeBinding bindManyToOneAttribute(
|
||||
attributeSource.getReferencedEntityAttributeName() == null
|
||||
? referencedEntityBinding.getHierarchyDetails().getEntityIdentifier().getValueBinding()
|
||||
: referencedEntityBinding.locateAttributeBinding( attributeSource.getReferencedEntityAttributeName() );
|
||||
if ( ! referencedAttributeBinding.getAttribute().isSingular() ) {
|
||||
throw new MappingException(
|
||||
String.format( "Many-to-one attribute (%s) references a plural attribute (%s)",
|
||||
attribute.getName(),
|
||||
referencedAttributeBinding.getAttribute().getName()
|
||||
),
|
||||
bindingContexts.peek().getOrigin()
|
||||
);
|
||||
}
|
||||
final SingularAttributeBinding singularReferencedAttributeBinding =
|
||||
(SingularAttributeBinding) referencedAttributeBinding;
|
||||
// todo : we should consider basing references on columns instead of property-ref, which would require a resolution (later) of property-ref to column names
|
||||
final ManyToOneAttributeBinding attributeBinding =
|
||||
attributeBindingContainer.makeManyToOneAttributeBinding(
|
||||
@ -738,10 +856,18 @@ private ManyToOneAttributeBinding bindManyToOneAttribute(
|
||||
attributeSource.isIncludedInOptimisticLocking(),
|
||||
attributeSource.isLazy(),
|
||||
createMetaAttributeContext( attributeBindingContainer, attributeSource ),
|
||||
referencedAttributeBinding,
|
||||
singularReferencedAttributeBinding,
|
||||
relationalValueBindings );
|
||||
// TODO: is this needed?
|
||||
referencedAttributeBinding.addEntityReferencingAttributeBinding( attributeBinding );
|
||||
// Foreign key...
|
||||
ForeignKey foreignKey = createOrLocateForeignKey(
|
||||
attributeSource.getForeignKeyName(),
|
||||
attributeBinding,
|
||||
singularReferencedAttributeBinding
|
||||
);
|
||||
bindForeignKeyColumns( foreignKey, attributeBinding, singularReferencedAttributeBinding );
|
||||
// Type resolution...
|
||||
if ( !attribute.isTypeResolved() ) {
|
||||
attribute.resolveType( referencedEntityBinding.getEntity() );
|
||||
}
|
||||
@ -846,17 +972,9 @@ private void bindSecondaryTables( final EntityBinding entityBinding, final Entit
|
||||
final TableSpecification table = createTable( secondaryTableSource.getTableSource(), null );
|
||||
// todo: really need a concept like SecondaryTableSource in the binding model as well
|
||||
// so that EntityBinding can know the proper foreign key to use to build SQL statements.
|
||||
ForeignKey foreignKey = null;
|
||||
if ( secondaryTableSource.getForeignKeyName() == null ) {
|
||||
// todo: for now lets assume we have to create it, but eventually we should look through the
|
||||
// candidate foreign keys referencing primary table also...
|
||||
foreignKey = table.createForeignKey( primaryTable, null );
|
||||
} else {
|
||||
foreignKey = table.locateForeignKey( secondaryTableSource.getForeignKeyName() );
|
||||
if ( foreignKey == null ) {
|
||||
foreignKey = table.createForeignKey( primaryTable, secondaryTableSource.getForeignKeyName() );
|
||||
}
|
||||
}
|
||||
ForeignKey foreignKey = createOrLocateForeignKey(
|
||||
secondaryTableSource.getForeignKeyName(), table, primaryTable
|
||||
);
|
||||
for ( final PrimaryKeyJoinColumnSource joinColumnSource : secondaryTableSource.getJoinColumns() ) {
|
||||
// todo : currently we only support columns here, not formulas
|
||||
// todo : apply naming strategy to infer missing column name
|
||||
|
@ -115,7 +115,7 @@ public ManyToOneAttributeBinding makeManyToOneAttributeBinding(
|
||||
boolean includedInOptimisticLocking,
|
||||
boolean lazy,
|
||||
MetaAttributeContext metaAttributeContext,
|
||||
AttributeBinding referencedAttributeBinding,
|
||||
SingularAttributeBinding referencedAttributeBinding,
|
||||
List<RelationalValueBinding> valueBindings);
|
||||
|
||||
/**
|
||||
|
@ -185,7 +185,7 @@ public ManyToOneAttributeBinding makeManyToOneAttributeBinding(
|
||||
boolean includedInOptimisticLocking,
|
||||
boolean lazy,
|
||||
MetaAttributeContext metaAttributeContext,
|
||||
AttributeBinding referencedAttributeBinding,
|
||||
SingularAttributeBinding referencedAttributeBinding,
|
||||
List<RelationalValueBinding> valueBindings) {
|
||||
final ManyToOneAttributeBinding binding = new ManyToOneAttributeBinding(
|
||||
this,
|
||||
|
@ -531,7 +531,7 @@ public ManyToOneAttributeBinding makeManyToOneAttributeBinding(
|
||||
boolean includedInOptimisticLocking,
|
||||
boolean lazy,
|
||||
MetaAttributeContext metaAttributeContext,
|
||||
AttributeBinding referencedAttributeBinding,
|
||||
SingularAttributeBinding referencedAttributeBinding,
|
||||
List<RelationalValueBinding> valueBindings) {
|
||||
final ManyToOneAttributeBinding binding = new ManyToOneAttributeBinding(
|
||||
this,
|
||||
|
@ -47,7 +47,7 @@ public class ManyToOneAttributeBinding
|
||||
|
||||
private final List<RelationalValueBinding> relationalValueBindings;
|
||||
|
||||
private final AttributeBinding referencedAttributeBinding;
|
||||
private final SingularAttributeBinding referencedAttributeBinding;
|
||||
|
||||
private boolean isLogicalOneToOne;
|
||||
|
||||
@ -62,7 +62,7 @@ public ManyToOneAttributeBinding(
|
||||
boolean includedInOptimisticLocking,
|
||||
boolean lazy,
|
||||
MetaAttributeContext metaAttributeContext,
|
||||
AttributeBinding referencedAttributeBinding,
|
||||
SingularAttributeBinding referencedAttributeBinding,
|
||||
List<RelationalValueBinding> relationalValueBindings) {
|
||||
super(
|
||||
container,
|
||||
@ -196,38 +196,6 @@ public final EntityBinding getReferencedEntityBinding() {
|
||||
return (EntityBinding) referencedAttributeBinding.getContainer();
|
||||
}
|
||||
|
||||
// private void buildForeignKey() {
|
||||
// // TODO: move this stuff to relational model
|
||||
// ForeignKey foreignKey = getValue().getTable()
|
||||
// .createForeignKey( referencedAttributeBinding.getValue().getTable(), foreignKeyName );
|
||||
// Iterator<SimpleValue> referencingValueIterator = getSimpleValues().iterator();
|
||||
// Iterator<SimpleValue> targetValueIterator = referencedAttributeBinding.getSimpleValues().iterator();
|
||||
// while ( referencingValueIterator.hasNext() ) {
|
||||
// if ( !targetValueIterator.hasNext() ) {
|
||||
// // TODO: improve this message
|
||||
// throw new MappingException(
|
||||
// "number of values in many-to-one reference is greater than number of values in target"
|
||||
// );
|
||||
// }
|
||||
// SimpleValue referencingValue = referencingValueIterator.next();
|
||||
// SimpleValue targetValue = targetValueIterator.next();
|
||||
// if ( Column.class.isInstance( referencingValue ) ) {
|
||||
// if ( !Column.class.isInstance( targetValue ) ) {
|
||||
// // TODO improve this message
|
||||
// throw new MappingException( "referencing value is a column, but target is not a column" );
|
||||
// }
|
||||
// foreignKey.addColumnMapping( Column.class.cast( referencingValue ), Column.class.cast( targetValue ) );
|
||||
// }
|
||||
// else if ( Column.class.isInstance( targetValue ) ) {
|
||||
// // TODO: improve this message
|
||||
// throw new MappingException( "referencing value is not a column, but target is a column." );
|
||||
// }
|
||||
// }
|
||||
// if ( targetValueIterator.hasNext() ) {
|
||||
// throw new MappingException( "target value has more simple values than referencing value" );
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void validate() {
|
||||
// // can't check this until both the domain and relational states are initialized...
|
||||
// if ( getCascadeTypes().contains( CascadeType.DELETE_ORPHAN ) ) {
|
||||
|
@ -24,6 +24,7 @@
|
||||
package org.hibernate.metamodel.spi.binding;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@ -42,8 +43,10 @@
|
||||
import org.hibernate.metamodel.spi.domain.SingularAttribute;
|
||||
import org.hibernate.metamodel.internal.MetadataImpl;
|
||||
import org.hibernate.metamodel.spi.relational.Column;
|
||||
import org.hibernate.metamodel.spi.relational.ForeignKey;
|
||||
import org.hibernate.metamodel.spi.relational.Identifier;
|
||||
import org.hibernate.metamodel.spi.relational.JdbcDataType;
|
||||
import org.hibernate.metamodel.spi.relational.TableSpecification;
|
||||
import org.hibernate.metamodel.spi.relational.Value;
|
||||
import org.hibernate.metamodel.spi.source.MetadataImplementor;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
@ -133,7 +136,9 @@ public void testEntityWithManyToOneMapping() {
|
||||
metadata,
|
||||
entityWithManyToOneBinding,
|
||||
attributeBinding,
|
||||
simpleEntityBinding.getHierarchyDetails().getEntityIdentifier().getValueBinding(),
|
||||
SingularAttributeBinding.class.cast(
|
||||
simpleEntityBinding.getHierarchyDetails().getEntityIdentifier().getValueBinding()
|
||||
),
|
||||
"`simpleEntity`"
|
||||
);
|
||||
|
||||
@ -141,7 +146,7 @@ public void testEntityWithManyToOneMapping() {
|
||||
metadata,
|
||||
entityWithManyToOneBinding,
|
||||
entityWithManyToOneBinding.locateAttributeBinding( "simpleEntityFromPropertyRef" ),
|
||||
simpleEntityBinding.locateAttributeBinding( "name" ),
|
||||
SingularAttributeBinding.class.cast( simpleEntityBinding.locateAttributeBinding( "name" ) ),
|
||||
"`simplename`"
|
||||
);
|
||||
}
|
||||
@ -150,7 +155,7 @@ private void checkManyToOneAttributeBinding(
|
||||
MetadataImplementor metadata,
|
||||
EntityBinding entityWithManyToOneBinding,
|
||||
AttributeBinding attributeBinding,
|
||||
AttributeBinding referencedAttributeBinding,
|
||||
SingularAttributeBinding referencedAttributeBinding,
|
||||
String manyToOneColumnName) {
|
||||
final EntityBinding referencedEntityBinding = referencedAttributeBinding.getContainer().seekEntityBinding();
|
||||
final String referencedEntityName = referencedEntityBinding.getEntity().getName();
|
||||
@ -223,6 +228,25 @@ private void checkManyToOneAttributeBinding(
|
||||
assertEquals( referencedJdbcDataType.getTypeCode(), jdbcDataType.getTypeCode() );
|
||||
assertEquals( referencedJdbcDataType.getJavaType(), jdbcDataType.getJavaType() );
|
||||
assertEquals( referencedJdbcDataType.getTypeName(), jdbcDataType.getTypeName() );
|
||||
|
||||
// locate the foreignKey
|
||||
boolean sourceColumnFound = false;
|
||||
for ( ForeignKey fk : column.getTable().getForeignKeys() ) {
|
||||
for ( Column sourceColumn : fk.getSourceColumns() ) {
|
||||
if ( sourceColumn == column ) {
|
||||
assertFalse( "source column not found in more than one foreign key", sourceColumnFound );
|
||||
sourceColumnFound = true;
|
||||
Iterator<Column> targetColumnIterator = fk.getTargetColumns().iterator();
|
||||
assertTrue( targetColumnIterator.hasNext() );
|
||||
assertSame(
|
||||
referencedAttributeBinding.getRelationalValueBindings().iterator().next().getValue(),
|
||||
targetColumnIterator.next()
|
||||
);
|
||||
assertFalse( targetColumnIterator.hasNext() );
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue( "foreign key with specified source column found", sourceColumnFound );
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
x
Reference in New Issue
Block a user