From 74174444bbee8f2505af8ba08f9076d9fb2b0af2 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 12 May 2022 23:14:26 +1200 Subject: [PATCH] Bug - OneToMany orphanRemoval = true, replace collection adding back original collection entry - Adding back a bean from the original collection - Expect that bean to exist in the final result but, it's missing Fix here is in SaveManyBeans to add a insertAllChildren and use ebi.setNew(); for beans to force them to insert for this case. --- .../server/persist/SaveManyBeans.java | 7 ++- .../TestOrphanCollectionReplacement.java | 51 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 ebean-test/src/test/java/org/tests/cascade/TestOrphanCollectionReplacement.java diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java index f6f2f241c..57b18f312 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java @@ -35,6 +35,7 @@ public final class SaveManyBeans extends SaveManyBase { private final boolean untouchedBeanCollection; private final Collection collection; private int sortOrder; + private boolean insertAllChildren; SaveManyBeans(DefaultPersister persister, boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { super(persister, insertedParent, many, parentBean, request); @@ -176,8 +177,11 @@ public final class SaveManyBeans extends SaveManyBase { skipSavingThisBean = false; // set the parent bean to detailBean many.setJoinValuesToChild(parentBean, detail, mapKeyValue); + } else if (insertAllChildren) { + ebi.setNew(); + skipSavingThisBean = false; + many.setJoinValuesToChild(parentBean, detail, mapKeyValue); } else { - // unmodified so skip depending on prop.isSaveRecurseSkippable(); skipSavingThisBean = saveRecurseSkippable; } } @@ -342,6 +346,7 @@ public final class SaveManyBeans extends SaveManyBase { if (!(value instanceof BeanCollection)) { if (!insertedParent && cascade && isChangedProperty()) { persister.addToFlushQueue(many.deleteByParentId(request.beanId(), null), transaction, 0); + insertAllChildren = true; } } else { BeanCollection c = (BeanCollection) value; diff --git a/ebean-test/src/test/java/org/tests/cascade/TestOrphanCollectionReplacement.java b/ebean-test/src/test/java/org/tests/cascade/TestOrphanCollectionReplacement.java new file mode 100644 index 000000000..8dbedc9b3 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/cascade/TestOrphanCollectionReplacement.java @@ -0,0 +1,51 @@ +package org.tests.cascade; + +import io.ebean.DB; +import io.ebean.xtest.BaseTestCase; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class TestOrphanCollectionReplacement extends BaseTestCase { + + @Test + void replaceCollection_whenOrphan_expect_forcedInsert() { + long parentId; + { // setup + List children = new ArrayList<>(); + children.add(new COOneMany("c0")); + children.add(new COOneMany("c1")); + + COOne parent = new COOne("p0"); + parent.setChildren(children); + + DB.save(parent); + parentId = parent.getId(); + } + + { // act + COOne fetchedParent = DB.find(COOne.class, parentId); + assert fetchedParent != null; + + COOneMany role = new COOneMany("c2"); + + List filtered = fetchedParent.getChildren().stream().filter(r -> "c0".equals(r.getName())).collect(Collectors.toList()); + + List updatedRoles = new ArrayList<>(); + updatedRoles.addAll(filtered); + updatedRoles.addAll(List.of(role)); + fetchedParent.setChildren(updatedRoles); + + DB.save(fetchedParent); + } + + COOne fetchedUser2 = DB.find(COOne.class, parentId); + requireNonNull(fetchedUser2); + assertEquals(2, fetchedUser2.getChildren().size()); + } +}