From ea3fb833a48254ee364fd99bdbcb2c5ff36d6d8e Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 11 Oct 2021 22:48:18 +1300 Subject: [PATCH 1/5] Weak reference based PersistenceContext for streaming queries --- .../io/ebean/bean/PersistenceContext.java | 11 - .../server/core/OrmQueryRequest.java | 14 - .../server/query/CQueryIteratorSimple.java | 1 - .../query/CQueryIteratorWithBuffer.java | 2 - .../DefaultPersistenceContext.java | 98 +------ .../transaction/WeakPersistenceContext.java | 272 ++++++++++++++++++ .../DefaultPersistenceContextTest.java | 98 ++----- 7 files changed, 298 insertions(+), 198 deletions(-) create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/transaction/WeakPersistenceContext.java diff --git a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java index 9d67499d1..33215595a 100644 --- a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java +++ b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java @@ -64,17 +64,6 @@ public interface PersistenceContext { */ 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/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index 85d89ffe2..a56319fd4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -263,20 +263,6 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery return jsonRead; } - /** - * For iterate queries reset the persistenceContext and loadContext. - */ - public void flushPersistenceContextOnIterate() { - if (persistenceContext.resetLimit()) { - persistenceContext = persistenceContext.forIterateReset(); - loadContext.resetPersistenceContext(persistenceContext); - if (jsonRead != null) { - jsonRead.setPersistenceContext(persistenceContext); - jsonRead.setLoadContext(loadContext); - } - } - } - /** * Get the TransactionContext either explicitly set on the query or * transaction scoped. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java index d438ec747..6d183b936 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java @@ -26,7 +26,6 @@ final class CQueryIteratorSimple implements QueryIterator { public boolean hasNext() { boolean ret = false; try { - request.flushPersistenceContextOnIterate(); ret = cquery.hasNext(); return ret; } catch (SQLException e) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java index ad1a924b5..3df436716 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java @@ -35,8 +35,6 @@ final class CQueryIteratorWithBuffer implements QueryIterator { try { if (buffer.isEmpty() && moreToLoad) { // load buffer - request.flushPersistenceContextOnIterate(); - int i = -1; while (moreToLoad && ++i < bufferSize) { if (cquery.hasNext()) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java index 169ca59a3..911c54c47 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java @@ -25,15 +25,9 @@ import java.util.concurrent.locks.ReentrantLock; */ public final class DefaultPersistenceContext implements SpiPersistenceContext { - /** - * Map used hold caches. One cache per bean type. - */ private final HashMap, ClassContext> typeCache = new HashMap<>(); - private final ReentrantLock lock = new ReentrantLock(); - private int putCount; - /** * Create a new PersistenceContext. */ @@ -41,48 +35,15 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { } /** - * 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). + * Return a PersistenceContext to use for streaming queries. */ @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); - } - - @Override - public boolean resetLimit() { - lock.lock(); - try { - if (putCount < 100) { - return false; - } - putCount = 0; - for (ClassContext value : typeCache.values()) { - if (value.resetLimit()) { - return true; - } - } - // checking after another 100 puts - return false; - } finally { - lock.unlock(); + WeakPersistenceContext weak = new WeakPersistenceContext(); + for (ClassContext value : typeCache.values()) { + weak.add(value.rootType, value.deleteSet, value.map); } + return weak; } /** @@ -92,7 +53,6 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public void put(Class rootType, Object id, Object bean) { lock.lock(); try { - putCount++; classContext(rootType).put(id, bean); } finally { lock.unlock(); @@ -103,7 +63,6 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public Object putIfAbsent(Class rootType, Object id, Object bean) { lock.lock(); try { - putCount++; return classContext(rootType).putIfAbsent(id, bean); } finally { lock.unlock(); @@ -232,61 +191,18 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { private final Map map = new HashMap<>(); private final Class rootType; private Set deleteSet; - private int initialSize; - private ClassContext parent; private ClassContext(Class rootType) { this.rootType = rootType; } - /** - * Create as a shallow copy. - */ - private ClassContext(ClassContext source, boolean initial) { - this.rootType = source.rootType; - if (initial || source.isTransfer()) { - parent = source.transferParent(); - initialSize = parent.size(); - if (source.deleteSet != null) { - deleteSet = new HashSet<>(source.deleteSet); - } - } - } - - /** - * True if this should be transferred to a new iterator persistence context. - */ - private boolean isTransfer() { - // map not added to and has some original/parent beans - return map.isEmpty() && initialSize > 0; - } - - private ClassContext transferParent() { - return (parent != null) ? parent : this; - } - - /** - * Return a shallow copy if initial copy or it has not grown (still at initialSize). - */ - private ClassContext copy(boolean initial) { - return new ClassContext(this, initial); - } - - /** - * Return true if grown above the reset limit size of 1000. - */ - private boolean resetLimit() { - return map.size() > 1000; - } - @Override public String toString() { return "size:" + map.size(); } private Object get(Object id) { - Object bean = (parent == null) ? null : parent.get(id); - return bean != null ? bean : map.get(id); + return map.get(id); } private WithOption getWithOption(Object id) { @@ -313,7 +229,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { } private int size() { - return map.size() + initialSize; + return map.size(); } private void clear() { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/WeakPersistenceContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/WeakPersistenceContext.java new file mode 100644 index 000000000..4d7371bf9 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/WeakPersistenceContext.java @@ -0,0 +1,272 @@ +package io.ebeaninternal.server.transaction; + +import io.ebean.bean.EntityBean; +import io.ebean.bean.PersistenceContext; +import io.ebeaninternal.api.SpiBeanType; +import io.ebeaninternal.api.SpiBeanTypeManager; +import io.ebeaninternal.api.SpiPersistenceContext; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Weak reference based PersistenceContext for "streaming queries". + */ +final class WeakPersistenceContext implements SpiPersistenceContext { + + private final HashMap, WeakClassContext> typeCache = new HashMap<>(); + private final ReentrantLock lock = new ReentrantLock(); + + WeakPersistenceContext() { + } + + /** + * Load from the initiating persistence context. + */ + void add(Class rootType, Set deleteSet, Map map) { + typeCache.put(rootType, new WeakClassContext( rootType, deleteSet, map)); + } + + @Override + public PersistenceContext forIterate() { + return this; + } + + @Override + public void put(Class rootType, Object id, Object bean) { + lock.lock(); + try { + classContext(rootType).put(id, bean); + } finally { + lock.unlock(); + } + } + + @Override + public Object putIfAbsent(Class rootType, Object id, Object bean) { + lock.lock(); + try { + return classContext(rootType).putIfAbsent(id, bean); + } finally { + lock.unlock(); + } + } + + @Override + public Object get(Class rootType, Object id) { + lock.lock(); + try { + return classContext(rootType).get(id); + } finally { + lock.unlock(); + } + } + + @Override + public WithOption getWithOption(Class rootType, Object id) { + lock.lock(); + try { + return classContext(rootType).getWithOption(id); + } finally { + lock.unlock(); + } + } + + @Override + public int size(Class rootType) { + lock.lock(); + try { + WeakClassContext classMap = typeCache.get(rootType); + return classMap == null ? 0 : classMap.size(); + } finally { + lock.unlock(); + } + } + + @Override + public void clear() { + lock.lock(); + try { + typeCache.clear(); + } finally { + lock.unlock(); + } + } + + @Override + public void clear(Class rootType) { + lock.lock(); + try { + WeakClassContext classMap = typeCache.get(rootType); + if (classMap != null) { + classMap.clear(); + } + } finally { + lock.unlock(); + } + } + + @Override + public void deleted(Class rootType, Object id) { + lock.lock(); + try { + WeakClassContext classMap = typeCache.get(rootType); + if (classMap != null && id != null) { + classMap.deleted(id); + } + } finally { + lock.unlock(); + } + } + + @Override + public void clear(Class rootType, Object id) { + lock.lock(); + try { + WeakClassContext classMap = typeCache.get(rootType); + if (classMap != null && id != null) { + classMap.remove(id); + } + } finally { + lock.unlock(); + } + } + + @Override + public List dirtyBeans(SpiBeanTypeManager manager) { + lock.lock(); + try { + List list = new ArrayList<>(); + for (WeakClassContext classContext : typeCache.values()) { + classContext.dirtyBeans(manager, list); + } + return list; + } finally { + lock.unlock(); + } + } + + @Override + public String toString() { + lock.lock(); + try { + return typeCache.toString(); + } finally { + lock.unlock(); + } + } + + private WeakClassContext classContext(Class rootType) { + return typeCache.computeIfAbsent(rootType, k -> new WeakClassContext(rootType)); + } + + private static class WeakClassContext { + + private final ReferenceQueue queue = new ReferenceQueue<>(); + private final Map map = new HashMap<>(); + private final Class rootType; + private Set deleteSet; + + private WeakClassContext(Class rootType) { + this.rootType = rootType; + } + + private WeakClassContext(Class rootType, Set initialDeleteSet, Map initialMap) { + this.rootType = rootType; + this.deleteSet = initialDeleteSet == null ? null : new HashSet<>(initialDeleteSet); + for (Map.Entry entry : initialMap.entrySet()) { + Object id = entry.getKey(); + map.put(id, new BeanRef(id, entry.getValue(), queue)); + } + } + + private void expungeStaleEntries() { + Reference ref; + while ((ref = queue.poll()) != null) { + map.remove(((BeanRef)ref).key()); + } + } + + @Override + public String toString() { + return "size:" + map.size(); + } + + private Object get(Object id) { + expungeStaleEntries(); + Reference ref = map.get(id); + return ref == null ? null : ref.get(); + } + + private WithOption getWithOption(Object id) { + if (deleteSet != null && deleteSet.contains(id)) { + return WithOption.DELETED; + } + Object bean = get(id); + return (bean == null) ? null : new WithOption(bean); + } + + private Object putIfAbsent(Object id, Object bean) { + Object existingValue = get(id); + if (existingValue != null) { + // it is not absent + return existingValue; + } + // put the new value and return null indicating the put was successful + put(id, bean); + return null; + } + + private void put(Object id, Object b) { + expungeStaleEntries(); + map.put(id, new BeanRef(id, b, queue)); + } + + private int size() { + return map.size(); + } + + private void clear() { + map.clear(); + } + + private void remove(Object id) { + map.remove(id); + } + + private void deleted(Object id) { + if (deleteSet == null) { + deleteSet = new HashSet<>(); + } + deleteSet.add(id); + map.remove(id); + } + + /** + * Add the dirty beans to the list. + */ + void dirtyBeans(SpiBeanTypeManager manager, List list) { + final SpiBeanType beanType = manager.beanType(rootType); + for (BeanRef value : map.values()) { + EntityBean bean = (EntityBean) value.get(); + if (bean != null && (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean))) { + list.add(bean); + } + } + } + } + + private static class BeanRef extends WeakReference { + private final Object key; + private BeanRef(Object key, Object referent, ReferenceQueue q) { + super(referent, q); + this.key = key; + } + Object key() { + return key; + } + } +} diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java index 4983c31e3..1aa4af51c 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java @@ -6,16 +6,13 @@ import org.junit.jupiter.api.Test; import org.tests.model.basic.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -public class DefaultPersistenceContextTest { +class DefaultPersistenceContextTest { private final Customer customer42; - private final Car car1; - public DefaultPersistenceContextTest() { + DefaultPersistenceContextTest() { customer42 = new Customer(); customer42.setId(42); car1 = new Car(); @@ -37,8 +34,7 @@ public class DefaultPersistenceContextTest { } @Test - public void put_get_withInheritance() { - + void put_get_withInheritance() { PersistenceContext pc = pc(); pc.put(root(Vehicle.class), 1, car1); @@ -47,8 +43,7 @@ public class DefaultPersistenceContextTest { } @Test - public void put_get() { - + void put_get() { PersistenceContext pc = pc(); pc.put(Customer.class, customer42.getId(), customer42); @@ -57,8 +52,7 @@ public class DefaultPersistenceContextTest { } @Test - public void putIfAbsent_when_absent() { - + void putIfAbsent_when_absent() { PersistenceContext pc = pc(); Object existing = pc.putIfAbsent(Customer.class, customer42.getId(), customer42); @@ -66,8 +60,7 @@ public class DefaultPersistenceContextTest { } @Test - public void putIfAbsent_when_notAbsent() { - + void putIfAbsent_when_notAbsent() { PersistenceContext pc = pcWith42(); Object existing = pc.putIfAbsent(Customer.class, customer42.getId(), new Customer()); @@ -75,30 +68,28 @@ public class DefaultPersistenceContextTest { } @Test - public void get_when_empty() { + void get_when_empty() { PersistenceContext pc = pc(); Object found = pc.get(Customer.class, 42); assertThat(found).isNull(); } @Test - public void get_when_there() { + void get_when_there() { PersistenceContext pc = pcWith42(); Object found = pc.get(Customer.class, 42); assertThat(found).isSameAs(customer42); } @Test - public void getWithOption_when_empty() { - + void getWithOption_when_empty() { PersistenceContext pc = pc(); PersistenceContext.WithOption withOption = pc.getWithOption(Customer.class, 42); assertThat(withOption).isNull(); } @Test - public void getWithOption_when_there() { - + void getWithOption_when_there() { PersistenceContext pc = pcWith42(); PersistenceContext.WithOption withOption = pc.getWithOption(Customer.class, 42); @@ -106,8 +97,7 @@ public class DefaultPersistenceContextTest { } @Test - public void getWithOption_when_deleted() { - + void getWithOption_when_deleted() { PersistenceContext pc = pcWith42(); pc.deleted(Customer.class, 42); @@ -117,38 +107,33 @@ public class DefaultPersistenceContextTest { } @Test - public void size_when_empty() { - + void size_when_empty() { PersistenceContext pc = pc(); assertThat(pc.size(Customer.class)).isEqualTo(0); } @Test - public void size_when_some() { - + void size_when_some() { PersistenceContext pc = pcWith42(); assertThat(pc.size(Customer.class)).isEqualTo(1); } @Test - public void clear() { - + void clear() { PersistenceContext pc = pcWith42(); pc.clear(); assertThat(pc.size(Customer.class)).isEqualTo(0); } @Test - public void clearClass() { - + void clearClass() { PersistenceContext pc = pcWith42(); pc.clear(Customer.class); assertThat(pc.size(Customer.class)).isEqualTo(0); } @Test - public void clearClassAndId() { - + void clearClassAndId() { PersistenceContext pc = pcWith42(); pc.put(Customer.class, 43, new Customer()); @@ -160,7 +145,7 @@ public class DefaultPersistenceContextTest { } @Test - public void forIterate() { + void forIterate() { final DefaultPersistenceContext pc = pcWith42(); final Object origCustomer42 = pc.get(Customer.class, 42); @@ -177,7 +162,7 @@ public class DefaultPersistenceContextTest { } @Test - public void forIterate_many() { + void forIterate_many() { DefaultPersistenceContext pc = new DefaultPersistenceContext(); addCustomers(pc, 1, 100); addContacts(pc, 1, 1010); @@ -191,44 +176,7 @@ public class DefaultPersistenceContextTest { } @Test - public void forIterate_resetLimit_forIterateReset() { - DefaultPersistenceContext initialPc = new DefaultPersistenceContext(); - addCustomers(initialPc, 1, 100); - addContacts(initialPc, 1, 1010); - - final PersistenceContext pcIterate = initialPc.forIterate(); - assertFalse(pcIterate.resetLimit()); - - // added 900 NEW contact beans - addContacts(pcIterate, 2000, 900); - assertThat(pcIterate.size(Contact.class)).isEqualTo(1910); - assertFalse(pcIterate.resetLimit()); - - // boundary, added 1000 NEW contact beans (still false) - addContacts(pcIterate, 3000, 100); - assertFalse(pcIterate.resetLimit()); - - addContacts(pcIterate, 4000, 1); - addProducts(pcIterate, 1, 100); - // ACT - over 1000 added beans boundary for contacts so returns true - assertTrue(pcIterate.resetLimit()); - - assertThat(pcIterate.size(Contact.class)).isEqualTo(2011); - assertThat(pcIterate.size(Customer.class)).isEqualTo(100); - assertThat(pcIterate.size(Product.class)).isEqualTo(100); - - // ACT - obtain new PC forIterateReset - PersistenceContext pcReset = pcIterate.forIterateReset(); - - // keeps original customer beans as no new added beans there - assertThat(pcReset.size(Customer.class)).isEqualTo(100); // customers didn't change - // added beans to contacts and products so those where reset - assertThat(pcReset.size(Contact.class)).isEqualTo(0); - assertThat(pcReset.size(Product.class)).isEqualTo(0); - } - - @Test - public void toString_sillyTest() { + void toString_sillyTest() { DefaultPersistenceContext pc = pcWith42(); assertThat(pc.toString()).contains("org.tests.model.basic.Customer"); } @@ -248,12 +196,4 @@ public class DefaultPersistenceContextTest { pc.put(Contact.class, i, bean); } } - - private void addProducts(PersistenceContext pc, int start, int loop) { - for (int i = start; i < start + loop; i++) { - Product bean = new Product(); - bean.setId(i); - pc.put(Product.class, i, bean); - } - } } From 3f5009aa91b5ad9b009edb8fa392fc853a2a4409 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Mon, 11 Oct 2021 16:25:21 +0200 Subject: [PATCH 2/5] Use WeakReferences when in iterate query mode --- .../io/ebean/bean/PersistenceContext.java | 9 +- .../server/core/OrmQueryRequest.java | 15 +- .../DefaultPersistenceContext.java | 131 +++++++-- .../transaction/WeakPersistenceContext.java | 272 ------------------ .../tests/basic/TestPersistenceContext.java | 66 +++++ 5 files changed, 195 insertions(+), 298 deletions(-) delete mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/transaction/WeakPersistenceContext.java diff --git a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java index 33215595a..ed03080cd 100644 --- a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java +++ b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java @@ -60,10 +60,15 @@ public interface PersistenceContext { int size(Class rootType); /** - * Return a copy of the Persistence context to use for large query iteration. + * Signalizes the PersistenceContext, the begin for large query iteration. */ - PersistenceContext forIterate(); + void beginIterate(); + /** + * Signalizes the PersistenceContext, the end for large query iteration. + */ + void endIterate(); + /** * Wrapper on a bean to also indicate if a bean has been deleted. *

diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index a56319fd4..6ef1e352b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -225,6 +225,9 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery createdTransaction = true; } persistenceContext = persistenceContext(query, transaction); + if (Type.ITERATE == query.getType()) { + persistenceContext.beginIterate(); + } loadContext = new DLoadContext(this, secondaryQueries); } @@ -233,6 +236,9 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery */ @Override public void rollbackTransIfRequired() { + if (Type.ITERATE == query.getType()) { + persistenceContext.endIterate(); + } if (createdTransaction) { try { transaction.end(); @@ -278,11 +284,7 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery if (scope == PersistenceContextScope.QUERY || t == null) { return new DefaultPersistenceContext(); } - if (Type.ITERATE == query.getType()) { - return t.getPersistenceContext().forIterate(); - } else { - return t.getPersistenceContext(); - } + return t.getPersistenceContext(); } /** @@ -292,6 +294,9 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery */ @Override public void endTransIfRequired() { + if (Type.ITERATE == query.getType()) { + persistenceContext.endIterate(); + } if (createdTransaction && transaction.isActive()) { transaction.commit(); if (query.getType().isUpdate()) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java index 911c54c47..1ff3d44fa 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java @@ -6,6 +6,9 @@ import io.ebeaninternal.api.SpiBeanType; import io.ebeaninternal.api.SpiBeanTypeManager; import io.ebeaninternal.api.SpiPersistenceContext; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; import java.util.*; import java.util.concurrent.locks.ReentrantLock; @@ -27,6 +30,14 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { private final HashMap, ClassContext> typeCache = new HashMap<>(); private final ReentrantLock lock = new ReentrantLock(); + private final ReferenceQueue queue = new ReferenceQueue(); + + /** + * When we are inside an iterate loop, we will add only WeakReferences. This + * allows the JVM GC to collect beans, which are not referenced elsewhere. In + * normal operation, we will use hard references, to avoid performance impact + */ + private int iterateDepth; /** * Create a new PersistenceContext. @@ -34,16 +45,25 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public DefaultPersistenceContext() { } - /** - * Return a PersistenceContext to use for streaming queries. - */ @Override - public PersistenceContext forIterate() { - WeakPersistenceContext weak = new WeakPersistenceContext(); - for (ClassContext value : typeCache.values()) { - weak.add(value.rootType, value.deleteSet, value.map); + public void beginIterate() { + lock.lock(); + try { + iterateDepth++; + } finally { + lock.unlock(); + } + } + + @Override + public void endIterate() { + lock.lock(); + try { + iterateDepth--; + expungeStaleEntries(); // when leaving the iterator, cleanup. + } finally { + lock.unlock(); } - return weak; } /** @@ -53,7 +73,8 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public void put(Class rootType, Object id, Object bean) { lock.lock(); try { - classContext(rootType).put(id, bean); + expungeStaleEntries(); + classContext(rootType).useReferences(iterateDepth > 0).put(id, bean); } finally { lock.unlock(); } @@ -63,7 +84,8 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public Object putIfAbsent(Class rootType, Object id, Object bean) { lock.lock(); try { - return classContext(rootType).putIfAbsent(id, bean); + expungeStaleEntries(); + return classContext(rootType).useReferences(iterateDepth > 0).putIfAbsent(id, bean); } finally { lock.unlock(); } @@ -76,6 +98,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public Object get(Class rootType, Object id) { lock.lock(); try { + expungeStaleEntries(); return classContext(rootType).get(id); } finally { lock.unlock(); @@ -86,6 +109,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public WithOption getWithOption(Class rootType, Object id) { lock.lock(); try { + expungeStaleEntries(); return classContext(rootType).getWithOption(id); } finally { lock.unlock(); @@ -99,6 +123,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public int size(Class rootType) { lock.lock(); try { + expungeStaleEntries(); ClassContext classMap = typeCache.get(rootType); return classMap == null ? 0 : classMap.size(); } finally { @@ -114,6 +139,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { lock.lock(); try { typeCache.clear(); + expungeStaleEntries(); } finally { lock.unlock(); } @@ -127,6 +153,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { if (classMap != null) { classMap.clear(); } + expungeStaleEntries(); } finally { lock.unlock(); } @@ -140,6 +167,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { if (classMap != null && id != null) { classMap.deleted(id); } + expungeStaleEntries(); } finally { lock.unlock(); } @@ -153,6 +181,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { if (classMap != null && id != null) { classMap.remove(id); } + expungeStaleEntries(); } finally { lock.unlock(); } @@ -162,6 +191,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public List dirtyBeans(SpiBeanTypeManager manager) { lock.lock(); try { + expungeStaleEntries(); List list = new ArrayList<>(); for (ClassContext classContext : typeCache.values()) { classContext.dirtyBeans(manager, list); @@ -172,10 +202,23 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { } } + /** + * When there is a queue, poll it to remove stale entries from the map. Note: + * This is always done AFTER useReferences was called with + * true. Polling an empty queue has no performance impact. + */ + private void expungeStaleEntries() { + Reference ref; + while ((ref = queue.poll()) != null) { + ((BeanRef)ref).expunge(); + } + } + @Override public String toString() { lock.lock(); try { + expungeStaleEntries(); return typeCache.toString(); } finally { lock.unlock(); @@ -183,26 +226,47 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { } private ClassContext classContext(Class rootType) { - return typeCache.computeIfAbsent(rootType, k -> new ClassContext(rootType)); + return typeCache.computeIfAbsent(rootType, k -> new ClassContext(k, queue)); } - + private static class ClassContext { private final Map map = new HashMap<>(); private final Class rootType; + private final ReferenceQueue queue; private Set deleteSet; + + private boolean useReferences; + private int weakCount; - private ClassContext(Class rootType) { + + private ClassContext(Class rootType, ReferenceQueue queue) { this.rootType = rootType; + this.queue = queue; } + /** + * When called with "true", initialize referenceQueue and store BeanRefs instead + * of real object references. + */ + public ClassContext useReferences(boolean useReferences) { + this.useReferences = useReferences; + return this; + } + + @Override public String toString() { - return "size:" + map.size(); + return "size:" + map.size() + " (" + weakCount + " weak)"; } private Object get(Object id) { - return map.get(id); + Object ret = map.get(id); + if (ret instanceof BeanRef) { + return ((BeanRef)ret).get(); + } else { + return ret; + } } private WithOption getWithOption(Object id) { @@ -220,12 +284,17 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { return existingValue; } // put the new value and return null indicating the put was successful - map.put(id, bean); + put(id, bean); return null; } private void put(Object id, Object b) { - map.put(id, b); + if (useReferences) { + weakCount++; + map.put(id, new BeanRef(this, id, b, queue)); + } else { + map.put(id, b); + } } private int size() { @@ -234,10 +303,14 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { private void clear() { map.clear(); + weakCount = 0; } private void remove(Object id) { - map.remove(id); + Object ret = map.remove(id); + if (ret instanceof BeanRef) { + weakCount--; + } } private void deleted(Object id) { @@ -245,7 +318,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { deleteSet = new HashSet<>(); } deleteSet.add(id); - map.remove(id); + remove(id); } /** @@ -254,6 +327,10 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { void dirtyBeans(SpiBeanTypeManager manager, List list) { final SpiBeanType beanType = manager.beanType(rootType); for (Object value : map.values()) { + if (queue != null && value instanceof BeanRef) { + value = ((BeanRef) value).get(); + if (value == null) continue; + } EntityBean bean = (EntityBean) value; if (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean)) { list.add(value); @@ -262,4 +339,20 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { } } + private static class BeanRef extends WeakReference { + + private final ClassContext classContext; + private final Object key; + + private BeanRef(ClassContext classContext, Object key, Object referent, ReferenceQueue q) { + super(referent, q); + this.classContext = classContext; + this.key = key; + } + + private void expunge() { + classContext.remove(key); + } + + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/WeakPersistenceContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/WeakPersistenceContext.java deleted file mode 100644 index 4d7371bf9..000000000 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/WeakPersistenceContext.java +++ /dev/null @@ -1,272 +0,0 @@ -package io.ebeaninternal.server.transaction; - -import io.ebean.bean.EntityBean; -import io.ebean.bean.PersistenceContext; -import io.ebeaninternal.api.SpiBeanType; -import io.ebeaninternal.api.SpiBeanTypeManager; -import io.ebeaninternal.api.SpiPersistenceContext; - -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.*; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Weak reference based PersistenceContext for "streaming queries". - */ -final class WeakPersistenceContext implements SpiPersistenceContext { - - private final HashMap, WeakClassContext> typeCache = new HashMap<>(); - private final ReentrantLock lock = new ReentrantLock(); - - WeakPersistenceContext() { - } - - /** - * Load from the initiating persistence context. - */ - void add(Class rootType, Set deleteSet, Map map) { - typeCache.put(rootType, new WeakClassContext( rootType, deleteSet, map)); - } - - @Override - public PersistenceContext forIterate() { - return this; - } - - @Override - public void put(Class rootType, Object id, Object bean) { - lock.lock(); - try { - classContext(rootType).put(id, bean); - } finally { - lock.unlock(); - } - } - - @Override - public Object putIfAbsent(Class rootType, Object id, Object bean) { - lock.lock(); - try { - return classContext(rootType).putIfAbsent(id, bean); - } finally { - lock.unlock(); - } - } - - @Override - public Object get(Class rootType, Object id) { - lock.lock(); - try { - return classContext(rootType).get(id); - } finally { - lock.unlock(); - } - } - - @Override - public WithOption getWithOption(Class rootType, Object id) { - lock.lock(); - try { - return classContext(rootType).getWithOption(id); - } finally { - lock.unlock(); - } - } - - @Override - public int size(Class rootType) { - lock.lock(); - try { - WeakClassContext classMap = typeCache.get(rootType); - return classMap == null ? 0 : classMap.size(); - } finally { - lock.unlock(); - } - } - - @Override - public void clear() { - lock.lock(); - try { - typeCache.clear(); - } finally { - lock.unlock(); - } - } - - @Override - public void clear(Class rootType) { - lock.lock(); - try { - WeakClassContext classMap = typeCache.get(rootType); - if (classMap != null) { - classMap.clear(); - } - } finally { - lock.unlock(); - } - } - - @Override - public void deleted(Class rootType, Object id) { - lock.lock(); - try { - WeakClassContext classMap = typeCache.get(rootType); - if (classMap != null && id != null) { - classMap.deleted(id); - } - } finally { - lock.unlock(); - } - } - - @Override - public void clear(Class rootType, Object id) { - lock.lock(); - try { - WeakClassContext classMap = typeCache.get(rootType); - if (classMap != null && id != null) { - classMap.remove(id); - } - } finally { - lock.unlock(); - } - } - - @Override - public List dirtyBeans(SpiBeanTypeManager manager) { - lock.lock(); - try { - List list = new ArrayList<>(); - for (WeakClassContext classContext : typeCache.values()) { - classContext.dirtyBeans(manager, list); - } - return list; - } finally { - lock.unlock(); - } - } - - @Override - public String toString() { - lock.lock(); - try { - return typeCache.toString(); - } finally { - lock.unlock(); - } - } - - private WeakClassContext classContext(Class rootType) { - return typeCache.computeIfAbsent(rootType, k -> new WeakClassContext(rootType)); - } - - private static class WeakClassContext { - - private final ReferenceQueue queue = new ReferenceQueue<>(); - private final Map map = new HashMap<>(); - private final Class rootType; - private Set deleteSet; - - private WeakClassContext(Class rootType) { - this.rootType = rootType; - } - - private WeakClassContext(Class rootType, Set initialDeleteSet, Map initialMap) { - this.rootType = rootType; - this.deleteSet = initialDeleteSet == null ? null : new HashSet<>(initialDeleteSet); - for (Map.Entry entry : initialMap.entrySet()) { - Object id = entry.getKey(); - map.put(id, new BeanRef(id, entry.getValue(), queue)); - } - } - - private void expungeStaleEntries() { - Reference ref; - while ((ref = queue.poll()) != null) { - map.remove(((BeanRef)ref).key()); - } - } - - @Override - public String toString() { - return "size:" + map.size(); - } - - private Object get(Object id) { - expungeStaleEntries(); - Reference ref = map.get(id); - return ref == null ? null : ref.get(); - } - - private WithOption getWithOption(Object id) { - if (deleteSet != null && deleteSet.contains(id)) { - return WithOption.DELETED; - } - Object bean = get(id); - return (bean == null) ? null : new WithOption(bean); - } - - private Object putIfAbsent(Object id, Object bean) { - Object existingValue = get(id); - if (existingValue != null) { - // it is not absent - return existingValue; - } - // put the new value and return null indicating the put was successful - put(id, bean); - return null; - } - - private void put(Object id, Object b) { - expungeStaleEntries(); - map.put(id, new BeanRef(id, b, queue)); - } - - private int size() { - return map.size(); - } - - private void clear() { - map.clear(); - } - - private void remove(Object id) { - map.remove(id); - } - - private void deleted(Object id) { - if (deleteSet == null) { - deleteSet = new HashSet<>(); - } - deleteSet.add(id); - map.remove(id); - } - - /** - * Add the dirty beans to the list. - */ - void dirtyBeans(SpiBeanTypeManager manager, List list) { - final SpiBeanType beanType = manager.beanType(rootType); - for (BeanRef value : map.values()) { - EntityBean bean = (EntityBean) value.get(); - if (bean != null && (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean))) { - list.add(bean); - } - } - } - } - - private static class BeanRef extends WeakReference { - private final Object key; - private BeanRef(Object key, Object referent, ReferenceQueue q) { - super(referent, q); - this.key = key; - } - Object key() { - return key; - } - } -} diff --git a/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java b/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java index ea894091a..4d79e5b89 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java +++ b/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java @@ -2,16 +2,23 @@ package org.tests.basic; import io.ebean.BaseTestCase; import io.ebean.DB; +import io.ebean.Query; +import io.ebean.Transaction; +import io.ebeaninternal.api.SpiPersistenceContext; +import io.ebeaninternal.api.SpiTransaction; + import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; +import java.util.List; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class TestPersistenceContext extends BaseTestCase { @@ -112,6 +119,65 @@ public class TestPersistenceContext extends BaseTestCase { } }); } + } + + @Disabled // run manually + @Test + void testPcScopes() throws InterruptedException { + for (int i = 0; i < 5000; i++) { + Customer c = new Customer(); + c.setName("Customer #" + i); + DB.save(c); + Order o = new Order(); + o.setCustomer(c); + DB.save(o); + } + + try (Transaction txn = DB.beginTransaction()) { + List first100 = DB.find(Customer.class).where().le("id", 100).findList(); + assertEquals(100, first100.size()); + for (Customer c : first100) { + c.setSmallnote("one of the first 100"); + } + + Customer[] lastBean = new Customer[1]; + DB.find(Customer.class).setLazyLoadBatchSize(1).findEach(customer -> { + if (customer.getId() <= 100) { + assertEquals("one of the first 100", customer.getSmallnote()); + // nested finds + DB.find(Order.class).where().eq("customer", customer).findEach(20, consumer->{}); + } else { + assertNotEquals("one of the first 100", customer.getSmallnote()); + } + lastBean[0] = customer; + }); + + SpiPersistenceContext pc = ((SpiTransaction) txn).getPersistenceContext(); + System.out.println(pc.toString()); // Customer=size:5000 (4900 weak), Order=size:100 (100 weak) + + System.gc(); + Thread.sleep(100); + pc.get(Customer.class, 1); // trigger expungeStaleEntries + pc.get(Order.class, 1); // Checkme: Would it be a problem, that every classContext has its own queue + System.out.println(pc.toString()); // Customer=size:101 (1 weak), no orders + + first100 = DB.find(Customer.class).where().le("id", 100).findList(); + for (Customer c : first100) { + assertEquals("one of the first 100", c.getSmallnote()); + } + Customer lastBeanFromDb = DB.find(Customer.class).setId(lastBean[0].getId()).findOne(); + assertTrue(lastBeanFromDb == lastBean[0]); + + // read 200 + DB.find(Customer.class).where().le("id", 200).findList(); + System.out.println(pc.toString()); // Customer=size:201 (1 weak) + + lastBean[0] = null; + lastBeanFromDb = null; + System.gc(); + Thread.sleep(100); + System.out.println(pc.toString()); // Customer=size:200 (0 weak) + } } } From 100832ddd926e45ebe02b03afdcab8351bf43d5d Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Mon, 11 Oct 2021 17:16:34 +0200 Subject: [PATCH 3/5] Changed test to test pc.beginIterate / endIterate --- .../DefaultPersistenceContextTest.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java index 1aa4af51c..236d3a2fc 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java @@ -145,24 +145,24 @@ class DefaultPersistenceContextTest { } @Test - void forIterate() { + void beginIterate() { final DefaultPersistenceContext pc = pcWith42(); final Object origCustomer42 = pc.get(Customer.class, 42); // act - final PersistenceContext pcIterate = pc.forIterate(); - assertThat(pc).isNotSameAs(pcIterate); - assertThat(pcIterate.size(Customer.class)).isEqualTo(1); + pc.beginIterate(); + assertThat(pc.size(Customer.class)).isEqualTo(1); // assert same instance (bean effectively transferred to iterator persistence context - final Object customer42 = pcIterate.get(Customer.class, 42); + final Object customer42 = pc.get(Customer.class, 42); assertThat(customer42).isSameAs(origCustomer42); - final PersistenceContext.WithOption option = pcIterate.getWithOption(Customer.class, 42); + final PersistenceContext.WithOption option = pc.getWithOption(Customer.class, 42); assertThat(option.getBean()).isSameAs(origCustomer42); + pc.endIterate(); } @Test - void forIterate_many() { + void beginIterate_many() throws InterruptedException { DefaultPersistenceContext pc = new DefaultPersistenceContext(); addCustomers(pc, 1, 100); addContacts(pc, 1, 1010); @@ -170,9 +170,22 @@ class DefaultPersistenceContextTest { assertThat(pc.size(Contact.class)).isEqualTo(1010); // act - final PersistenceContext pcIterate = pc.forIterate(); - assertThat(pcIterate.size(Customer.class)).isEqualTo(100); - assertThat(pcIterate.size(Contact.class)).isEqualTo(1010); + pc.beginIterate(); + assertThat(pc.size(Customer.class)).isEqualTo(100); + assertThat(pc.size(Contact.class)).isEqualTo(1010); + + addCustomers(pc, 200, 100); + addContacts(pc, 2000, 1010); + + assertThat(pc.size(Customer.class)).isEqualTo(200); + assertThat(pc.size(Contact.class)).isEqualTo(2020); + pc.endIterate(); + + System.gc(); + Thread.sleep(50); // give the GC some time + + assertThat(pc.size(Customer.class)).isEqualTo(100); + assertThat(pc.size(Contact.class)).isEqualTo(1010); } @Test From 5322ab3e7e9c3b81f92b72a93de26f18522f94d7 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Fri, 8 Oct 2021 09:27:38 +0200 Subject: [PATCH 4/5] Remove: ReadJson.resetContext as it is not used --- .../io/ebean/text/json/JsonBeanReader.java | 2 +- .../ebeaninternal/api/json/SpiJsonReader.java | 2 +- .../server/deploy/BeanDescriptorJsonHelp.java | 2 +- .../server/text/json/DJsonBeanReader.java | 4 ++-- .../server/text/json/ReadJson.java | 18 +++++------------- .../io/ebean/text/json/JsonBeanReaderTest.java | 2 +- 6 files changed, 11 insertions(+), 19 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/text/json/JsonBeanReader.java b/ebean-api/src/main/java/io/ebean/text/json/JsonBeanReader.java index fc948016f..7acdb976f 100644 --- a/ebean-api/src/main/java/io/ebean/text/json/JsonBeanReader.java +++ b/ebean-api/src/main/java/io/ebean/text/json/JsonBeanReader.java @@ -20,7 +20,7 @@ public interface JsonBeanReader { /** * Create a new reader taking the context from the existing one but using a new JsonParser. */ - JsonBeanReader forJson(JsonParser moreJson, boolean resetContext); + JsonBeanReader forJson(JsonParser moreJson); /** * Add a bean explicitly to the persistence context. diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java b/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java index aaf4e2ce8..4594cbe25 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java @@ -14,7 +14,7 @@ public interface SpiJsonReader { PersistenceContext getPersistenceContext(); - SpiJsonReader forJson(JsonParser moreJson, boolean resetContext); + SpiJsonReader forJson(JsonParser moreJson); void persistenceContextPut(Object beanId, T currentBean); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java index 5abb988c7..b11c987bb 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java @@ -90,7 +90,7 @@ final class BeanDescriptorJsonHelp { return null; } JsonParser newParser = node.traverse(); - SpiJsonReader newReader = jsonRead.forJson(newParser, false); + SpiJsonReader newReader = jsonRead.forJson(newParser); // check for the discriminator value to determine the correct sub type String discColumn = inheritInfo.getRoot().getDiscriminatorColumn(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonBeanReader.java b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonBeanReader.java index 8193d397c..79465e037 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonBeanReader.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonBeanReader.java @@ -45,7 +45,7 @@ public final class DJsonBeanReader implements JsonBeanReader { } @Override - public JsonBeanReader forJson(JsonParser moreJson, boolean resetContext) { - return new DJsonBeanReader<>(desc, readJson.forJson(moreJson, resetContext)); + public JsonBeanReader forJson(JsonParser moreJson) { + return new DJsonBeanReader<>(desc, readJson.forJson(moreJson)); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java index f685188b6..7acf4e531 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java @@ -50,22 +50,14 @@ public final class ReadJson implements SpiJsonReader { /** * Construct when transferring load context, persistence context, object mapper etc to a new ReadJson instance. */ - private ReadJson(JsonParser moreJson, ReadJson source, boolean resetContext) { + private ReadJson(JsonParser moreJson, ReadJson source) { this.parser = moreJson; this.rootDesc = source.rootDesc; this.pathStack = source.pathStack; this.visitorMap = source.visitorMap; this.objectMapper = source.objectMapper; - if (resetContext) { - this.persistenceContext = new DefaultPersistenceContext(); - this.loadContext = source.loadContext; - if (loadContext != null) { - loadContext.resetPersistenceContext(persistenceContext); - } - } else { - this.persistenceContext = source.persistenceContext; - this.loadContext = source.loadContext; - } + this.persistenceContext = source.persistenceContext; + this.loadContext = source.loadContext; } private LoadContext initLoadContext(BeanDescriptor desc, JsonReadOptions readOptions) { @@ -96,8 +88,8 @@ public final class ReadJson implements SpiJsonReader { * Return a new instance of ReadJson using the existing context but with a new JsonParser. */ @Override - public SpiJsonReader forJson(JsonParser moreJson, boolean resetContext) { - return new ReadJson(moreJson, this, resetContext); + public SpiJsonReader forJson(JsonParser moreJson) { + return new ReadJson(moreJson, this); } /** diff --git a/ebean-test/src/test/java/io/ebean/text/json/JsonBeanReaderTest.java b/ebean-test/src/test/java/io/ebean/text/json/JsonBeanReaderTest.java index 6cf8f52db..a60b18d3f 100644 --- a/ebean-test/src/test/java/io/ebean/text/json/JsonBeanReaderTest.java +++ b/ebean-test/src/test/java/io/ebean/text/json/JsonBeanReaderTest.java @@ -47,7 +47,7 @@ public class JsonBeanReaderTest extends BaseTestCase { beanReader.read(); JsonParser more = getParser(); - JsonBeanReader moreReader = beanReader.forJson(more, true); + JsonBeanReader moreReader = beanReader.forJson(more); Customer customer = moreReader.read(); assertThat(customer.getId()).isEqualTo(42); From 2f277938e58890711e91538a6b9fe7487fca2aa4 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Mon, 11 Oct 2021 17:48:09 +0200 Subject: [PATCH 5/5] Remove LoadContext.resetPersistenceContext --- .../java/io/ebeaninternal/api/LoadContext.java | 8 -------- .../server/loadcontext/DLoadBeanContext.java | 2 +- .../server/loadcontext/DLoadContext.java | 15 +-------------- .../server/loadcontext/DLoadManyContext.java | 2 +- 4 files changed, 3 insertions(+), 24 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java b/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java index 51f02826c..b23a183bb 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java @@ -36,14 +36,6 @@ public interface LoadContext { */ PersistenceContext getPersistenceContext(); - /** - * Set the persistence context used by this query and future lazy loading. - *

- * Used by query iterator when processing large result sets. - *

- */ - void resetPersistenceContext(PersistenceContext persistenceContext); - /** * Register a Bean for lazy loading. */ 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 2dd6c1378..427d59de2 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 @@ -46,7 +46,7 @@ final class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext /** * Reset the buffers after a query iterator reset. */ - public void clear() { + private void clear() { if (bufferList != null) { bufferList.clear(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java index 018a9f647..bfef45804 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java @@ -57,7 +57,7 @@ public final class DLoadContext implements LoadContext { private final ProfileLocation profileLocation; private final ProfilingListener profilingListener; private final Map nodePathMap = new HashMap<>(); - private PersistenceContext persistenceContext; + private final PersistenceContext persistenceContext; private List secQuery; private Object tenantId; @@ -251,19 +251,6 @@ public final class DLoadContext implements LoadContext { return persistenceContext; } - @Override - public void resetPersistenceContext(PersistenceContext persistenceContext) { - this.persistenceContext = persistenceContext; - // clear the load contexts for beans and beanCollections - for (DLoadBeanContext beanContext : beanMap.values()) { - beanContext.clear(); - } - for (DLoadManyContext manyContext : manyMap.values()) { - manyContext.clear(); - } - this.rootBeanContext.clear(); - } - @Override public void register(String path, EntityBeanIntercept ebi) { getBeanContext(path).register(ebi); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java index 8473e148b..68bd61d81 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -48,7 +48,7 @@ final class DLoadManyContext extends DLoadBaseContext implements LoadManyContext /** * Reset the buffers for a query iterator reset. */ - public void clear() { + private void clear() { if (bufferList != null) { bufferList.clear(); }