mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-21 22:18:55 +00:00
Merge pull request #2411 from ebean-orm/feature/weak-reference-persistence-context
Weak reference based persistence context for streaming queries
This commit is contained in:
@@ -60,21 +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();
|
||||
|
||||
/**
|
||||
* Return a new Persistence context during iteration of large query result.
|
||||
* Signalizes the PersistenceContext, the end for large query iteration.
|
||||
*/
|
||||
PersistenceContext forIterateReset();
|
||||
|
||||
/**
|
||||
* Return true if the persistence context has grown and hit the 'reset limit'
|
||||
* during large query iteration.
|
||||
*/
|
||||
boolean resetLimit();
|
||||
|
||||
void endIterate();
|
||||
|
||||
/**
|
||||
* Wrapper on a bean to also indicate if a bean has been deleted.
|
||||
* <p>
|
||||
|
||||
@@ -20,7 +20,7 @@ public interface JsonBeanReader<T> {
|
||||
/**
|
||||
* Create a new reader taking the context from the existing one but using a new JsonParser.
|
||||
*/
|
||||
JsonBeanReader<T> forJson(JsonParser moreJson, boolean resetContext);
|
||||
JsonBeanReader<T> forJson(JsonParser moreJson);
|
||||
|
||||
/**
|
||||
* Add a bean explicitly to the persistence context.
|
||||
|
||||
@@ -36,14 +36,6 @@ public interface LoadContext {
|
||||
*/
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
/**
|
||||
* Set the persistence context used by this query and future lazy loading.
|
||||
* <p>
|
||||
* Used by query iterator when processing large result sets.
|
||||
* </p>
|
||||
*/
|
||||
void resetPersistenceContext(PersistenceContext persistenceContext);
|
||||
|
||||
/**
|
||||
* Register a Bean for lazy loading.
|
||||
*/
|
||||
|
||||
@@ -14,7 +14,7 @@ public interface SpiJsonReader {
|
||||
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
SpiJsonReader forJson(JsonParser moreJson, boolean resetContext);
|
||||
SpiJsonReader forJson(JsonParser moreJson);
|
||||
|
||||
<T> void persistenceContextPut(Object beanId, T currentBean);
|
||||
|
||||
|
||||
@@ -225,6 +225,9 @@ public final class OrmQueryRequest<T> 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<T> extends BeanRequest implements SpiOrmQuery
|
||||
*/
|
||||
@Override
|
||||
public void rollbackTransIfRequired() {
|
||||
if (Type.ITERATE == query.getType()) {
|
||||
persistenceContext.endIterate();
|
||||
}
|
||||
if (createdTransaction) {
|
||||
try {
|
||||
transaction.end();
|
||||
@@ -263,20 +269,6 @@ public final class OrmQueryRequest<T> 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.
|
||||
@@ -292,11 +284,7 @@ public final class OrmQueryRequest<T> 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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,6 +294,9 @@ public final class OrmQueryRequest<T> 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()) {
|
||||
|
||||
@@ -90,7 +90,7 @@ final class BeanDescriptorJsonHelp<T> {
|
||||
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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public final class DLoadContext implements LoadContext {
|
||||
private final ProfileLocation profileLocation;
|
||||
private final ProfilingListener profilingListener;
|
||||
private final Map<String, ObjectGraphNode> nodePathMap = new HashMap<>();
|
||||
private PersistenceContext persistenceContext;
|
||||
private final PersistenceContext persistenceContext;
|
||||
private List<OrmQueryProperties> 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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ final class CQueryIteratorSimple<T> implements QueryIterator<T> {
|
||||
public boolean hasNext() {
|
||||
boolean ret = false;
|
||||
try {
|
||||
request.flushPersistenceContextOnIterate();
|
||||
ret = cquery.hasNext();
|
||||
return ret;
|
||||
} catch (SQLException e) {
|
||||
|
||||
@@ -35,8 +35,6 @@ final class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
|
||||
try {
|
||||
if (buffer.isEmpty() && moreToLoad) {
|
||||
// load buffer
|
||||
request.flushPersistenceContextOnIterate();
|
||||
|
||||
int i = -1;
|
||||
while (moreToLoad && ++i < bufferSize) {
|
||||
if (cquery.hasNext()) {
|
||||
|
||||
@@ -45,7 +45,7 @@ public final class DJsonBeanReader<T> implements JsonBeanReader<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonBeanReader<T> forJson(JsonParser moreJson, boolean resetContext) {
|
||||
return new DJsonBeanReader<>(desc, readJson.forJson(moreJson, resetContext));
|
||||
public JsonBeanReader<T> forJson(JsonParser moreJson) {
|
||||
return new DJsonBeanReader<>(desc, readJson.forJson(moreJson));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+100
-91
@@ -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;
|
||||
|
||||
@@ -25,14 +28,16 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
*/
|
||||
public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
|
||||
/**
|
||||
* Map used hold caches. One cache per bean type.
|
||||
*/
|
||||
private final HashMap<Class<?>, ClassContext> typeCache = new HashMap<>();
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
|
||||
|
||||
private int putCount;
|
||||
/**
|
||||
* 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.
|
||||
@@ -40,46 +45,22 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
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<Class<?>, ClassContext> entry : parent.typeCache.entrySet()) {
|
||||
typeCache.put(entry.getKey(), entry.getValue().copy(initial));
|
||||
@Override
|
||||
public void beginIterate() {
|
||||
lock.lock();
|
||||
try {
|
||||
iterateDepth++;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetLimit() {
|
||||
public void endIterate() {
|
||||
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;
|
||||
iterateDepth--;
|
||||
expungeStaleEntries(); // when leaving the iterator, cleanup.
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -92,8 +73,8 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
public void put(Class<?> rootType, Object id, Object bean) {
|
||||
lock.lock();
|
||||
try {
|
||||
putCount++;
|
||||
classContext(rootType).put(id, bean);
|
||||
expungeStaleEntries();
|
||||
classContext(rootType).useReferences(iterateDepth > 0).put(id, bean);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -103,8 +84,8 @@ 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);
|
||||
expungeStaleEntries();
|
||||
return classContext(rootType).useReferences(iterateDepth > 0).putIfAbsent(id, bean);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -117,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();
|
||||
@@ -127,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();
|
||||
@@ -140,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 {
|
||||
@@ -155,6 +139,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
lock.lock();
|
||||
try {
|
||||
typeCache.clear();
|
||||
expungeStaleEntries();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -168,6 +153,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
if (classMap != null) {
|
||||
classMap.clear();
|
||||
}
|
||||
expungeStaleEntries();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -181,6 +167,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
if (classMap != null && id != null) {
|
||||
classMap.deleted(id);
|
||||
}
|
||||
expungeStaleEntries();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -194,6 +181,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
if (classMap != null && id != null) {
|
||||
classMap.remove(id);
|
||||
}
|
||||
expungeStaleEntries();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -203,6 +191,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
public List<Object> dirtyBeans(SpiBeanTypeManager manager) {
|
||||
lock.lock();
|
||||
try {
|
||||
expungeStaleEntries();
|
||||
List<Object> list = new ArrayList<>();
|
||||
for (ClassContext classContext : typeCache.values()) {
|
||||
classContext.dirtyBeans(manager, list);
|
||||
@@ -213,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 <code>useReferences</code> was called with
|
||||
* <code>true</code>. 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();
|
||||
@@ -224,69 +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<Object, Object> map = new HashMap<>();
|
||||
private final Class<?> rootType;
|
||||
private final ReferenceQueue<Object> queue;
|
||||
private Set<Object> deleteSet;
|
||||
private int initialSize;
|
||||
private ClassContext parent;
|
||||
|
||||
private boolean useReferences;
|
||||
private int weakCount;
|
||||
|
||||
private ClassContext(Class<?> rootType) {
|
||||
|
||||
private ClassContext(Class<?> rootType, ReferenceQueue<Object> queue) {
|
||||
this.rootType = rootType;
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create as a shallow copy.
|
||||
* When called with "true", initialize referenceQueue and store BeanRefs instead
|
||||
* of real object references.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
public ClassContext useReferences(boolean useReferences) {
|
||||
this.useReferences = useReferences;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
return "size:" + map.size() + " (" + weakCount + " weak)";
|
||||
}
|
||||
|
||||
private Object get(Object id) {
|
||||
Object bean = (parent == null) ? null : parent.get(id);
|
||||
return bean != null ? bean : map.get(id);
|
||||
Object ret = map.get(id);
|
||||
if (ret instanceof BeanRef) {
|
||||
return ((BeanRef)ret).get();
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private WithOption getWithOption(Object id) {
|
||||
@@ -304,24 +284,33 @@ 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() {
|
||||
return map.size() + initialSize;
|
||||
return map.size();
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -329,7 +318,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
deleteSet = new HashSet<>();
|
||||
}
|
||||
deleteSet.add(id);
|
||||
map.remove(id);
|
||||
remove(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,6 +327,10 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
void dirtyBeans(SpiBeanTypeManager manager, List<Object> 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);
|
||||
@@ -346,4 +339,20 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
||||
}
|
||||
}
|
||||
|
||||
private static class BeanRef extends WeakReference<Object> {
|
||||
|
||||
private final ClassContext classContext;
|
||||
private final Object key;
|
||||
|
||||
private BeanRef(ClassContext classContext, Object key, Object referent, ReferenceQueue<? super Object> q) {
|
||||
super(referent, q);
|
||||
this.classContext = classContext;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
private void expunge() {
|
||||
classContext.remove(key);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class JsonBeanReaderTest extends BaseTestCase {
|
||||
beanReader.read();
|
||||
|
||||
JsonParser more = getParser();
|
||||
JsonBeanReader<Customer> moreReader = beanReader.forJson(more, true);
|
||||
JsonBeanReader<Customer> moreReader = beanReader.forJson(more);
|
||||
|
||||
Customer customer = moreReader.read();
|
||||
assertThat(customer.getId()).isEqualTo(42);
|
||||
|
||||
+40
-87
@@ -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,24 +145,24 @@ public class DefaultPersistenceContextTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public 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
|
||||
public void forIterate_many() {
|
||||
void beginIterate_many() throws InterruptedException {
|
||||
DefaultPersistenceContext pc = new DefaultPersistenceContext();
|
||||
addCustomers(pc, 1, 100);
|
||||
addContacts(pc, 1, 1010);
|
||||
@@ -185,50 +170,26 @@ public 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
|
||||
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 +209,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Customer> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user