From ddc00fedb60f1ddfeedd2e147f3bfef258e89e74 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Thu, 31 May 2018 10:55:58 +1200 Subject: [PATCH] #1397 - @DbForeignKey(noConstraint = true) is ignored on @ManyToMany ... foreign constraints still added --- .../build/ModelBuildIntersectionTable.java | 11 ++-- .../server/deploy/BeanPropertyAssoc.java | 10 ++++ .../server/deploy/BeanPropertyAssocOne.java | 8 --- .../deploy/meta/DeployBeanPropertyAssoc.java | 12 ++++- .../meta/DeployBeanPropertyAssocOne.java | 11 ---- .../deploy/parse/AnnotationAssocManys.java | 8 +++ .../java/org/tests/ddl/DfkNoneViaMtoM.java | 54 +++++++++++++++++++ .../org/tests/ddl/TestForeignKeyModes.java | 35 +++++++++++- 8 files changed, 124 insertions(+), 25 deletions(-) create mode 100644 src/test/java/org/tests/ddl/DfkNoneViaMtoM.java diff --git a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildIntersectionTable.java b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildIntersectionTable.java index 6772e57d6..9c2cf29ec 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildIntersectionTable.java +++ b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildIntersectionTable.java @@ -5,6 +5,7 @@ import io.ebeaninternal.dbmigration.model.MTable; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import io.ebeaninternal.server.deploy.PropertyForeignKey; import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.deploy.TableJoinColumn; @@ -48,10 +49,12 @@ class ModelBuildIntersectionTable { private void buildFkConstraints() { - ctx.fkeyBuilder(intersectionTable) - .addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true) - .addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false); - + PropertyForeignKey foreignKey = manyProp.getForeignKey(); + if (foreignKey == null || !foreignKey.isNoConstraint()) { + ctx.fkeyBuilder(intersectionTable) + .addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true) + .addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false); + } intersectionTable.checkDuplicateForeignKeys(); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java index 7fa20d8d6..bb4fab41b 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -65,6 +65,8 @@ public abstract class BeanPropertyAssoc extends BeanProperty implements STree */ final TableJoin tableJoin; + final PropertyForeignKey foreignKey; + /** * The type of the joined bean. */ @@ -90,6 +92,7 @@ public abstract class BeanPropertyAssoc extends BeanProperty implements STree */ public BeanPropertyAssoc(BeanDescriptor descriptor, DeployBeanPropertyAssoc deploy) { super(descriptor, deploy); + this.foreignKey = deploy.getForeignKey(); this.extraWhere = InternString.intern(deploy.getExtraWhere()); this.beanTable = deploy.getBeanTable(); this.mappedBy = InternString.intern(deploy.getMappedBy()); @@ -128,6 +131,13 @@ public abstract class BeanPropertyAssoc extends BeanProperty implements STree return fetchPreference; } + /** + * Return the extra configuration for the foreign key. + */ + public PropertyForeignKey getForeignKey() { + return foreignKey; + } + /** * Create a ElPropertyValue for a *ToOne or *ToMany. */ diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 25f84abe1..d29e12946 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -44,8 +44,6 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr private final boolean primaryKeyExport; - private final PropertyForeignKey foreignKey; - private AssocOneHelp localHelp; protected final BeanProperty[] embeddedProps; @@ -73,8 +71,6 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr DeployBeanPropertyAssocOne deploy) { super(descriptor, deploy); - - foreignKey = deploy.getForeignKey(); primaryKeyExport = deploy.isPrimaryKeyExport(); oneToOne = deploy.isOneToOne(); oneToOneExported = deploy.isOneToOneExported(); @@ -323,10 +319,6 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr } } - public PropertyForeignKey getForeignKey() { - return foreignKey; - } - public boolean hasForeignKey() { return foreignKey == null || !foreignKey.isNoConstraint(); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java index cbe3dc48f..69d9dc747 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.meta; import io.ebeaninternal.server.deploy.BeanCascadeInfo; import io.ebeaninternal.server.deploy.BeanTable; +import io.ebeaninternal.server.deploy.PropertyForeignKey; /** * Abstract base for properties mapped to an associated bean, list, set or map. @@ -16,7 +17,7 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { /** * Persist settings. */ - protected final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo(); + final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo(); /** * The join table information. @@ -42,6 +43,8 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { private int fetchPreference = 1000; + private PropertyForeignKey foreignKey; + /** * Construct the property. */ @@ -117,6 +120,13 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { return cascadeInfo; } + public void setForeignKey(PropertyForeignKey foreignKey) { + this.foreignKey = foreignKey; + } + + public PropertyForeignKey getForeignKey() { + return foreignKey; + } /** * Return the mappedBy deployment attribute. diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java index 920d0141e..4dd42a503 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java @@ -1,6 +1,5 @@ package io.ebeaninternal.server.deploy.meta; -import io.ebeaninternal.server.deploy.PropertyForeignKey; import io.ebeaninternal.server.query.SqlJoinType; import javax.persistence.CascadeType; @@ -22,8 +21,6 @@ public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { private String columnPrefix; - private PropertyForeignKey foreignKey; - private boolean orphanRemoval; /** @@ -148,14 +145,6 @@ public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { } } - public void setForeignKey(PropertyForeignKey foreignKey) { - this.foreignKey = foreignKey; - } - - public PropertyForeignKey getForeignKey() { - return foreignKey; - } - public void setOrphanRemoval(boolean orphanRemoval) { this.orphanRemoval = orphanRemoval; } diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java index 68d3171d4..b628c290b 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.deploy.parse; +import io.ebean.annotation.DbForeignKey; import io.ebean.annotation.FetchPreference; import io.ebean.annotation.HistoryExclude; import io.ebean.annotation.PrivateOwned; @@ -12,6 +13,7 @@ import io.ebean.util.StringHelper; import io.ebeaninternal.server.deploy.BeanDescriptorManager; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanTable; +import io.ebeaninternal.server.deploy.PropertyForeignKey; import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; @@ -95,6 +97,12 @@ class AnnotationAssocManys extends AnnotationParser { readElementCollection(prop, elementCollection); } + // for ManyToMany typically to disable foreign keys from intersection table + DbForeignKey dbForeignKey = get(prop, DbForeignKey.class); + if (dbForeignKey != null){ + prop.setForeignKey(new PropertyForeignKey(dbForeignKey)); + } + if (get(prop, HistoryExclude.class) != null) { prop.setExcludedFromHistory(); } diff --git a/src/test/java/org/tests/ddl/DfkNoneViaMtoM.java b/src/test/java/org/tests/ddl/DfkNoneViaMtoM.java new file mode 100644 index 000000000..9b781e3c8 --- /dev/null +++ b/src/test/java/org/tests/ddl/DfkNoneViaMtoM.java @@ -0,0 +1,54 @@ +package org.tests.ddl; + +import io.ebean.annotation.DbForeignKey; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class DfkNoneViaMtoM { + + @Id + long id; + + String name; + + /** + * Have no Foreign key constraints on the intersection table. + */ + @ManyToMany(cascade = CascadeType.ALL) + @DbForeignKey(noConstraint = true) + List ones = new ArrayList<>(); + + public DfkNoneViaMtoM(String name) { + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getOnes() { + return ones; + } + + public void setOnes(List ones) { + this.ones = ones; + } +} diff --git a/src/test/java/org/tests/ddl/TestForeignKeyModes.java b/src/test/java/org/tests/ddl/TestForeignKeyModes.java index a9b1216b2..113c72e16 100644 --- a/src/test/java/org/tests/ddl/TestForeignKeyModes.java +++ b/src/test/java/org/tests/ddl/TestForeignKeyModes.java @@ -48,6 +48,40 @@ public class TestForeignKeyModes extends BaseTestCase { assertThat(found.getOne()).isNotNull(); } + @Test + public void noneViaManyToMany() { + + DfkOne one = Ebean.getReference(DfkOne.class, 999L); + + DfkNoneViaMtoM none = new DfkNoneViaMtoM("none2"); + none.getOnes().add(one); + + // Would normally fail as DfkOne id:999 is not actually in Database + // and with the foreign key constraint on the intersection table the + // insert into the intersection table would fail + Ebean.save(none); + + DfkNoneViaMtoM found = Ebean.find(DfkNoneViaMtoM.class) + .setId(none.getId()) + .fetch("ones", "id") + .findOne(); + + assertThat(found).isNotNull(); + + // but we can't actually fetch it via ORM M2M ... as it joins across + // to DfkOne and that doesn't exist + assertThat(found.getOnes()).isNotNull(); + assertThat(found.getOnes()).hasSize(0); + + LoggedSqlCollector.start(); + Ebean.delete(none); + + List sql = LoggedSqlCollector.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("delete from dfk_none_via_mto_m_dfk_one where dfk_none_via_mto_m_id = ?"); + assertThat(sql.get(1)).contains("delete from dfk_none_via_mto_m where id=?"); + } + @Test public void setNullOnDelete() { @@ -63,7 +97,6 @@ public class TestForeignKeyModes extends BaseTestCase { DfkSetNull found = Ebean.find(DfkSetNull.class, other.getId()); assertThat(found).isNotNull(); assertThat(found.getOne()).isNull(); - } @Test