diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java index 4b8e3d8dcd..ada34b378f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -227,7 +227,7 @@ public void loadBean(EntityBeanIntercept ebi) { // re-add to the batch and lazy load from DB skipping l2 cache if (loadingStarted.get()) { if (CoreLog.markedAsDeleted.isLoggable(DEBUG)) { - CoreLog.markedAsDeleted.log(DEBUG, "Adding " + ebi + "to batch " + this + "after loadingStarted(2) ", new RuntimeException("Adding to batch after load(2")); + CoreLog.markedAsDeleted.log(DEBUG, "Adding " + ebi + "to batch " + this + "after loadingStarted(2) ", new RuntimeException("Adding to batch after load(2)")); } } batch.add(ebi); @@ -239,9 +239,28 @@ public void loadBean(EntityBeanIntercept ebi) { return; } } - + // ensure, that every bean in the batch is in the persistence context. + // this may happen, when bean was previously deleted, but the result is not yet committed. + List reincarnatedIds = null; + for (EntityBeanIntercept batchEbi : batch) { + Object id = context.desc.getId(batchEbi.owner()); + if (id != null && context.desc.contextPutIfAbsent(persistenceContext, id, batchEbi.owner()) == null) { + if (reincarnatedIds == null) { + reincarnatedIds = new ArrayList<>(); + } + reincarnatedIds.add(id); + if (CoreLog.markedAsDeleted.isLoggable(DEBUG)) { + CoreLog.markedAsDeleted.log(DEBUG, "Temporary adding " + ebi + "to persistence context", new RuntimeException("Temporary adding bean to persistence context")); + } + } + } context.desc.ebeanServer().loadBean(new LoadBeanRequest(this, ebi, context.hitCache)); batch.clear(); + if (reincarnatedIds != null) { + for (Object id : reincarnatedIds) { + context.desc.contextClear(persistenceContext, id); + } + } } } diff --git a/ebean-test/src/test/java/io/ebean/xtest/event/BeanPersistControllerTest.java b/ebean-test/src/test/java/io/ebean/xtest/event/BeanPersistControllerTest.java index e89b808a95..1388f28969 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/event/BeanPersistControllerTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/event/BeanPersistControllerTest.java @@ -2,13 +2,15 @@ import io.ebean.Database; +import io.ebean.DatabaseBuilder; import io.ebean.DatabaseFactory; import io.ebean.Transaction; -import io.ebean.DatabaseBuilder; import io.ebean.config.DatabaseConfig; import io.ebean.event.BeanDeleteIdRequest; import io.ebean.event.BeanPersistAdapter; +import io.ebean.event.BeanPersistController; import io.ebean.event.BeanPersistRequest; +import io.ebean.test.LoggedSql; import org.junit.jupiter.api.Test; import org.tests.model.basic.EBasicVer; import org.tests.model.basic.UTDetail; @@ -132,7 +134,7 @@ public void testInsertUpdateDelete_given_stopPersistingAdapter() { assertThat(stopPersistingAdapter.methodsCalled).containsExactly("preDeleteById"); stopPersistingAdapter.methodsCalled.clear(); - db.deleteAll(EBasicVer.class, Arrays.asList(22,23,24)); + db.deleteAll(EBasicVer.class, Arrays.asList(22, 23, 24)); assertThat(stopPersistingAdapter.methodsCalled).hasSize(3); assertThat(stopPersistingAdapter.methodsCalled).containsExactly("preDeleteById", "preDeleteById", "preDeleteById"); stopPersistingAdapter.methodsCalled.clear(); @@ -140,7 +142,116 @@ public void testInsertUpdateDelete_given_stopPersistingAdapter() { db.shutdown(); } - private Database getDatabase(PersistAdapter persistAdapter) { + @Test + public void testCascade() { + Database db = getDatabase(new BeanPersistAdapter() { + @Override + public boolean isRegisterFor(Class cls) { + return UTMaster.class == cls; + } + + @Override + public boolean preDelete(BeanPersistRequest request) { + return false; + } + }); + Integer id; + UTMaster master = new UTMaster(); + master.addDetail(new UTDetail()); + db.save(master); + id = master.getId(); + + master = db.find(UTMaster.class, id); + assertThat(master.getDetails()).hasSize(1); + + try (Transaction txn = db.beginTransaction()) { + txn.setBatchMode(true); + db.delete(master); + txn.commit(); + } + + master = db.find(UTMaster.class, id); + assertThat(master).isNotNull(); + // CHECKME: Deleting of master was denied by the PersistListener + // What about detail? Is this intended, that it will be deleted? + assertThat(master.getDetails()).hasSize(0); + + } + + @Test + public void testInsertUpdateDelete_with_LazyLoad() { + + Database db = getDatabase(new BeanPersistAdapter() { + + @Override + public boolean isRegisterFor(Class cls) { + return EBasicVer.class == cls; + } + + @Override + public boolean preInsert(BeanPersistRequest request) { + assertThat(((EBasicVer) request.bean()).getDescription()).isEqualTo("MyDescription"); + return true; + } + + @Override + public boolean preUpdate(BeanPersistRequest request) { + assertThat(((EBasicVer) request.bean()).getDescription()).isEqualTo("MyDescription"); + return true; + } + + @Override + public boolean preDelete(BeanPersistRequest request) { + assertThat(((EBasicVer) request.bean()).getDescription()).isEqualTo("MyDescription"); + return true; + } + }); + Integer id; + try (Transaction txn = db.beginTransaction()) { + txn.setBatchMode(true); + EBasicVer bean = new EBasicVer("testController"); + bean.setDescription("MyDescription"); + + db.save(bean); + txn.commit(); + id = bean.getId(); + } + + + try (Transaction txn = db.beginTransaction()) { + txn.setBatchMode(true); + EBasicVer bean = db.find(EBasicVer.class).setUseCache(false).select("name").setId(id).findOne(); + bean.setName("otherName"); + + db.save(bean); + + txn.commitAndContinue(); + + EBasicVer bean2 = db.find(EBasicVer.class).setUseCache(false).select("name").setId(id).findOne(); + assertThat(bean2).isSameAs(bean); + } + + try (Transaction txn = db.beginTransaction()) { + txn.setBatchMode(true); + EBasicVer bean = db.find(EBasicVer.class).setUseCache(false).select("name").setId(id).findOne(); + + db.delete(bean); + txn.commitAndContinue(); + System.out.println(txn); + } +/* + db.update(bean); + + db.delete(bean); + + db.delete(EBasicVer.class, 22); + + db.deleteAll(EBasicVer.class, Arrays.asList(22,23,24)); +*/ + db.shutdown(); + } + + private Database getDatabase(BeanPersistController persistAdapter) { DatabaseBuilder config = new DatabaseConfig(); config.setName("h2ebasicver"); config.loadFromProperties(); @@ -194,12 +305,12 @@ public boolean preUpdate(BeanPersistRequest request) { Object bean = request.bean(); if (bean instanceof UTDetail) { - UTDetail detail = (UTDetail)bean; + UTDetail detail = (UTDetail) bean; // invoke lazy loading ... which invoke the flush of the jdbc batch detail.setQty(42); } if (bean instanceof UTMaster) { - UTMaster master = (UTMaster)bean; + UTMaster master = (UTMaster) bean; UTMaster.Journal journal = master.getJournal(); if (journal == null) { journal = new UTMaster.Journal();