diff --git a/src/main/java/io/ebean/bean/PersistenceContext.java b/src/main/java/io/ebean/bean/PersistenceContext.java
index 1ece57962..a8a42e51b 100644
--- a/src/main/java/io/ebean/bean/PersistenceContext.java
+++ b/src/main/java/io/ebean/bean/PersistenceContext.java
@@ -61,6 +61,22 @@ public interface PersistenceContext {
*/
int size(Class> rootType);
+ /**
+ * Return a copy of the Persistence context to use for large query iteration.
+ */
+ PersistenceContext forIterate();
+
+ /**
+ * Return a new Persistence context during iteration of large query result.
+ */
+ PersistenceContext forIterateReset();
+
+ /**
+ * Return true if the persistence context has grown and hit the 'reset limit'
+ * during large query iteration.
+ */
+ boolean resetLimit();
+
/**
* Wrapper on a bean to also indicate if a bean has been deleted.
*
diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
index 7a4cb412a..afd5edba4 100644
--- a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
@@ -316,7 +316,6 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery
*/
@Override
public JsonReadOptions createJsonReadOptions() {
-
persistenceContext = getPersistenceContext(query, transaction);
if (query.getPersistenceContext() == null) {
query.setPersistenceContext(persistenceContext);
@@ -327,7 +326,6 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery
loadContext = new DLoadContext(this, secondaryQueries);
jsonRead.setLoadContext(loadContext);
}
-
return jsonRead;
}
@@ -335,8 +333,8 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery
* For iterate queries reset the persistenceContext and loadContext.
*/
public void flushPersistenceContextOnIterate() {
- if (!iterateSingleContext) {
- persistenceContext = new DefaultPersistenceContext();
+ if (!iterateSingleContext && persistenceContext.resetLimit()) {
+ persistenceContext = persistenceContext.forIterateReset();
loadContext.resetPersistenceContext(persistenceContext);
if (jsonRead != null) {
jsonRead.setPersistenceContext(persistenceContext);
@@ -350,7 +348,6 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery
* transaction scoped.
*/
private PersistenceContext getPersistenceContext(SpiQuery> query, SpiTransaction t) {
-
// check if there is already a persistence context set which is the case
// when lazy loading or query joins are executed
PersistenceContext ctx = query.getPersistenceContext();
@@ -358,7 +355,14 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery
// determine the scope (from the query and then server)
PersistenceContextScope scope = ebeanServer.getPersistenceContextScope(query);
- return (scope == PersistenceContextScope.QUERY || t == null) ? new DefaultPersistenceContext() : t.getPersistenceContext();
+ if (scope == PersistenceContextScope.QUERY || t == null) {
+ return new DefaultPersistenceContext();
+ }
+ if (Type.ITERATE == query.getType()) {
+ return t.getPersistenceContext().forIterate();
+ } else {
+ return t.getPersistenceContext();
+ }
}
/**
@@ -590,7 +594,6 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery
* Merge in prior L2 bean cache hits with the query result.
*/
public void mergeCacheHits(BeanCollection result) {
-
if (cacheBeans != null && !cacheBeans.isEmpty()) {
if (query.getType() == Type.MAP) {
mergeCacheHitsToMap(result);
diff --git a/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java
index 96c696c21..9d8252071 100644
--- a/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java
+++ b/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java
@@ -175,7 +175,6 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
@Override
public void loadBean(EntityBeanIntercept ebi) {
// A synchronized (this) is effectively held by EntityBeanIntercept.loadBean()
-
if (context.desc.lazyLoadMany(ebi)) {
// lazy load property was a Many
return;
@@ -183,7 +182,6 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
if (context.hitCache) {
Set hits = context.desc.cacheBeanLoadAll(list, persistenceContext, ebi.getLazyLoadPropertyIndex(), ebi.getLazyLoadProperty());
-
list.removeAll(hits);
if (list.isEmpty() || hits.contains(ebi)) {
// successfully hit the L2 cache so don't invoke DB lazy loading
@@ -193,6 +191,7 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
LoadBeanRequest req = new LoadBeanRequest(this, ebi, context.hitCache);
context.desc.getEbeanServer().loadBean(req);
+ list.clear();
}
}
diff --git a/src/main/java/io/ebeaninternal/server/query/CQuery.java b/src/main/java/io/ebeaninternal/server/query/CQuery.java
index 6ab020b36..9e8548702 100644
--- a/src/main/java/io/ebeaninternal/server/query/CQuery.java
+++ b/src/main/java/io/ebeaninternal/server/query/CQuery.java
@@ -608,12 +608,10 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran
}
QueryIterator readIterate(int bufferSize, OrmQueryRequest request) {
-
- if (bufferSize > 0) {
- return new CQueryIteratorWithBuffer<>(this, request, bufferSize);
-
- } else {
+ if (bufferSize < 2) {
return new CQueryIteratorSimple<>(this, request);
+ } else {
+ return new CQueryIteratorWithBuffer<>(this, request, bufferSize);
}
}
diff --git a/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java
index 087770730..620cffa64 100644
--- a/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java
+++ b/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java
@@ -34,18 +34,62 @@ public final class DefaultPersistenceContext implements PersistenceContext {
private final Monitor monitor = new Monitor();
+ private int putCount;
+
/**
* Create a new PersistenceContext.
*/
public DefaultPersistenceContext() {
}
+ /**
+ * Create as a shallow copy with initial or types that have not been added to.
+ */
+ private DefaultPersistenceContext(DefaultPersistenceContext parent, boolean initial) {
+ for (Map.Entry, ClassContext> entry : parent.typeCache.entrySet()) {
+ typeCache.put(entry.getKey(), entry.getValue().copy(initial));
+ }
+ }
+
+ /**
+ * Return the initial shallow copy with each ClassContext noting it's initialSize (to detect additions).
+ */
+ @Override
+ public PersistenceContext forIterate() {
+ return new DefaultPersistenceContext(this, true);
+ }
+
+ /**
+ * Return a shallow copy including each ClassContext that has had no additions (still at initialSize).
+ */
+ @Override
+ public PersistenceContext forIterateReset() {
+ return new DefaultPersistenceContext(this, false);
+ }
+
+ public boolean resetLimit() {
+ synchronized (monitor) {
+ if (putCount < 100) {
+ return false;
+ }
+ putCount = 0;
+ for (ClassContext value : typeCache.values()) {
+ if (value.resetLimit()) {
+ return true;
+ }
+ }
+ // checking after another 100 puts
+ return false;
+ }
+ }
+
/**
* Set an object into the PersistenceContext.
*/
@Override
public void put(Class> rootType, Object id, Object bean) {
synchronized (monitor) {
+ putCount++;
getClassContext(rootType).put(id, bean);
}
}
@@ -53,6 +97,7 @@ public final class DefaultPersistenceContext implements PersistenceContext {
@Override
public Object putIfAbsent(Class> rootType, Object id, Object bean) {
synchronized (monitor) {
+ putCount++;
return getClassContext(rootType).putIfAbsent(id, bean);
}
}
@@ -133,7 +178,6 @@ public final class DefaultPersistenceContext implements PersistenceContext {
}
private ClassContext getClassContext(Class> rootType) {
-
return typeCache.computeIfAbsent(rootType, k -> new ClassContext());
}
@@ -143,9 +187,45 @@ public final class DefaultPersistenceContext implements PersistenceContext {
private Set