mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-11-12 22:41:35 +00:00
Use WeakReferences when in iterate query mode
This commit is contained in:
1 parent
ea3fb833a4
commit
3f5009aa91
5 files changed
+195
-298
No files matched your search
@@ -60,10 +60,15 @@ public interface PersistenceContext {
|
|||||||
int size(Class<?> rootType);
|
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.
|
* Wrapper on a bean to also indicate if a bean has been deleted.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -225,6 +225,9 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
|||||||
createdTransaction = true;
|
createdTransaction = true;
|
||||||
}
|
}
|
||||||
persistenceContext = persistenceContext(query, transaction);
|
persistenceContext = persistenceContext(query, transaction);
|
||||||
|
if (Type.ITERATE == query.getType()) {
|
||||||
|
persistenceContext.beginIterate();
|
||||||
|
}
|
||||||
loadContext = new DLoadContext(this, secondaryQueries);
|
loadContext = new DLoadContext(this, secondaryQueries);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,6 +236,9 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void rollbackTransIfRequired() {
|
public void rollbackTransIfRequired() {
|
||||||
|
if (Type.ITERATE == query.getType()) {
|
||||||
|
persistenceContext.endIterate();
|
||||||
|
}
|
||||||
if (createdTransaction) {
|
if (createdTransaction) {
|
||||||
try {
|
try {
|
||||||
transaction.end();
|
transaction.end();
|
||||||
@@ -278,11 +284,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
|||||||
if (scope == PersistenceContextScope.QUERY || t == null) {
|
if (scope == PersistenceContextScope.QUERY || t == null) {
|
||||||
return new DefaultPersistenceContext();
|
return new DefaultPersistenceContext();
|
||||||
}
|
}
|
||||||
if (Type.ITERATE == query.getType()) {
|
return t.getPersistenceContext();
|
||||||
return t.getPersistenceContext().forIterate();
|
|
||||||
} else {
|
|
||||||
return t.getPersistenceContext();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,6 +294,9 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void endTransIfRequired() {
|
public void endTransIfRequired() {
|
||||||
|
if (Type.ITERATE == query.getType()) {
|
||||||
|
persistenceContext.endIterate();
|
||||||
|
}
|
||||||
if (createdTransaction && transaction.isActive()) {
|
if (createdTransaction && transaction.isActive()) {
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
if (query.getType().isUpdate()) {
|
if (query.getType().isUpdate()) {
|
||||||
|
|||||||
+112
-19
@@ -6,6 +6,9 @@ import io.ebeaninternal.api.SpiBeanType;
|
|||||||
import io.ebeaninternal.api.SpiBeanTypeManager;
|
import io.ebeaninternal.api.SpiBeanTypeManager;
|
||||||
import io.ebeaninternal.api.SpiPersistenceContext;
|
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.*;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
@@ -27,6 +30,14 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
|
|
||||||
private final HashMap<Class<?>, ClassContext> typeCache = new HashMap<>();
|
private final HashMap<Class<?>, ClassContext> typeCache = new HashMap<>();
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
private final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Create a new PersistenceContext.
|
||||||
@@ -34,16 +45,25 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
public DefaultPersistenceContext() {
|
public DefaultPersistenceContext() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a PersistenceContext to use for streaming queries.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public PersistenceContext forIterate() {
|
public void beginIterate() {
|
||||||
WeakPersistenceContext weak = new WeakPersistenceContext();
|
lock.lock();
|
||||||
for (ClassContext value : typeCache.values()) {
|
try {
|
||||||
weak.add(value.rootType, value.deleteSet, value.map);
|
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) {
|
public void put(Class<?> rootType, Object id, Object bean) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
classContext(rootType).put(id, bean);
|
expungeStaleEntries();
|
||||||
|
classContext(rootType).useReferences(iterateDepth > 0).put(id, bean);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -63,7 +84,8 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
public Object putIfAbsent(Class<?> rootType, Object id, Object bean) {
|
public Object putIfAbsent(Class<?> rootType, Object id, Object bean) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
return classContext(rootType).putIfAbsent(id, bean);
|
expungeStaleEntries();
|
||||||
|
return classContext(rootType).useReferences(iterateDepth > 0).putIfAbsent(id, bean);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -76,6 +98,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
public Object get(Class<?> rootType, Object id) {
|
public Object get(Class<?> rootType, Object id) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
|
expungeStaleEntries();
|
||||||
return classContext(rootType).get(id);
|
return classContext(rootType).get(id);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
@@ -86,6 +109,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
public WithOption getWithOption(Class<?> rootType, Object id) {
|
public WithOption getWithOption(Class<?> rootType, Object id) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
|
expungeStaleEntries();
|
||||||
return classContext(rootType).getWithOption(id);
|
return classContext(rootType).getWithOption(id);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
@@ -99,6 +123,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
public int size(Class<?> rootType) {
|
public int size(Class<?> rootType) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
|
expungeStaleEntries();
|
||||||
ClassContext classMap = typeCache.get(rootType);
|
ClassContext classMap = typeCache.get(rootType);
|
||||||
return classMap == null ? 0 : classMap.size();
|
return classMap == null ? 0 : classMap.size();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -114,6 +139,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
typeCache.clear();
|
typeCache.clear();
|
||||||
|
expungeStaleEntries();
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -127,6 +153,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
if (classMap != null) {
|
if (classMap != null) {
|
||||||
classMap.clear();
|
classMap.clear();
|
||||||
}
|
}
|
||||||
|
expungeStaleEntries();
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -140,6 +167,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
if (classMap != null && id != null) {
|
if (classMap != null && id != null) {
|
||||||
classMap.deleted(id);
|
classMap.deleted(id);
|
||||||
}
|
}
|
||||||
|
expungeStaleEntries();
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -153,6 +181,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
if (classMap != null && id != null) {
|
if (classMap != null && id != null) {
|
||||||
classMap.remove(id);
|
classMap.remove(id);
|
||||||
}
|
}
|
||||||
|
expungeStaleEntries();
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -162,6 +191,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
public List<Object> dirtyBeans(SpiBeanTypeManager manager) {
|
public List<Object> dirtyBeans(SpiBeanTypeManager manager) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
|
expungeStaleEntries();
|
||||||
List<Object> list = new ArrayList<>();
|
List<Object> list = new ArrayList<>();
|
||||||
for (ClassContext classContext : typeCache.values()) {
|
for (ClassContext classContext : typeCache.values()) {
|
||||||
classContext.dirtyBeans(manager, list);
|
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 <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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
|
expungeStaleEntries();
|
||||||
return typeCache.toString();
|
return typeCache.toString();
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
@@ -183,26 +226,47 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ClassContext classContext(Class<?> rootType) {
|
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 static class ClassContext {
|
||||||
|
|
||||||
private final Map<Object, Object> map = new HashMap<>();
|
private final Map<Object, Object> map = new HashMap<>();
|
||||||
private final Class<?> rootType;
|
private final Class<?> rootType;
|
||||||
|
private final ReferenceQueue<Object> queue;
|
||||||
private Set<Object> deleteSet;
|
private Set<Object> deleteSet;
|
||||||
|
|
||||||
|
private boolean useReferences;
|
||||||
|
private int weakCount;
|
||||||
|
|
||||||
private ClassContext(Class<?> rootType) {
|
|
||||||
|
private ClassContext(Class<?> rootType, ReferenceQueue<Object> queue) {
|
||||||
this.rootType = rootType;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "size:" + map.size();
|
return "size:" + map.size() + " (" + weakCount + " weak)";
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object get(Object id) {
|
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) {
|
private WithOption getWithOption(Object id) {
|
||||||
@@ -220,12 +284,17 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
return existingValue;
|
return existingValue;
|
||||||
}
|
}
|
||||||
// put the new value and return null indicating the put was successful
|
// put the new value and return null indicating the put was successful
|
||||||
map.put(id, bean);
|
put(id, bean);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void put(Object id, Object b) {
|
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() {
|
private int size() {
|
||||||
@@ -234,10 +303,14 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
|
|
||||||
private void clear() {
|
private void clear() {
|
||||||
map.clear();
|
map.clear();
|
||||||
|
weakCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void remove(Object id) {
|
private void remove(Object id) {
|
||||||
map.remove(id);
|
Object ret = map.remove(id);
|
||||||
|
if (ret instanceof BeanRef) {
|
||||||
|
weakCount--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleted(Object id) {
|
private void deleted(Object id) {
|
||||||
@@ -245,7 +318,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
deleteSet = new HashSet<>();
|
deleteSet = new HashSet<>();
|
||||||
}
|
}
|
||||||
deleteSet.add(id);
|
deleteSet.add(id);
|
||||||
map.remove(id);
|
remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -254,6 +327,10 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
|
|||||||
void dirtyBeans(SpiBeanTypeManager manager, List<Object> list) {
|
void dirtyBeans(SpiBeanTypeManager manager, List<Object> list) {
|
||||||
final SpiBeanType beanType = manager.beanType(rootType);
|
final SpiBeanType beanType = manager.beanType(rootType);
|
||||||
for (Object value : map.values()) {
|
for (Object value : map.values()) {
|
||||||
|
if (queue != null && value instanceof BeanRef) {
|
||||||
|
value = ((BeanRef) value).get();
|
||||||
|
if (value == null) continue;
|
||||||
|
}
|
||||||
EntityBean bean = (EntityBean) value;
|
EntityBean bean = (EntityBean) value;
|
||||||
if (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean)) {
|
if (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean)) {
|
||||||
list.add(value);
|
list.add(value);
|
||||||
@@ -262,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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
-272
@@ -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<Class<?>, WeakClassContext> typeCache = new HashMap<>();
|
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
|
||||||
|
|
||||||
WeakPersistenceContext() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load from the initiating persistence context.
|
|
||||||
*/
|
|
||||||
void add(Class<?> rootType, Set<Object> deleteSet, Map<Object, Object> 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<Object> dirtyBeans(SpiBeanTypeManager manager) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
List<Object> 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<Object> queue = new ReferenceQueue<>();
|
|
||||||
private final Map<Object, BeanRef> map = new HashMap<>();
|
|
||||||
private final Class<?> rootType;
|
|
||||||
private Set<Object> deleteSet;
|
|
||||||
|
|
||||||
private WeakClassContext(Class<?> rootType) {
|
|
||||||
this.rootType = rootType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private WeakClassContext(Class<?> rootType, Set<Object> initialDeleteSet, Map<Object, Object> initialMap) {
|
|
||||||
this.rootType = rootType;
|
|
||||||
this.deleteSet = initialDeleteSet == null ? null : new HashSet<>(initialDeleteSet);
|
|
||||||
for (Map.Entry<Object, Object> 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<Object> 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<Object> 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<Object> {
|
|
||||||
private final Object key;
|
|
||||||
private BeanRef(Object key, Object referent, ReferenceQueue<? super Object> q) {
|
|
||||||
super(referent, q);
|
|
||||||
this.key = key;
|
|
||||||
}
|
|
||||||
Object key() {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,16 +2,23 @@ package org.tests.basic;
|
|||||||
|
|
||||||
import io.ebean.BaseTestCase;
|
import io.ebean.BaseTestCase;
|
||||||
import io.ebean.DB;
|
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.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.tests.model.basic.Customer;
|
import org.tests.model.basic.Customer;
|
||||||
import org.tests.model.basic.Order;
|
import org.tests.model.basic.Order;
|
||||||
import org.tests.model.basic.ResetBasicData;
|
import org.tests.model.basic.ResetBasicData;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class TestPersistenceContext extends BaseTestCase {
|
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