diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java index 43e1a5f6d..c0aea5695 100644 --- a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java @@ -283,10 +283,12 @@ public final class PersistRequestBean extends PersistRequest implements BeanP * Check for batch escalation on cascade. */ public void checkBatchEscalationOnCascade() { - if (transaction.checkBatchEscalationOnCascade(this)) { - // we escalated to use batch mode so flush when done - // but if createdTransaction then commit will flush it - batchOnCascadeSet = !createdTransaction; + if (type != Type.INSERT || beanDescriptor.isCascadeBatchEscalateSupported()) { + if (transaction.checkBatchEscalationOnCascade(this)) { + // we escalated to use batch mode so flush when done + // but if createdTransaction then commit will flush it + batchOnCascadeSet = !createdTransaction; + } } persistCascade = transaction.isPersistCascade(); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index a8c70e7e2..0e8b8bcf6 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -140,6 +140,7 @@ public class BeanDescriptor implements BeanType, STreeType { private final short profileBeanId; private final boolean multiValueSupported; + private boolean cascadeBatchEscalateSupported; public enum EntityType { ORM, EMBEDDED, VIEW, SQL, DOC @@ -752,6 +753,24 @@ public class BeanDescriptor implements BeanType, STreeType { } } + private boolean hasCircularImportedId() { + for (BeanPropertyAssocOne assocOne : propertiesOneImportedSave) { + if (assocOne.hasCircularImportedId(this)) { + return true; + } + } + return false; + } + + boolean hasCircularImportedIdTo(BeanDescriptor sourceDesc) { + for (BeanPropertyAssocOne assocOne : propertiesOneImportedSave) { + if (assocOne.getTargetDescriptor() == sourceDesc) { + return true; + } + } + return false; + } + void registerColumn(String dbColumn, String path) { String key = dbColumn.toLowerCase(); // check for clash with imported OneToOne PK @@ -812,6 +831,7 @@ public class BeanDescriptor implements BeanType, STreeType { */ @SuppressWarnings("unchecked") void initialiseDocMapping() { + cascadeBatchEscalateSupported = supportCascadeBatch(); for (BeanPropertyAssocMany many : propertiesMany) { many.initialisePostTarget(); } @@ -826,6 +846,18 @@ public class BeanDescriptor implements BeanType, STreeType { cacheHelp.deriveNotifyFlags(); } + private boolean supportCascadeBatch() { + return idType == IdType.IDENTITY || !hasCircularImportedId(); + } + + /** + * Return false if JDBC batch can't be implicitly escalated to. + * This happens when we have circular import id situation (need to defer setting identity value). + */ + public boolean isCascadeBatchEscalateSupported() { + return cascadeBatchEscalateSupported; + } + void initInheritInfo() { if (inheritInfo != null) { // need to check every BeanDescriptor in the inheritance hierarchy diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index aa41e95ab..ac64689be 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -807,4 +807,8 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr } } } + + public boolean hasCircularImportedId(BeanDescriptor sourceDesc) { + return targetDescriptor.hasCircularImportedIdTo(sourceDesc); + } } diff --git a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java index ba30f71fc..04c5fcfd9 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java @@ -643,6 +643,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { // implicit transaction, no gain by batching where depth <= 0 return false; } + // return batchMode; } diff --git a/src/test/java/org/tests/basic/one2one/TestOne2OneBookingInvoice.java b/src/test/java/org/tests/basic/one2one/TestOne2OneBookingInvoice.java index ded00f249..59d407e55 100644 --- a/src/test/java/org/tests/basic/one2one/TestOne2OneBookingInvoice.java +++ b/src/test/java/org/tests/basic/one2one/TestOne2OneBookingInvoice.java @@ -2,10 +2,12 @@ package org.tests.basic.one2one; import io.ebean.BaseTestCase; import io.ebean.Ebean; - -import org.junit.Assert; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + public class TestOne2OneBookingInvoice extends BaseTestCase { @Test @@ -25,23 +27,23 @@ public class TestOne2OneBookingInvoice extends BaseTestCase { Ebean.save(b); Invoice invoice = Ebean.find(Invoice.class, ai.getId()); - Assert.assertEquals(b.getId(), invoice.getBooking().getId()); + assertEquals(b.getId(), invoice.getBooking().getId()); Booking b1 = Ebean.find(Booking.class, b.getId()); Invoice ai1 = b1.getAgentInvoice(); - Assert.assertNotNull(ai1); + assertNotNull(ai1); Booking b2 = ai1.getBooking(); - Assert.assertNotNull(b2); - Assert.assertEquals(b1.getId(), b2.getId()); - Assert.assertSame(b1, b2); + assertNotNull(b2); + assertEquals(b1.getId(), b2.getId()); + assertSame(b1, b2); Invoice ci1 = b1.getClientInvoice(); Booking b3 = ci1.getBooking(); - Assert.assertNotNull(b3); - Assert.assertEquals(b1.getId(), b2.getId()); - Assert.assertSame(b1, b2); + assertNotNull(b3); + assertEquals(b1.getId(), b2.getId()); + assertSame(b1, b2); // cleanup ai.setBooking(null); @@ -49,7 +51,5 @@ public class TestOne2OneBookingInvoice extends BaseTestCase { Ebean.save(ai); Ebean.save(ci); Ebean.delete(b); - - } } diff --git a/src/test/java/org/tests/iud/PcfCalendar.java b/src/test/java/org/tests/iud/PcfCalendar.java new file mode 100644 index 000000000..2f30ccd49 --- /dev/null +++ b/src/test/java/org/tests/iud/PcfCalendar.java @@ -0,0 +1,19 @@ +package org.tests.iud; + +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +import static javax.persistence.CascadeType.ALL; + +@Entity +public class PcfCalendar extends PcfModel { + + @OneToMany(cascade = ALL, orphanRemoval = true) + private List events = new ArrayList<>(); + + public void addEvent(PcfEvent event) { + events.add(event); + } +} diff --git a/src/test/java/org/tests/iud/PcfCity.java b/src/test/java/org/tests/iud/PcfCity.java new file mode 100644 index 000000000..2612bc1f3 --- /dev/null +++ b/src/test/java/org/tests/iud/PcfCity.java @@ -0,0 +1,24 @@ +package org.tests.iud; + +import javax.persistence.Entity; +import javax.persistence.OneToOne; + +import static javax.persistence.CascadeType.ALL; + +@Entity +public class PcfCity extends PcfModel { + + private final String name; + + @OneToOne(cascade = ALL, optional = false) + private PcfPerson mayor; + + @OneToOne(cascade = ALL, optional = false) + private PcfPerson viceMayor; + + public PcfCity(String name, PcfPerson mayor, PcfPerson viceMayor) { + this.name = name; + this.mayor = mayor; + this.viceMayor = viceMayor; + } +} diff --git a/src/test/java/org/tests/iud/PcfCountry.java b/src/test/java/org/tests/iud/PcfCountry.java new file mode 100644 index 000000000..bdfb3f930 --- /dev/null +++ b/src/test/java/org/tests/iud/PcfCountry.java @@ -0,0 +1,20 @@ +package org.tests.iud; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class PcfCountry extends PcfModel { + + + @OneToMany(cascade = CascadeType.ALL) + private List cities = new ArrayList<>(); + + public void addCity(PcfCity city) { + cities.add(city); + } + +} diff --git a/src/test/java/org/tests/iud/PcfEvent.java b/src/test/java/org/tests/iud/PcfEvent.java new file mode 100644 index 000000000..1a8aa8d40 --- /dev/null +++ b/src/test/java/org/tests/iud/PcfEvent.java @@ -0,0 +1,13 @@ +package org.tests.iud; + +import javax.persistence.Entity; + +@Entity +public class PcfEvent extends PcfModel { + + final String name; + + public PcfEvent(String name) { + this.name = name; + } +} diff --git a/src/test/java/org/tests/iud/PcfModel.java b/src/test/java/org/tests/iud/PcfModel.java new file mode 100644 index 000000000..118435243 --- /dev/null +++ b/src/test/java/org/tests/iud/PcfModel.java @@ -0,0 +1,18 @@ +package org.tests.iud; + +import io.ebean.Model; + +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.Version; + +@MappedSuperclass +public class PcfModel extends Model { + + @Id + long id; + + @Version + long version; + +} diff --git a/src/test/java/org/tests/iud/PcfPerson.java b/src/test/java/org/tests/iud/PcfPerson.java new file mode 100644 index 000000000..1a3cfdf59 --- /dev/null +++ b/src/test/java/org/tests/iud/PcfPerson.java @@ -0,0 +1,26 @@ +package org.tests.iud; + +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +import static javax.persistence.CascadeType.ALL; + +@Entity +public class PcfPerson extends PcfModel { + + private final String name; + + @OneToMany(cascade = ALL, orphanRemoval = true) + private List calendars = new ArrayList<>(); + + public PcfPerson(String name) { + this.name = name; + } + + public void addCalendar(PcfCalendar calendar) { + calendars.add(calendar); + } + +} diff --git a/src/test/java/org/tests/iud/TestPersistCascade.java b/src/test/java/org/tests/iud/TestPersistCascade.java new file mode 100644 index 000000000..853360e37 --- /dev/null +++ b/src/test/java/org/tests/iud/TestPersistCascade.java @@ -0,0 +1,46 @@ +package org.tests.iud; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebean.Transaction; +import org.avaje.moduuid.ModUUID; +import org.junit.Test; + +public class TestPersistCascade extends BaseTestCase { + + @Test + public void insert() throws InterruptedException { + + PcfCountry country = new PcfCountry(); + for (int a = 0; a < 3; a++) { + PcfPerson mayor = createPerson(); + PcfPerson viceMayor = createPerson(); + country.addCity(new PcfCity("c_" + ModUUID.newShortId(), mayor, viceMayor)); + } +// try (Transaction txn = DB.beginTransaction()) { +// txn.setBatchSize(20); +// country.save(); +// txn.commit(); +// } + + country.save(); + + + Thread.sleep(2000); + + country.deletePermanent(); + } + + private static PcfPerson createPerson() { + PcfPerson person = new PcfPerson("per_" + ModUUID.newShortId()); + for (int a = 0; a < 2; a++) { + PcfCalendar calendar = new PcfCalendar(); + for (int b = 0; b < 10; b++) { + calendar.addEvent(new PcfEvent("evt_" + ModUUID.newShortId())); + } + person.addCalendar(calendar); + } + return person; + } + +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index e9db0bc93..c16a68a11 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -79,9 +79,9 @@ - - - + + +