mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-21 04:37:22 +00:00
PR #3656 fix - lazy initialised O2M relation are not correctly persisted on subsequent saves (#3793)
- Root cause: SaveManyBeans.removeAssocManyOrphans() only called setModifyListening when insertedParent=true. A first save with null O2M skipped it; the lazily-initialized collection on subsequent saves had no listen mode → .clear() untracked → orphan not deleted.
- Fix: Added else { setListenMode(c, many); } to set the listen mode for existing collections that were never initialized (uses the existing null-guard helper).
- Tests: testModifyListenModeSet2 now passes; all 26 cascade tests green.
-------------
Original #3656 description:
We found an issue, when O2M relations are not correctly persisted to the DB.
This happens, when
a bean is saved and the O2M property is empty
something is added and cleared again in two subsequent saves.
the same master-bean object has to be used
The issue here is, that the BeanCollection is lazily initialized with no modifyListenMode set after the first save.
This happens only for O2M relations with no order column. (Others work fine) See: https://github.com/ebean-orm/ebean-agent/blob/d4c40f1ce85c58f99cb0a85152aaa0a0075a9c01/ebean-agent/src/main/java/io/ebean/enhance/entity/FieldMeta.java#L469
And we need also a save, where the bean is saved with an empty/null value in the O2M property.
Subsequent saves will not update the modifyListenMode. See SaveManyBeans
if (insertedParent) {
// after insert set the modify listening mode for private owned etc
c.setModifyListening(many.modifyListenMode());
}
We found this in one of our unit-tests, where we've configured a bean for different states. It is probably something, that should not be too critical in real code, as you normally save a bean only once (When the bean was retrieved from DB, it should not occur)
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
This commit is contained in:
co-authored by
robin.bygrave
parent
ca19a78c23
commit
490c70a7b7
@@ -345,6 +345,10 @@ final class SaveManyBeans extends SaveManyBase {
|
||||
if (insertedParent) {
|
||||
// after insert set the modify listening mode for private owned etc
|
||||
c.setModifyListening(many.modifyListenMode());
|
||||
} else {
|
||||
// a lazily initialized collection (e.g. first save had null value) has no
|
||||
// listen mode yet - set it now so that subsequent modifications are tracked
|
||||
setListenMode(c, many);
|
||||
}
|
||||
// We must not reset when we still have to update other entities in the collection and set their new orderColumn value
|
||||
if (!hasOrderColumn) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package org.tests.cascade;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.xtest.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.test.LoggedSql;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.tests.model.basic.Section;
|
||||
import org.tests.model.basic.SubSection;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -149,6 +152,44 @@ public class TestOrderedList extends BaseTestCase {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifyListenModeSet1() {
|
||||
final OmOrderedMaster master = new OmOrderedMaster("Master");
|
||||
final OmOrderedDetail detail1 = new OmOrderedDetail("Detail1");
|
||||
DB.save(detail1);
|
||||
DB.save(master);
|
||||
|
||||
master.getDetails().add(detail1);
|
||||
|
||||
DB.save(master);
|
||||
|
||||
master.getDetails().clear();
|
||||
|
||||
DB.save(master);
|
||||
|
||||
OmOrderedMaster masterDb = DB.find(OmOrderedMaster.class, master.getId());
|
||||
assertThat(masterDb.getDetails()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifyListenModeSet2() {
|
||||
final Section master = new Section();
|
||||
final SubSection detail1 = new SubSection();
|
||||
DB.save(detail1);
|
||||
DB.save(master);
|
||||
|
||||
master.getSubSections().add(detail1);
|
||||
|
||||
DB.save(master);
|
||||
|
||||
master.getSubSections().clear();
|
||||
|
||||
DB.save(master);
|
||||
|
||||
Section masterDb = DB.find(Section.class, master.getId());
|
||||
assertThat(masterDb.getSubSections()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifyListWithCache() {
|
||||
final OmCacheOrderedMaster master = new OmCacheOrderedMaster("Master");
|
||||
|
||||
Reference in New Issue
Block a user