From 80309f844ef494ee8f22f5441cc9328dcc209bf6 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Thu, 17 Oct 2019 12:32:13 +1300 Subject: [PATCH] #1844 - Persisting an update of ElementCollection with JDBC batch, ensure delete, insert batch order --- .../server/core/PersistRequestBean.java | 62 ++++++----------- .../ebeaninternal/server/core/Persister.java | 12 +++- .../server/deploy/BeanPropertyAssocMany.java | 2 +- .../deploy/BeanPropertyAssocManySqlHelp.java | 4 +- .../server/persist/DefaultPersister.java | 19 ++++-- .../server/persist/SaveMany.java | 13 ++++ .../server/persist/SaveManyBase.java | 12 ++-- .../server/persist/SaveManyBeans.java | 13 ++-- .../persist/SaveManyElementCollection.java | 52 +++++++++----- .../persist/SaveManyElementCollectionMap.java | 55 +++++++++------ .../model/elementcollection/EcsmChild.java | 68 +++++++++++++++++++ .../model/elementcollection/EcsmParent.java | 61 +++++++++++++++++ .../TestElementCollectionBasic.java | 30 ++++++++ .../TestElementCollectionBasicMap.java | 10 +-- .../TestElementCollectionCascade.java | 66 ++++++++++++++++++ .../TestElementCollectionCascadeMultiple.java | 9 ++- 16 files changed, 379 insertions(+), 109 deletions(-) create mode 100644 src/main/java/io/ebeaninternal/server/persist/SaveMany.java create mode 100644 src/test/java/org/tests/model/elementcollection/EcsmChild.java create mode 100644 src/test/java/org/tests/model/elementcollection/EcsmParent.java create mode 100644 src/test/java/org/tests/model/elementcollection/TestElementCollectionCascade.java diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java index 320dc1816..0804a0604 100644 --- a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java @@ -27,7 +27,7 @@ import io.ebeaninternal.server.persist.BatchedSqlException; import io.ebeaninternal.server.persist.DeleteMode; import io.ebeaninternal.server.persist.Flags; import io.ebeaninternal.server.persist.PersistExecute; -import io.ebeaninternal.server.persist.SaveManyBeans; +import io.ebeaninternal.server.persist.SaveMany; import io.ebeaninternal.server.transaction.BeanPersistIdMap; import io.ebeanservice.docstore.api.DocStoreUpdate; import io.ebeanservice.docstore.api.DocStoreUpdateContext; @@ -169,10 +169,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP */ private boolean getterCallback; - /** - * postUpdate notifications. Used to combine bean and element update updates into single postUpdate event. - */ - private int pendingPostUpdateNotify; + private boolean pendingPostUpdateNotify; /** * Set to true when post execute has occurred (so includes batch flush). @@ -187,7 +184,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP /** * Many to many intersection table changes that are held for later batch processing. */ - private List saveManyIntersections; + private List saveMany; public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, SpiTransaction t, PersistExecute persistExecute, PersistRequest.Type type, int flags) { @@ -889,12 +886,8 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } private void postUpdateNotify() { - if (pendingPostUpdateNotify > 0) { - // invoke the delayed postUpdate notification (combined with element collection update) + if (pendingPostUpdateNotify) { controller.postUpdate(this); - } else { - // batched update with no element collection, send postUpdate notification once it executes - pendingPostUpdateNotify = -1; } } @@ -917,6 +910,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP */ @Override public void postExecute() { + saveQueuedMany(); postExecute = true; if (controller != null) { controllerPost(); @@ -944,12 +938,11 @@ public final class PersistRequestBean extends PersistRequest implements BeanP if (isLogSummary()) { logSummary(); } - saveQueuedManyIntersection(); } - private void saveQueuedManyIntersection() { - if (saveManyIntersections != null) { - saveManyIntersections.forEach(SaveManyBeans::saveIntersectionBatch); + private void saveQueuedMany() { + if (saveMany != null) { + saveMany.forEach(SaveMany::saveBatch); } } @@ -960,19 +953,14 @@ public final class PersistRequestBean extends PersistRequest implements BeanP if (controller != null && !dirty) { // fire preUpdate notification when only element collection updated controller.preUpdate(this); - } - } - - /** - * Combine with the beans postUpdate event notification. - */ - public boolean postElementCollectionUpdate() { - if (controller != null) { - pendingPostUpdateNotify += 2; + pendingPostUpdateNotify = true; } if (!dirty) { setNotifyCache(); } + } + + public boolean isNotifyCache() { return notifyCache; } @@ -982,13 +970,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP controller.postInsert(this); break; case UPDATE: - if (pendingPostUpdateNotify == -1) { - // notify now - batched bean update with no element collection - controller.postUpdate(this); - } else { - // delay notify to combine with element collection update - pendingPostUpdateNotify++; - } + controller.postUpdate(this); break; case DELETE_SOFT: controller.postSoftDelete(this); @@ -1105,7 +1087,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP updatedManysOnly = true; setNotifyCache(); addPostCommitListeners(); - saveQueuedManyIntersection(); + saveQueuedMany(); } notifyCacheOnComplete(); postUpdateNotify(); @@ -1444,20 +1426,20 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } /** - * Return true if the intersection table updates should be queued and batched. + * Return true if the intersection table updates or element collection updates should be queued. */ - public boolean isQueueManyIntersection() { + public boolean isQueueSaveMany() { return !postExecute; } /** - * The intersection table updates to the batch executed later on postExecute. + * The intersection table updates or element collection to the batch executed later on postExecute. */ - public void addManyIntersection(SaveManyBeans saveManyIntersection) { - if (this.saveManyIntersections == null) { - this.saveManyIntersections = new ArrayList<>(); + public void addSaveMany(SaveMany saveManyRequest) { + if (this.saveMany == null) { + this.saveMany = new ArrayList<>(); } - this.saveManyIntersections.add(saveManyIntersection); + this.saveMany.add(saveManyRequest); } public boolean isForcedUpdate() { @@ -1471,7 +1453,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP saveRecurse = true; } - public void setGeneratedId() { + private void setGeneratedId() { beanDescriptor.setGeneratedId(entityBean, transaction); } } diff --git a/src/main/java/io/ebeaninternal/server/core/Persister.java b/src/main/java/io/ebeaninternal/server/core/Persister.java index 2bcda4b88..b8bb132e1 100644 --- a/src/main/java/io/ebeaninternal/server/core/Persister.java +++ b/src/main/java/io/ebeaninternal/server/core/Persister.java @@ -104,8 +104,18 @@ public interface Persister { void executeOrQueue(SpiSqlUpdate update, SpiTransaction t, boolean queue); /** - * Add the statement to JDBC batch for later execution via executeBatch. + * Queue the SqlUpdate for early execution (with JDBC batch). */ + void addToFlushQueue(SpiSqlUpdate update, SpiTransaction t); + + /** + * Queue the SqlUpdate for late execution (with JDBC batch). + */ + void addToFlushQueueLast(SpiSqlUpdate update, SpiTransaction t); + + /** + * Add the statement to JDBC batch for later execution via executeBatch. + */ void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction); /** diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 28722043e..06e2b61ec 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -963,7 +963,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST return !elementCollection && cascadeInfo.isDelete(); } - public String insertElementCollection() { + public SpiSqlUpdate insertElementCollection() { return sqlHelp.insertElementCollection(); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java index 3882f349c..0cd4831de 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java @@ -59,8 +59,8 @@ class BeanPropertyAssocManySqlHelp { return sb.toString(); } - String insertElementCollection() { - return elementCollectionInsertSql; + SpiSqlUpdate insertElementCollection() { + return new DefaultSqlUpdate(elementCollectionInsertSql); } private static class Cols extends BaseTablePropertyVisitor { diff --git a/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java index 9d893c9fe..1d6ab2437 100644 --- a/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java +++ b/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java @@ -151,6 +151,16 @@ public final class DefaultPersister implements Persister { } } + @Override + public void addToFlushQueue(SpiSqlUpdate update, SpiTransaction t) { + addToFlushQueue(update, t, true); + } + + @Override + public void addToFlushQueueLast(SpiSqlUpdate update, SpiTransaction t) { + addToFlushQueue(update, t, false); + } + private void addToFlushQueue(SpiSqlUpdate update, SpiTransaction t, boolean early) { new PersistRequestUpdateSql(server, update, t, persistExecute).addToFlushQueue(early); } @@ -949,18 +959,15 @@ public final class DefaultPersister implements Persister { private SaveManyBase saveManyRequest(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { if (!many.isElementCollection()) { - return new SaveManyBeans(insertedParent, many, parentBean, request, this); - + return new SaveManyBeans(this, insertedParent, many, parentBean, request); } else if (many.getManyType().isMap()) { - return new SaveManyElementCollectionMap(insertedParent, many, parentBean, request); - + return new SaveManyElementCollectionMap(this, insertedParent, many, parentBean, request); } else { - return new SaveManyElementCollection(insertedParent, many, parentBean, request); + return new SaveManyElementCollection(this, insertedParent, many, parentBean, request); } } void deleteManyIntersection(EntityBean bean, BeanPropertyAssocMany many, SpiTransaction t, boolean publish, boolean queue) { - SpiSqlUpdate sqlDelete = deleteAllIntersection(bean, many, publish); if (queue) { addToFlushQueue(sqlDelete, t, true); diff --git a/src/main/java/io/ebeaninternal/server/persist/SaveMany.java b/src/main/java/io/ebeaninternal/server/persist/SaveMany.java new file mode 100644 index 000000000..f4c826cd8 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/persist/SaveMany.java @@ -0,0 +1,13 @@ +package io.ebeaninternal.server.persist; + +/** + * Save many that can be queued up to execute after the associated + * bean has been actually been persisted. + */ +public interface SaveMany { + + /** + * Save the many property (after the associated bean persist). + */ + void saveBatch(); +} diff --git a/src/main/java/io/ebeaninternal/server/persist/SaveManyBase.java b/src/main/java/io/ebeaninternal/server/persist/SaveManyBase.java index 3c8ab0777..aaf52fb64 100644 --- a/src/main/java/io/ebeaninternal/server/persist/SaveManyBase.java +++ b/src/main/java/io/ebeaninternal/server/persist/SaveManyBase.java @@ -14,10 +14,11 @@ import java.io.IOException; /** * Base for saving entity bean collections and element collections. */ -abstract class SaveManyBase { +abstract class SaveManyBase implements SaveMany { private static final Logger log = LoggerFactory.getLogger(SaveManyBase.class); + final DefaultPersister persister; final PersistRequestBean request; final SpiEbeanServer server; final boolean insertedParent; @@ -26,7 +27,8 @@ abstract class SaveManyBase { final EntityBean parentBean; final Object value; - SaveManyBase(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { + SaveManyBase(DefaultPersister persister, boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { + this.persister = persister; this.request = request; this.server = request.getServer(); this.insertedParent = insertedParent; @@ -41,10 +43,10 @@ abstract class SaveManyBase { */ abstract void save(); - void preElementCollectionUpdate(Object parentId) { + void preElementCollectionUpdate() { if (!insertedParent) { request.preElementCollectionUpdate(); - server.execute(many.deleteByParentId(parentId, null), transaction); + persister.addToFlushQueue(many.deleteByParentId(request.getBeanId(), null), transaction); } } @@ -64,7 +66,7 @@ abstract class SaveManyBase { void postElementCollectionUpdate() { if (!insertedParent) { - if (request.postElementCollectionUpdate()) { + if (request.isNotifyCache()) { try { String asJson = many.jsonWriteCollection(value); request.addCollectionChange(many.getName(), asJson); diff --git a/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java b/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java index 883947439..653d387f1 100644 --- a/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java +++ b/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java @@ -37,12 +37,10 @@ public class SaveManyBeans extends SaveManyBase { private final DeleteMode deleteMode; private Collection collection; - private final DefaultPersister persister; private int sortOrder; - SaveManyBeans(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request, DefaultPersister persister) { - super(insertedParent, many, parentBean, request); - this.persister = persister; + SaveManyBeans(DefaultPersister persister, boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { + super(persister, insertedParent, many, parentBean, request); this.cascade = many.getCascadeInfo().isSave(); this.publish = request.isPublish(); this.targetDescriptor = many.getTargetDescriptor(); @@ -238,9 +236,9 @@ public class SaveManyBeans extends SaveManyBase { if (value == null) { return; } - if (request.isQueueManyIntersection()) { + if (request.isQueueSaveMany()) { // queue/delay until bean persist request is flushed - request.addManyIntersection(this); + request.addSaveMany(this); } else { saveAssocManyIntersection(false); } @@ -249,7 +247,8 @@ public class SaveManyBeans extends SaveManyBase { /** * Push intersection table changes onto batch flush queue. */ - public void saveIntersectionBatch() { + @Override + public void saveBatch() { saveAssocManyIntersection(true); } diff --git a/src/main/java/io/ebeaninternal/server/persist/SaveManyElementCollection.java b/src/main/java/io/ebeaninternal/server/persist/SaveManyElementCollection.java index 63215c145..be8874a03 100644 --- a/src/main/java/io/ebeaninternal/server/persist/SaveManyElementCollection.java +++ b/src/main/java/io/ebeaninternal/server/persist/SaveManyElementCollection.java @@ -1,7 +1,7 @@ package io.ebeaninternal.server.persist; -import io.ebean.SqlUpdate; import io.ebean.bean.EntityBean; +import io.ebeaninternal.api.SpiSqlUpdate; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.deploy.BeanCollectionUtil; import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; @@ -13,28 +13,44 @@ import java.util.Collection; */ class SaveManyElementCollection extends SaveManyBase { - SaveManyElementCollection(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { - super(insertedParent, many, parentBean, request); + private Collection collection; + + SaveManyElementCollection(DefaultPersister persister, boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { + super(persister, insertedParent, many, parentBean, request); + } + + private boolean modifiedCollection() { + return collection != null && (insertedParent || BeanCollectionUtil.isModified(value)); } @Override void save() { - - Collection collection = BeanCollectionUtil.getActualEntries(value); - if (collection != null && (insertedParent || BeanCollectionUtil.isModified(value))) { - Object parentId = request.getBeanId(); - preElementCollectionUpdate(parentId); - - transaction.depth(+1); - SqlUpdate sqlInsert = server.createSqlUpdate(many.insertElementCollection()); - for (Object value : collection) { - sqlInsert.setNextParameter(parentId); - many.bindElementValue(sqlInsert, value); - server.execute(sqlInsert, transaction); + collection = BeanCollectionUtil.getActualEntries(value); + if (modifiedCollection()) { + preElementCollectionUpdate(); + if (insertedParent && request.isQueueSaveMany()) { + request.addSaveMany(this); + } else { + saveCollection(); } - transaction.depth(-1); - resetModifyState(); - postElementCollectionUpdate(); } } + + @Override + public void saveBatch() { + saveCollection(); + } + + private void saveCollection() { + SpiSqlUpdate proto = many.insertElementCollection(); + Object parentId = request.getBeanId(); + for (Object value : collection) { + final SpiSqlUpdate sqlInsert = proto.copy(); + sqlInsert.setNextParameter(parentId); + many.bindElementValue(sqlInsert, value); + persister.addToFlushQueueLast(sqlInsert, transaction); + } + resetModifyState(); + postElementCollectionUpdate(); + } } diff --git a/src/main/java/io/ebeaninternal/server/persist/SaveManyElementCollectionMap.java b/src/main/java/io/ebeaninternal/server/persist/SaveManyElementCollectionMap.java index 41511262d..66d084650 100644 --- a/src/main/java/io/ebeaninternal/server/persist/SaveManyElementCollectionMap.java +++ b/src/main/java/io/ebeaninternal/server/persist/SaveManyElementCollectionMap.java @@ -1,7 +1,7 @@ package io.ebeaninternal.server.persist; -import io.ebean.SqlUpdate; import io.ebean.bean.EntityBean; +import io.ebeaninternal.api.SpiSqlUpdate; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.deploy.BeanCollectionUtil; import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; @@ -14,31 +14,46 @@ import java.util.Set; */ class SaveManyElementCollectionMap extends SaveManyBase { - SaveManyElementCollectionMap(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { - super(insertedParent, many, parentBean, request); + private Set> entries; + + SaveManyElementCollectionMap(DefaultPersister persister, boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { + super(persister, insertedParent, many, parentBean, request); + } + + private boolean modifiedCollection() { + return entries != null && (insertedParent || BeanCollectionUtil.isModified(value)); } @SuppressWarnings("unchecked") @Override void save() { - - Set> entries = (Set>) BeanCollectionUtil.getActualEntries(value); - if (entries != null && (insertedParent || BeanCollectionUtil.isModified(value))) { - Object parentId = request.getBeanId(); - preElementCollectionUpdate(parentId); - - transaction.depth(+1); - SqlUpdate sqlInsert = server.createSqlUpdate(many.insertElementCollection()); - for (Map.Entry entry : entries) { - sqlInsert.setNextParameter(parentId); - sqlInsert.setNextParameter(entry.getKey()); - many.bindElementValue(sqlInsert, entry.getValue()); - server.execute(sqlInsert, transaction); + entries = (Set>) BeanCollectionUtil.getActualEntries(value); + if (modifiedCollection()) { + preElementCollectionUpdate(); + if (insertedParent && request.isQueueSaveMany()) { + request.addSaveMany(this); + } else { + saveCollection(); } - - transaction.depth(-1); - resetModifyState(); - postElementCollectionUpdate(); } } + + @Override + public void saveBatch() { + saveCollection(); + } + + private void saveCollection() { + SpiSqlUpdate proto = many.insertElementCollection(); + Object parentId = request.getBeanId(); + for (Map.Entry entry : entries) { + final SpiSqlUpdate sqlInsert = proto.copy(); + sqlInsert.setNextParameter(parentId); + sqlInsert.setNextParameter(entry.getKey()); + many.bindElementValue(sqlInsert, entry.getValue()); + persister.addToFlushQueueLast(sqlInsert, transaction); + } + resetModifyState(); + postElementCollectionUpdate(); + } } diff --git a/src/test/java/org/tests/model/elementcollection/EcsmChild.java b/src/test/java/org/tests/model/elementcollection/EcsmChild.java new file mode 100644 index 000000000..5299f4dca --- /dev/null +++ b/src/test/java/org/tests/model/elementcollection/EcsmChild.java @@ -0,0 +1,68 @@ +package org.tests.model.elementcollection; + +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Version; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; + +@Entity +public class EcsmChild { + + @Id + UUID oneId; + + String name; + + @ElementCollection + @CollectionTable(name = "ecsm_values", joinColumns = @JoinColumn(name = "host_id", referencedColumnName = "one_id")) + Set values = new LinkedHashSet<>(); + + @Version + long version; + + public EcsmChild(String name) { + this.name = name; + } + + @Override + public String toString() { + return "id:" + oneId + " name:" + name + " values:" + values; + } + + public UUID getOneId() { + return oneId; + } + + public void setOneId(UUID oneId) { + this.oneId = oneId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + public Set getValues() { + return values; + } + + public void setValues(Set values) { + this.values = values; + } +} diff --git a/src/test/java/org/tests/model/elementcollection/EcsmParent.java b/src/test/java/org/tests/model/elementcollection/EcsmParent.java new file mode 100644 index 000000000..f9c319a03 --- /dev/null +++ b/src/test/java/org/tests/model/elementcollection/EcsmParent.java @@ -0,0 +1,61 @@ +package org.tests.model.elementcollection; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Version; +import java.util.List; + +import static javax.persistence.CascadeType.ALL; + +@Entity +public class EcsmParent { + + @Id + private long id; + + @Version + private long version; + + private String name; + + @OneToMany(cascade = ALL) + private List children; + + public EcsmParent(String name) { + this.name = name; + } + + 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 String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + +} diff --git a/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasic.java b/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasic.java index ceff9db4f..38e08ef91 100644 --- a/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasic.java +++ b/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasic.java @@ -16,6 +16,36 @@ public class TestElementCollectionBasic extends BaseTestCase { return EcPersonPersistAdapter.eventLog(); } + @Test + public void insertThen_UpdateWhenNotChanged_expect_noChanges() { + + EcPerson person = new EcPerson("Nothing021"); + person.getPhoneNumbers().add("021 1234"); + person.getPhoneNumbers().add("021 4321"); + + LoggedSqlCollector.start(); + Ebean.save(person); + + List sql = LoggedSqlCollector.current(); + assertThat(eventLog()).containsOnly("preInsert", "postInsert"); + assertThat(sql).hasSize(4); + + final EcPerson found = Ebean.find(EcPerson.class, person.getId()); + found.getPhoneNumbers().size(); + + sql = LoggedSqlCollector.current(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("from ec_person t0 where t0.id = ?"); + assertThat(sql.get(1)).contains("from ec_person_phone t0 where"); + + // save when not actually changed + Ebean.save(found); + + sql = LoggedSqlCollector.stop(); + assertThat(sql).isEmpty(); + assertThat(eventLog()).isEmpty(); + } + @Test public void test() { diff --git a/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicMap.java b/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicMap.java index accd8ba2c..bf3f78159 100644 --- a/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicMap.java +++ b/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicMap.java @@ -18,7 +18,7 @@ public class TestElementCollectionBasicMap extends BaseTestCase { LoggedSqlCollector.start(); - EcmPerson person = new EcmPerson("Fiona021"); + EcmPerson person = new EcmPerson("MFiona021"); person.getPhoneNumbers().put("home", "021 1234"); person.getPhoneNumbers().put("work", "021 4321"); DB.save(person); @@ -36,7 +36,7 @@ public class TestElementCollectionBasicMap extends BaseTestCase { assertThat(sql.get(2)).contains("insert into ecm_person_phone"); } - EcmPerson person1 = new EcmPerson("Fiona09"); + EcmPerson person1 = new EcmPerson("MFiona09"); person1.getPhoneNumbers().put("home", "09 1234"); person1.getPhoneNumbers().put("work", "09 4321"); person1.getPhoneNumbers().put("mob", "09 9876"); @@ -46,10 +46,12 @@ public class TestElementCollectionBasicMap extends BaseTestCase { List found = DB.find(EcmPerson.class).where() - .startsWith("name", "Fiona0") + .startsWith("name", "MFiona0") .order().asc("id") .findList(); + assertThat(found).hasSize(2); + Map phoneNumbers0 = found.get(0).getPhoneNumbers(); Map phoneNumbers1 = found.get(1).getPhoneNumbers(); phoneNumbers0.size(); @@ -68,7 +70,7 @@ public class TestElementCollectionBasicMap extends BaseTestCase { DB.find(EcmPerson.class) .fetch("phoneNumbers") .where() - .startsWith("name", "Fiona0") + .startsWith("name", "MFiona0") .order().asc("id") .findList(); diff --git a/src/test/java/org/tests/model/elementcollection/TestElementCollectionCascade.java b/src/test/java/org/tests/model/elementcollection/TestElementCollectionCascade.java new file mode 100644 index 000000000..4518420f6 --- /dev/null +++ b/src/test/java/org/tests/model/elementcollection/TestElementCollectionCascade.java @@ -0,0 +1,66 @@ +package org.tests.model.elementcollection; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import org.junit.Test; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestElementCollectionCascade extends BaseTestCase { + + @Test + public void test() { + + EcsmParent parent = new EcsmParent("p1"); + final List children = parent.getChildren(); + + children.add(createChild("c0", 2)); + children.add(createChild("c1", 2)); + + DB.save(parent); + + replaceWith(children.get(0), 22); + replaceWith(children.get(1), 5); + + parent.setName("p1-mod"); + DB.save(parent); + + Map> childVals = new LinkedHashMap<>(); + + final EcsmParent foundParent = DB.find(EcsmParent.class, parent.getId()); + final List children1 = foundParent.getChildren(); + for (EcsmChild ecsmOne : children1) { + final Set values = ecsmOne.getValues(); + values.size(); + childVals.put(ecsmOne.getName(), values); + } + + Set vals0 = childVals.get("c0"); + assertThat(vals0).hasSize(22); + + Set vals1 = childVals.get("c1"); + assertThat(vals1).hasSize(5); + } + + private void replaceWith(EcsmChild one, int count) { + one.getValues().clear(); + createChild(one, count); + } + + private EcsmChild createChild(String name, int count) { + return createChild(new EcsmChild(name), count); + } + + private EcsmChild createChild(EcsmChild one, int count) { + final String name = one.getName(); + for (int i = 0; i < count; i++) { + one.getValues().add(name + i); + } + return one; + } +} diff --git a/src/test/java/org/tests/model/elementcollection/TestElementCollectionCascadeMultiple.java b/src/test/java/org/tests/model/elementcollection/TestElementCollectionCascadeMultiple.java index 5a814b97c..5c802157a 100644 --- a/src/test/java/org/tests/model/elementcollection/TestElementCollectionCascadeMultiple.java +++ b/src/test/java/org/tests/model/elementcollection/TestElementCollectionCascadeMultiple.java @@ -29,18 +29,17 @@ public class TestElementCollectionCascadeMultiple extends BaseTestCase { save(top); final List sql = LoggedSqlCollector.stop(); + assertThat(sql).hasSize(9); assertThat(sql.get(0)).contains("insert into ecs_person"); assertThat(sql.get(1)).contains("-- bind"); - assertThat(sql.get(2)).contains("insert into ecs_person_phone"); + assertThat(sql.get(2)).contains("insert into ec_top"); assertThat(sql.get(3)).contains("-- bind"); - assertThat(sql.get(4)).contains("-- bind"); - assertThat(sql.get(5)).contains("insert into ec_top"); + assertThat(sql.get(4)).contains("insert into ecs_person_phone"); + assertThat(sql.get(5)).contains("-- bind"); assertThat(sql.get(6)).contains("-- bind"); assertThat(sql.get(7)).contains("insert into ec_top_ecs_person"); assertThat(sql.get(8)).contains("-- bind"); - - assertThat(sql).hasSize(9); } @Transactional(batchSize = 20)