diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 1bf56c671..a19c98982 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -1046,6 +1046,16 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return this; } + /** + * Return the full name taking into account inheritance. + */ + public String rootName() { + if (inheritInfo != null && !inheritInfo.isRoot()) { + return inheritInfo.getRoot().desc().getName(); + } + return name; + } + /** * Return the named ORM query. */ diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchControl.java b/src/main/java/io/ebeaninternal/server/persist/BatchControl.java index 56f60cfe3..03649a93b 100644 --- a/src/main/java/io/ebeaninternal/server/persist/BatchControl.java +++ b/src/main/java/io/ebeaninternal/server/persist/BatchControl.java @@ -4,6 +4,7 @@ import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.core.PersistRequest; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.deploy.BeanDescriptor; +import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import java.util.ArrayList; import java.util.Arrays; @@ -59,6 +60,8 @@ public final class BatchControl { private boolean batchFlushOnMixed = true; + private int maxDepth; + /** * Create for a given transaction, PersistExecute, default size and getGeneratedKeys. */ @@ -230,6 +233,7 @@ public final class BatchControl { public void clear() { pstmtHolder.clear(); beanHoldMap.clear(); + maxDepth = 0; } /** @@ -261,6 +265,7 @@ public final class BatchControl { if (resetTop) { beanHoldMap.clear(); + maxDepth = 0; } } catch (BatchedSqlException e) { // clear the batch on error in case we want to @@ -277,21 +282,53 @@ public final class BatchControl { private BatchedBeanHolder getBeanHolder(PersistRequestBean request) throws BatchedSqlException { BeanDescriptor beanDescriptor = request.getBeanDescriptor(); - BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName()); + BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.rootName()); if (batchBeanHolder == null) { int relativeDepth = transaction.depth(); + + int beanDepth = 100 + relativeDepth; if (relativeDepth == 0 && !beanHoldMap.isEmpty()) { - // flush and reset the batch as we are changing the type of our top level - // bean so just keep it simple and flush and reset the top - flushReset(); + // could be non-cascading or uni-directional relationship + // so see look for a 'parent' in the beanHoldMap + int maybe = relativeToParentDepth(beanDescriptor); + if (maybe != -1) { + beanDepth = maybe; + } else { + // we can't be certain of the relative ordering for this type so + // flush and reset the batch as we are changing the type of our top level + // bean so just keep it simple and flush and reset the top + flushReset(); + } } - batchBeanHolder = new BatchedBeanHolder(this, beanDescriptor, 100 + relativeDepth); - beanHoldMap.put(beanDescriptor.getFullName(), batchBeanHolder); + maxDepth = Math.max(maxDepth, beanDepth); + batchBeanHolder = new BatchedBeanHolder(this, beanDescriptor, beanDepth); + beanHoldMap.put(beanDescriptor.rootName(), batchBeanHolder); } return batchBeanHolder; } + /** + * Find a depth based on imported relationships (to a parent that is already in the buffer). + */ + private int relativeToParentDepth(BeanDescriptor beanDescriptor) { + + BeanPropertyAssocOne[] imported = beanDescriptor.propertiesOneImported(); + if (imported.length == 0) { + // a top level type so just maintain the order relative to the current depth + return maxDepth + 1; + } + + for (BeanPropertyAssocOne parent : imported) { + BatchedBeanHolder parentBatch = beanHoldMap.get(parent.getTargetDescriptor().rootName()); + if (parentBatch != null) { + // deeper that the parent + return parentBatch.getOrder() + 1; + } + } + return -1; + } + /** * Return true if this holds no persist requests. */ diff --git a/src/test/java/org/tests/batchinsert/TestBatchInsertFlush.java b/src/test/java/org/tests/batchinsert/TestBatchInsertFlush.java index 3c46acda3..bc6a6c743 100644 --- a/src/test/java/org/tests/batchinsert/TestBatchInsertFlush.java +++ b/src/test/java/org/tests/batchinsert/TestBatchInsertFlush.java @@ -6,15 +6,70 @@ import io.ebean.EbeanServer; import io.ebean.Transaction; import io.ebean.annotation.Transactional; import io.ebean.PersistBatch; +import org.ebeantest.LoggedSqlCollector; import org.tests.model.basic.EBasicVer; import org.junit.Test; +import org.tests.model.basic.TSDetail; +import org.tests.model.basic.TSMaster; import java.sql.Timestamp; +import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull; public class TestBatchInsertFlush extends BaseTestCase { + @Test + public void no_cascade() { + + EbeanServer server = Ebean.getDefaultServer(); + + Transaction transaction = server.beginTransaction(); + try { + transaction.setPersistCascade(false); + transaction.setBatchSize(10); + transaction.setBatch(PersistBatch.ALL); + + + LoggedSqlCollector.start(); + + TSMaster m = new TSMaster(); + m.setName("master1"); + server.save(m); + + // we don't want this to flush batch yet but instead + // determine its batch depth based on imported assoc to master + TSDetail d1 = new TSDetail("d1"); + d1.setMaster(m); + server.save(d1); + + TSDetail d2 = new TSDetail("d2"); + d2.setMaster(m); + server.save(d2); + + // we want this to batch flush with master 1 + TSMaster m2 = new TSMaster(); + m2.setName("master2"); + server.save(m2); + + transaction.commit(); + + List sql = LoggedSqlCollector.stop(); + + // we get the 2 master inserts first + assertThat(sql.get(0)).contains("insert into t_atable_thatisrelatively"); + assertThat(sql.get(1)).contains("insert into t_atable_thatisrelatively"); + // detail + assertThat(sql.get(2)).contains("insert into t_detail_with_other_namexxxyy"); + + + } finally { + transaction.end(); + } + + } + @Test @Transactional(batch = PersistBatch.ALL) public void transactional_flushOnGetId() { diff --git a/src/test/java/org/tests/model/basic/xtra/TestInsertBatchWithDifferentRootTypes.java b/src/test/java/org/tests/model/basic/xtra/TestInsertBatchWithDifferentRootTypes.java index 3079b03c7..1da392db9 100644 --- a/src/test/java/org/tests/model/basic/xtra/TestInsertBatchWithDifferentRootTypes.java +++ b/src/test/java/org/tests/model/basic/xtra/TestInsertBatchWithDifferentRootTypes.java @@ -48,18 +48,20 @@ public class TestInsertBatchWithDifferentRootTypes extends BaseTestCase { List loggedSql0 = LoggedSqlCollector.start(); assertEquals(0, loggedSql0.size()); - // causes a flush as EdExtendedParent is different from EdParent + // does not causes a flush as EdExtendedParent is same root as EdParent + // so they get the same batch depth Ebean.save(extendedParent); // insert statements for EdParent List loggedSql1 = LoggedSqlCollector.start(); - assertEquals(2, loggedSql1.size()); Ebean.commitTransaction(); + assertEquals(0, loggedSql1.size()); + // insert statements for EdExtendedParent List loggedSql2 = LoggedSqlCollector.start(); - assertEquals(2, loggedSql2.size()); + assertEquals(4, loggedSql2.size()); } finally { Ebean.endTransaction();