From 3bf9dd6743382056cd0a96096386575a7c9ee8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sn=C5=8Dwball?= Date: Thu, 23 May 2019 16:04:40 +0200 Subject: [PATCH 1/2] Testcase for ebean with circular dependences 1. While the javax.persistance api discourages the use of bidirectional cascades, it does not prohibit them. They're very useful and natural if you think of the database as a graph instead of a tree. test case 1 (testCircularCascade) tests this. 2. Second test case fetches the child, but saves the parent. If used like that, the child will not have it's changes saved. --- .../tests/update/TestUpdateCircularSave.java | 75 +++++++++++++++++++ .../java/org/tests/update/objects/Child.java | 52 +++++++++++++ .../java/org/tests/update/objects/Parent.java | 43 +++++++++++ .../org/tests/update/objects/SiblingA.java | 43 +++++++++++ .../org/tests/update/objects/SiblingB.java | 53 +++++++++++++ 5 files changed, 266 insertions(+) create mode 100644 src/test/java/org/tests/update/TestUpdateCircularSave.java create mode 100644 src/test/java/org/tests/update/objects/Child.java create mode 100644 src/test/java/org/tests/update/objects/Parent.java create mode 100644 src/test/java/org/tests/update/objects/SiblingA.java create mode 100644 src/test/java/org/tests/update/objects/SiblingB.java diff --git a/src/test/java/org/tests/update/TestUpdateCircularSave.java b/src/test/java/org/tests/update/TestUpdateCircularSave.java new file mode 100644 index 000000000..34b78c760 --- /dev/null +++ b/src/test/java/org/tests/update/TestUpdateCircularSave.java @@ -0,0 +1,75 @@ +package org.tests.update; + +import static org.junit.Assert.assertTrue; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import org.junit.Test; +import org.tests.update.objects.SiblingA; +import org.tests.update.objects.Parent; +import org.tests.update.objects.SiblingB; +import org.tests.update.objects.Child; + +public class TestUpdateCircularSave extends BaseTestCase { + + @Test + public void testCircularCascade() { + + long aId = createA(); + modifyPropertyToTrue(aId); + + SiblingA siblingA = Ebean.find(SiblingA.class, aId); + assert siblingA != null; + assertTrue(siblingA.getSiblingB().isTestProperty()); + } + + private void modifyPropertyToTrue(long aId) { + SiblingA siblingA = Ebean.find(SiblingA.class, aId); + assert siblingA != null; + + final SiblingB siblingB = siblingA.getSiblingB(); + siblingB.setTestProperty(true); + // Will get optimistic lock as version is increased twice on B + Ebean.save(siblingB); + } + + private long createA() { + SiblingA siblingA = new SiblingA(); + SiblingB siblingB = new SiblingB(); + siblingA.setSiblingB(siblingB); + + Ebean.save(siblingA); + return siblingA.getId(); + } + + + @Test + public void testFetchChildSaveParent() { + + long dId = createCAndD(); + modifyPropertyToTrue2(dId); + + Parent parent = Ebean.find(Parent.class, dId); + assert parent != null; + // Fails here because D was not saved even though C has cascade = ALL + assertTrue(parent.getChild().isTestProperty()); + } + + private void modifyPropertyToTrue2(long dId) { + Child child = Ebean.find(Child.class, dId); + assert child != null; + + child.setTestProperty(true); + final Parent parent = child.getParent(); + Ebean.save(parent); + } + + private long createCAndD() { + Parent parent = new Parent(); + Child child = new Child(); + parent.setChild(child); + + Ebean.save(parent); + return parent.getChild().getId(); + } +} diff --git a/src/test/java/org/tests/update/objects/Child.java b/src/test/java/org/tests/update/objects/Child.java new file mode 100644 index 000000000..37a70d9b6 --- /dev/null +++ b/src/test/java/org/tests/update/objects/Child.java @@ -0,0 +1,52 @@ +package org.tests.update.objects; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Version; + +@Entity +@Table(name = "e_save_test_d") +public class Child { + @Id + private Long id; + @Version + private long version = 0L; + @OneToOne(mappedBy = "child") + private Parent parent; + + private boolean testProperty = false; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + + public boolean isTestProperty() { + return testProperty; + } + + public void setTestProperty(boolean testProperty) { + this.testProperty = testProperty; + } +} diff --git a/src/test/java/org/tests/update/objects/Parent.java b/src/test/java/org/tests/update/objects/Parent.java new file mode 100644 index 000000000..edd587969 --- /dev/null +++ b/src/test/java/org/tests/update/objects/Parent.java @@ -0,0 +1,43 @@ +package org.tests.update.objects; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Version; + +@Entity +@Table(name = "e_save_test_c") +public class Parent { + @Id + private Long id; + @Version + private long version = 0L; + @OneToOne(cascade = CascadeType.ALL) + private Child child; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + public Child getChild() { + return child; + } + + public void setChild(Child child) { + this.child = child; + } +} diff --git a/src/test/java/org/tests/update/objects/SiblingA.java b/src/test/java/org/tests/update/objects/SiblingA.java new file mode 100644 index 000000000..2e5806657 --- /dev/null +++ b/src/test/java/org/tests/update/objects/SiblingA.java @@ -0,0 +1,43 @@ +package org.tests.update.objects; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Version; + +@Entity +@Table(name = "e_save_test_a") +public class SiblingA { + @Id + private Long id; + @Version + private long version = 0L; + @OneToOne(cascade = CascadeType.ALL, mappedBy = "siblingA") + private SiblingB siblingB; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + public SiblingB getSiblingB() { + return siblingB; + } + + public void setSiblingB(SiblingB siblingB) { + this.siblingB = siblingB; + } +} diff --git a/src/test/java/org/tests/update/objects/SiblingB.java b/src/test/java/org/tests/update/objects/SiblingB.java new file mode 100644 index 000000000..63b358645 --- /dev/null +++ b/src/test/java/org/tests/update/objects/SiblingB.java @@ -0,0 +1,53 @@ +package org.tests.update.objects; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Version; + +@Entity +@Table(name = "e_save_test_b") +public class SiblingB { + @Id + private Long id; + @Version + private long version = 0L; + @OneToOne(cascade = CascadeType.ALL) + private SiblingA siblingA; + + private boolean testProperty = false; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + public SiblingA getSiblingA() { + return siblingA; + } + + public void setSiblingA(SiblingA siblingA) { + this.siblingA = siblingA; + } + + public boolean isTestProperty() { + return testProperty; + } + + public void setTestProperty(boolean testProperty) { + this.testProperty = testProperty; + } +} From 52644647e404b177c1715b398661d75656ffcc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sn=C5=8Dwball?= Date: Mon, 27 May 2019 10:06:09 +0200 Subject: [PATCH 2/2] Add another test case for circular dependencies --- .../tests/update/TestUpdateCircularSave.java | 43 +++++++++++++------ .../java/org/tests/update/objects/Child.java | 2 +- .../java/org/tests/update/objects/Parent.java | 2 +- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/test/java/org/tests/update/TestUpdateCircularSave.java b/src/test/java/org/tests/update/TestUpdateCircularSave.java index 34b78c760..6d82e5fe0 100644 --- a/src/test/java/org/tests/update/TestUpdateCircularSave.java +++ b/src/test/java/org/tests/update/TestUpdateCircularSave.java @@ -1,5 +1,6 @@ package org.tests.update; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import io.ebean.BaseTestCase; @@ -44,32 +45,48 @@ public class TestUpdateCircularSave extends BaseTestCase { @Test - public void testFetchChildSaveParent() { + public void testFetchChildModifyChildSaveParent() { - long dId = createCAndD(); - modifyPropertyToTrue2(dId); + long childId = createParentAndChild().getChild().getId(); - Parent parent = Ebean.find(Parent.class, dId); - assert parent != null; - // Fails here because D was not saved even though C has cascade = ALL - assertTrue(parent.getChild().isTestProperty()); - } - - private void modifyPropertyToTrue2(long dId) { - Child child = Ebean.find(Child.class, dId); + Child child = Ebean.find(Child.class, childId); assert child != null; child.setTestProperty(true); final Parent parent = child.getParent(); Ebean.save(parent); + + assertChildModified(childId); } - private long createCAndD() { + private void assertChildModified(long childId) { + Parent parent = Ebean.find(Parent.class, childId); + assert parent != null; + // Fails here because D was not saved even though C has cascade = ALL + assertTrue(parent.getChild().isTestProperty()); + assertEquals(parent.getChild().getVersion(), 2L); + } + + private Parent createParentAndChild() { Parent parent = new Parent(); Child child = new Child(); parent.setChild(child); Ebean.save(parent); - return parent.getChild().getId(); + return parent; + } + + + @Test + public void testFetchParentModifyChildSaveParent() { + long parentId = createParentAndChild().getId(); + + Parent parent = Ebean.find(Parent.class, parentId); + assert parent != null; + + parent.getChild().setTestProperty(true); + Ebean.save(parent); + + assertChildModified(parent.getChild().getId()); } } diff --git a/src/test/java/org/tests/update/objects/Child.java b/src/test/java/org/tests/update/objects/Child.java index 37a70d9b6..b8a91a2b7 100644 --- a/src/test/java/org/tests/update/objects/Child.java +++ b/src/test/java/org/tests/update/objects/Child.java @@ -13,7 +13,7 @@ public class Child { private Long id; @Version private long version = 0L; - @OneToOne(mappedBy = "child") + @OneToOne private Parent parent; private boolean testProperty = false; diff --git a/src/test/java/org/tests/update/objects/Parent.java b/src/test/java/org/tests/update/objects/Parent.java index edd587969..bdcfa58f9 100644 --- a/src/test/java/org/tests/update/objects/Parent.java +++ b/src/test/java/org/tests/update/objects/Parent.java @@ -14,7 +14,7 @@ public class Parent { private Long id; @Version private long version = 0L; - @OneToOne(cascade = CascadeType.ALL) + @OneToOne(cascade = CascadeType.ALL, mappedBy = "parent") private Child child; public Long getId() {