HHH-14229 Fix unexpected foreign key creation

before this commit, foreign key is created even ConstraintMode.NO_CONSTRAINT present on the @ManyToOne side

(cherry picked from commit 633d0f08d6039c662437be6c83f9eaef3dbcab71)
This commit is contained in:
Yanming Zhou 2020-09-28 10:31:49 +08:00 committed by Gail Badner
parent 96ead8aad6
commit 20f6d4f5db
2 changed files with 137 additions and 24 deletions

View File

@ -51,6 +51,7 @@
import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.OptimisticLock;
import org.hibernate.annotations.OrderBy;
import org.hibernate.annotations.Parameter;
@ -104,6 +105,7 @@
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
@ -557,7 +559,9 @@ public void bind() {
}
//TODO reduce tableBinder != null and oneToMany
XClass collectionType = getCollectionType();
if ( inheritanceStatePerClass == null) throw new AssertionFailure( "inheritanceStatePerClass not set" );
if ( inheritanceStatePerClass == null) {
throw new AssertionFailure( "inheritanceStatePerClass not set" );
}
SecondPass sp = getSecondPass(
fkJoinColumns,
joinColumns,
@ -606,7 +610,9 @@ public void bind() {
binder.setUpdatable( updatable );
Property prop = binder.makeProperty();
//we don't care about the join stuffs because the column is on the association table.
if (! declaringClassSet) throw new AssertionFailure( "DeclaringClass is not set in CollectionBinder while binding" );
if (! declaringClassSet) {
throw new AssertionFailure( "DeclaringClass is not set in CollectionBinder while binding" );
}
propertyHolder.addProperty( prop, declaringClass );
}
@ -614,7 +620,7 @@ private void applySortingAndOrdering(Collection collection) {
boolean hadOrderBy = false;
boolean hadExplicitSort = false;
Class<? extends Comparator> comparatorClass = null;
Class<? extends Comparator<?>> comparatorClass = null;
if ( jpaOrderBy == null && sqlOrderBy == null ) {
if ( deprecatedSort != null ) {
@ -787,8 +793,9 @@ public SecondPass getSecondPass(
final TableBinder assocTableBinder,
final MetadataBuildingContext buildingContext) {
return new CollectionSecondPass( buildingContext, collection ) {
@SuppressWarnings("rawtypes")
@Override
public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedMetas) throws MappingException {
public void secondPass(Map persistentClasses, Map inheritedMetas) throws MappingException {
bindStarToManySecondPass(
persistentClasses,
collType,
@ -811,7 +818,7 @@ public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedM
* return true if it's a Fk, false if it's an association table
*/
protected boolean bindStarToManySecondPass(
Map persistentClasses,
Map<String, PersistentClass> persistentClasses,
XClass collType,
Ejb3JoinColumn[] fkJoinColumns,
Ejb3JoinColumn[] keyColumns,
@ -823,7 +830,7 @@ protected boolean bindStarToManySecondPass(
TableBinder associationTableBinder,
boolean ignoreNotFound,
MetadataBuildingContext buildingContext) {
PersistentClass persistentClass = (PersistentClass) persistentClasses.get( collType.getName() );
PersistentClass persistentClass = persistentClasses.get( collType.getName() );
boolean reversePropertyInJoin = false;
if ( persistentClass != null && StringHelper.isNotEmpty( this.mappedBy ) ) {
try {
@ -884,7 +891,7 @@ protected boolean bindStarToManySecondPass(
protected void bindOneToManySecondPass(
Collection collection,
Map persistentClasses,
Map<String, PersistentClass> persistentClasses,
Ejb3JoinColumn[] fkJoinColumns,
XClass collectionType,
boolean cascadeDeleteEnabled,
@ -906,7 +913,7 @@ protected void bindOneToManySecondPass(
oneToMany.setIgnoreNotFound( ignoreNotFound );
String assocClass = oneToMany.getReferencedEntityName();
PersistentClass associatedClass = (PersistentClass) persistentClasses.get( assocClass );
PersistentClass associatedClass = persistentClasses.get( assocClass );
if ( jpaOrderBy != null ) {
final String orderByFragment = buildOrderByClauseFromHql(
jpaOrderBy.value(),
@ -1223,8 +1230,8 @@ private static SimpleValue buildCollectionKey(
key.setForeignKeyName( StringHelper.nullIfEmpty( collectionTableAnn.foreignKey().name() ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( collectionTableAnn.foreignKey().foreignKeyDefinition() ) );
if ( key.getForeignKeyName() == null &&
key.getForeignKeyDefinition() == null &&
collectionTableAnn.joinColumns().length == 1 ) {
key.getForeignKeyDefinition() == null &&
collectionTableAnn.joinColumns().length == 1 ) {
JoinColumn joinColumn = collectionTableAnn.joinColumns()[0];
key.setForeignKeyName( StringHelper.nullIfEmpty( joinColumn.foreignKey().name() ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) );
@ -1269,15 +1276,25 @@ else if ( fkOverride != null ) {
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( fkOverride.foreignKeyDefinition() ) );
}
else {
final JoinColumn joinColumnAnn = property.getAnnotation( JoinColumn.class );
if ( joinColumnAnn != null ) {
if ( joinColumnAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT
|| joinColumnAnn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) {
key.setForeignKeyName( "none" );
}
else {
key.setForeignKeyName( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().name() ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().foreignKeyDefinition() ) );
final OneToMany oneToManyAnn = property.getAnnotation( OneToMany.class );
final OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
if ( oneToManyAnn != null && !oneToManyAnn.mappedBy().isEmpty()
&& ( onDeleteAnn == null || onDeleteAnn.action() != OnDeleteAction.CASCADE ) ) {
// foreign key should be up to @ManyToOne side
// @OnDelete generate "on delete cascade" foreign key
key.setForeignKeyName( "none" );
}
else {
final JoinColumn joinColumnAnn = property.getAnnotation( JoinColumn.class );
if ( joinColumnAnn != null ) {
if ( joinColumnAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT
|| joinColumnAnn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) {
key.setForeignKeyName( "none" );
}
else {
key.setForeignKeyName( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().name() ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().foreignKeyDefinition() ) );
}
}
}
}
@ -1291,7 +1308,7 @@ else if ( fkOverride != null ) {
private void bindManyToManySecondPass(
Collection collValue,
Map persistentClasses,
Map<String, PersistentClass> persistentClasses,
Ejb3JoinColumn[] joinColumns,
Ejb3JoinColumn[] inverseJoinColumns,
Ejb3Column[] elementColumns,
@ -1307,7 +1324,7 @@ private void bindManyToManySecondPass(
throw new IllegalArgumentException( "null was passed for argument property" );
}
final PersistentClass collectionEntity = (PersistentClass) persistentClasses.get( collType.getName() );
final PersistentClass collectionEntity = persistentClasses.get( collType.getName() );
final String hqlOrderBy = extractHqlOrderBy( jpaOrderBy );
boolean isCollectionOfEntities = collectionEntity != null;
@ -1742,13 +1759,13 @@ public static void bindManytoManyInverseFk(
final String mappedBy = columns[0].getMappedBy();
if ( StringHelper.isNotEmpty( mappedBy ) ) {
final Property property = referencedEntity.getRecursiveProperty( mappedBy );
Iterator mappedByColumns;
Iterator<Selectable> mappedByColumns;
if ( property.getValue() instanceof Collection ) {
mappedByColumns = ( (Collection) property.getValue() ).getKey().getColumnIterator();
}
else {
//find the appropriate reference key, can be in a join
Iterator joinsIt = referencedEntity.getJoinIterator();
Iterator<Join> joinsIt = referencedEntity.getJoinIterator();
KeyValue key = null;
while ( joinsIt.hasNext() ) {
Join join = (Join) joinsIt.next();
@ -1757,7 +1774,9 @@ public static void bindManytoManyInverseFk(
break;
}
}
if ( key == null ) key = property.getPersistentClass().getIdentifier();
if ( key == null ) {
key = property.getPersistentClass().getIdentifier();
}
mappedByColumns = key.getColumnIterator();
}
while ( mappedByColumns.hasNext() ) {

View File

@ -0,0 +1,94 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.foreignkeys.disabled;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.stream.StreamSupport;
import javax.persistence.CascadeType;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.mapping.Table;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
/**
* {@inheritDoc}
*
* @author Yanming Zhou
*/
@TestForIssue(jiraKey = "HHH-14229")
public class OneToManyBidirectionalForeignKeyTest {
private static final String TABLE_NAME_PLAIN = "plain";
private static final String TABLE_NAME_WITH_ON_DELETE = "cascade_delete";
@Test
public void testForeignKeyShouldNotBeCreated() {
Metadata metadata = new MetadataSources(new StandardServiceRegistryBuilder().build())
.addAnnotatedClass(PlainTreeEntity.class).addAnnotatedClass(TreeEntityWithOnDelete.class)
.buildMetadata();
assertTrue(findTable(metadata, TABLE_NAME_PLAIN).getForeignKeys().isEmpty());
assertFalse(findTable(metadata, TABLE_NAME_WITH_ON_DELETE).getForeignKeys().isEmpty());
}
private static Table findTable(Metadata metadata, String tableName) {
return StreamSupport.stream(metadata.getDatabase().getNamespaces().spliterator(), false)
.flatMap(namespace -> namespace.getTables().stream()).filter(t -> t.getName().equals(tableName))
.findFirst().orElse(null);
}
@Entity
@javax.persistence.Table(name = TABLE_NAME_PLAIN)
public static class PlainTreeEntity {
@Id
private Long id;
@ManyToOne
@JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
private PlainTreeEntity parent;
@OneToMany(mappedBy = "parent")
// workaround
// @org.hibernate.annotations.ForeignKey(name = "none")
private Collection<PlainTreeEntity> children = new ArrayList<>(0);
}
@Entity
@javax.persistence.Table(name = TABLE_NAME_WITH_ON_DELETE)
public static class TreeEntityWithOnDelete {
@Id
private Long id;
@ManyToOne
@JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
private TreeEntityWithOnDelete parent;
@OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
@OnDelete(action = OnDeleteAction.CASCADE)
private Collection<TreeEntityWithOnDelete> children = new ArrayList<>(0);
}
}