setUseQueryCache(CacheMode useCache);
+ /**
+ * Calls {@link #setUseQueryCache(CacheMode)} with ON or OFF.
+ *
+ * @see Query#setUseQueryCache(CacheMode)
+ */
+ default Query setUseQueryCache(boolean enabled) {
+ return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
+ }
+
/**
* Set to true if this query should execute against the doc store.
*
diff --git a/src/main/java/io/ebean/Query.java b/src/main/java/io/ebean/Query.java
index 6a9c7ddaf..9d383ed8d 100644
--- a/src/main/java/io/ebean/Query.java
+++ b/src/main/java/io/ebean/Query.java
@@ -1325,10 +1325,17 @@ public interface Query {
Query setUseCache(boolean useCache);
/**
- * Set this to true to use the query cache.
+ * Set the {@link CacheMode} to use the query for executing this query.
*/
- Query setUseQueryCache(boolean useQueryCache);
-
+ Query setUseQueryCache(CacheMode useQueryCache);
+
+ /**
+ * Calls {@link #setUseQueryCache(CacheMode)} with ON or OFF.
+ */
+ default Query setUseQueryCache(boolean enabled) {
+ return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
+ }
+
/**
* Set to true if this query should execute against the doc store.
*
diff --git a/src/main/java/io/ebean/bean/BeanCollection.java b/src/main/java/io/ebean/bean/BeanCollection.java
index 31d546407..25d84de7d 100644
--- a/src/main/java/io/ebean/bean/BeanCollection.java
+++ b/src/main/java/io/ebean/bean/BeanCollection.java
@@ -226,4 +226,9 @@ public interface BeanCollection extends Serializable {
* additions and removals have been processed.
*/
void modifyReset();
+
+ /**
+ * Return a shallow copy of this collection that is modifiable.
+ */
+ BeanCollection getShallowCopy();
}
diff --git a/src/main/java/io/ebean/common/AbstractBeanCollection.java b/src/main/java/io/ebean/common/AbstractBeanCollection.java
index 456993b6d..e13a6dc1d 100644
--- a/src/main/java/io/ebean/common/AbstractBeanCollection.java
+++ b/src/main/java/io/ebean/common/AbstractBeanCollection.java
@@ -213,4 +213,16 @@ abstract class AbstractBeanCollection implements BeanCollection {
boolean holdsModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
+
+ /**
+ * Copies all relevant properties for a clone. See {@link #getShallowCopy()}
+ * @param other
+ */
+ protected void setFromOriginal(AbstractBeanCollection other) {
+ this.disableLazyLoad = other.disableLazyLoad;
+ this.ebeanServerName = other.ebeanServerName;
+ this.loader = other.loader;
+ this.ownerBean = other.ownerBean;
+ this.propertyName = other.propertyName;
+ }
}
diff --git a/src/main/java/io/ebean/common/BeanList.java b/src/main/java/io/ebean/common/BeanList.java
index c11ed6179..42d67b993 100644
--- a/src/main/java/io/ebean/common/BeanList.java
+++ b/src/main/java/io/ebean/common/BeanList.java
@@ -541,4 +541,11 @@ public final class BeanList extends AbstractBeanCollection implements List
}
}
+
+ @Override
+ public BeanCollection getShallowCopy() {
+ BeanList copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
+ copy.setFromOriginal(this);
+ return copy;
+ }
}
diff --git a/src/main/java/io/ebean/common/BeanMap.java b/src/main/java/io/ebean/common/BeanMap.java
index 0d049df0a..34bb51f9e 100644
--- a/src/main/java/io/ebean/common/BeanMap.java
+++ b/src/main/java/io/ebean/common/BeanMap.java
@@ -340,4 +340,10 @@ public final class BeanMap extends AbstractBeanCollection implements Ma
return map.values();
}
+ @Override
+ public BeanCollection getShallowCopy() {
+ BeanMap copy = new BeanMap<>(new LinkedHashMap<>(map));
+ copy.setFromOriginal(this);
+ return copy;
+ }
}
diff --git a/src/main/java/io/ebean/common/BeanSet.java b/src/main/java/io/ebean/common/BeanSet.java
index 6ad3e0072..c19564247 100644
--- a/src/main/java/io/ebean/common/BeanSet.java
+++ b/src/main/java/io/ebean/common/BeanSet.java
@@ -377,5 +377,11 @@ public final class BeanSet extends AbstractBeanCollection implements Set getShallowCopy() {
+ BeanSet copy = new BeanSet<>(new LinkedHashSet<>(set));
+ copy.setFromOriginal(this);
+ return copy;
+ }
}
diff --git a/src/main/java/io/ebean/common/CopyOnFirstWriteList.java b/src/main/java/io/ebean/common/CopyOnFirstWriteList.java
new file mode 100644
index 000000000..aedb58d30
--- /dev/null
+++ b/src/main/java/io/ebean/common/CopyOnFirstWriteList.java
@@ -0,0 +1,180 @@
+package io.ebean.common;
+
+import java.io.Serializable;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+
+/**
+ * List that copies itself on first write access. Needed to keep memory footprint low and the ability
+ * to modify lists from cache.
+ *
+ * @author Roland Praml, FOCONIS AG
+ */
+public final class CopyOnFirstWriteList extends AbstractList implements List, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The underlying List implementation.
+ */
+ private List list;
+
+
+ public CopyOnFirstWriteList(List list) {
+ super();
+ this.list = list;
+ }
+
+ private volatile boolean copied = false;
+
+ @Override
+ public int size() {
+ return list.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return list.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return list.contains(o);
+ }
+
+ @Override
+ public Object[] toArray() {
+ return list.toArray();
+ }
+
+ @Override
+ public T[] toArray(T[] a) {
+ return list.toArray(a);
+ }
+
+ @Override
+ public boolean add(E e) {
+ checkCopyOnWrite();
+ return list.add(e);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ checkCopyOnWrite();
+ return list.remove(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection> c) {
+ return list.containsAll(c);
+ }
+
+ @Override
+ public boolean addAll(Collection extends E> c) {
+ checkCopyOnWrite();
+ return list.addAll(c);
+ }
+
+ @Override
+ public boolean addAll(int index, Collection extends E> c) {
+ checkCopyOnWrite();
+ return list.addAll(index, c);
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ checkCopyOnWrite();
+ return list.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ checkCopyOnWrite();
+ return list.retainAll(c);
+ }
+
+ @Override
+ public void replaceAll(UnaryOperator operator) {
+ checkCopyOnWrite();
+ list.replaceAll(operator);
+ }
+
+ @Override
+ public boolean removeIf(Predicate super E> filter) {
+ checkCopyOnWrite();
+ return list.removeIf(filter);
+ }
+
+ @Override
+ public void sort(Comparator super E> c) {
+ checkCopyOnWrite();
+ list.sort(c);
+ }
+
+ @Override
+ public void clear() {
+ if (!copied) {
+ list = new ArrayList<>();
+ copied = true;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return list.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return list.hashCode();
+ }
+
+ @Override
+ public E get(int index) {
+ return list.get(index);
+ }
+
+ @Override
+ public E set(int index, E element) {
+ checkCopyOnWrite();
+ return list.set(index, element);
+ }
+
+ @Override
+ public void add(int index, E element) {
+ checkCopyOnWrite();
+ list.add(index, element);
+ }
+
+ @Override
+ public E remove(int index) {
+ checkCopyOnWrite();
+ return list.remove(index);
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return list.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return list.lastIndexOf(o);
+ }
+
+ private void checkCopyOnWrite() {
+ if (!copied) {
+ synchronized (this) {
+ if (!copied) {
+ list = new ArrayList<>(list);
+ copied = true;
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/ebeaninternal/api/SpiQuery.java b/src/main/java/io/ebeaninternal/api/SpiQuery.java
index 63fe3228b..8a4022096 100644
--- a/src/main/java/io/ebeaninternal/api/SpiQuery.java
+++ b/src/main/java/io/ebeaninternal/api/SpiQuery.java
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
+import io.ebean.CacheMode;
import io.ebean.EbeanServer;
import io.ebean.ExpressionList;
import io.ebean.OrderBy;
@@ -583,9 +584,9 @@ public interface SpiQuery extends Query {
boolean isUseBeanCache();
/**
- * Return true if this query should use/check the query cache.
+ * Return the cache mode if this query should use/check the query cache.
*/
- boolean isUseQueryCache();
+ CacheMode getUseQueryCache();
/**
* Return true if the beans from this query should be loaded into the bean
diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
index 5bb58bb69..f5100ec3c 100644
--- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
@@ -35,6 +35,7 @@ import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.PersistenceContext.WithOption;
import io.ebean.cache.ServerCacheManager;
+import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.EncryptKeyManager;
import io.ebean.config.ServerConfig;
@@ -99,6 +100,7 @@ import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
+
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
@@ -1332,10 +1334,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return findIdsWithCopy(((SpiQuery) query).copy(), t);
}
+ @SuppressWarnings("unchecked")
@Override
public List findIdsWithCopy(Query query, Transaction t) {
SpiOrmQueryRequest> request = createQueryRequest(Type.ID_LIST, query, t);
+ Object result = request.getFromQueryCache();
+ if (result != null) {
+ if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) {
+ return new CopyOnFirstWriteList<>((List) result);
+ } else {
+ return (List ) result;
+ }
+ }
try {
request.initTransIfRequired();
return request.findIds();
diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
index d083c7fe1..78cbda57b 100644
--- a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
@@ -1,11 +1,13 @@
package io.ebeaninternal.server.core;
+import io.ebean.CacheMode;
import io.ebean.PersistenceContextScope;
import io.ebean.QueryIterator;
import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
+import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanQueryAdapter;
import io.ebean.event.BeanQueryRequest;
@@ -32,6 +34,8 @@ import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -471,12 +475,16 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
@SuppressWarnings("unchecked")
public Object getFromQueryCache() {
- if (!query.isUseQueryCache()) {
+ if (query.getUseQueryCache() == CacheMode.OFF) {
+ return null;
+ } else {
+ cacheKey = query.queryHash();
+ }
+
+ if (!query.getUseQueryCache().isGet()) {
return null;
}
- cacheKey = query.queryHash();
-
Object cached = beanDescriptor.queryCacheGet(cacheKey);
if (cached != null && isAuditReads() && readAuditQueryType()) {
@@ -491,6 +499,18 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
}
}
+ if (Boolean.FALSE.equals(query.isReadOnly())) {
+ // return shallow copies if readonly is explicitly set to false
+ if (cached instanceof BeanCollection) {
+ cached = ((BeanCollection>)cached).getShallowCopy();
+ } else if (cached instanceof List) {
+ cached = new CopyOnFirstWriteList<>((List)cached);
+ } else if (cached instanceof Set) {
+ cached = new LinkedHashSet<>((Set)cached);
+ } else if (cached instanceof Map) {
+ cached = new LinkedHashMap<>((Map)cached);
+ }
+ }
return cached;
}
diff --git a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java
index 72d789a95..915f874dc 100644
--- a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java
+++ b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java
@@ -463,7 +463,7 @@ public class DefaultExpressionList implements SpiExpressionList {
}
@Override
- public Query setUseQueryCache(boolean useCache) {
+ public Query setUseQueryCache(CacheMode useCache) {
return query.setUseQueryCache(useCache);
}
diff --git a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java
index 37d1e2510..7ba4a5086 100644
--- a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java
+++ b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java
@@ -742,7 +742,7 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression
}
@Override
- public Query setUseQueryCache(boolean useCache) {
+ public Query setUseQueryCache(CacheMode useCache) {
return exprList.setUseQueryCache(useCache);
}
diff --git a/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java b/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java
index 77e24aca5..01d81a454 100644
--- a/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java
+++ b/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java
@@ -13,7 +13,9 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
/**
@@ -71,7 +73,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
flushJdbcBatchOnQuery(request);
int result = queryEngine.findCount(request);
- if (request.getQuery().isUseQueryCache()) {
+ if (request.getQuery().getUseQueryCache().isPut()) {
request.putToQueryCache(result);
}
return result;
@@ -81,16 +83,28 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
public List findIds(OrmQueryRequest> request) {
flushJdbcBatchOnQuery(request);
- return queryEngine.findIds(request);
+ List result = queryEngine.findIds(request);
+ if (request.getQuery().getUseQueryCache().isPut()) {
+ result = Collections.unmodifiableList(result);
+ request.putToQueryCache(result);
+ if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) {
+ result = new ArrayList<>(result);
+ }
+ }
+ return result;
}
@Override
public List findSingleAttributeList(OrmQueryRequest> request) {
flushJdbcBatchOnQuery(request);
List result = queryEngine.findSingleAttributeList(request);
- if (!result.isEmpty() && request.getQuery().isUseQueryCache()) {
+ if (!result.isEmpty() && request.getQuery().getUseQueryCache().isPut()) {
// load the query result into the query cache
+ result = Collections.unmodifiableList(result);
request.putToQueryCache(result);
+ if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) {
+ result = new ArrayList<>(result);
+ }
}
return result;
}
@@ -137,9 +151,13 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
}
}
- if (!result.isEmpty() && query.isUseQueryCache()) {
+ if (!result.isEmpty() && query.getUseQueryCache().isPut()) {
// load the query result into the query cache
+ result.setReadOnly(true);
request.putToQueryCache(result);
+ if (Boolean.FALSE.equals(query.isReadOnly())) {
+ result = result.getShallowCopy();
+ }
}
return result;
diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
index f94e9261c..11cd893d0 100644
--- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.querydefn;
+import io.ebean.CacheMode;
import io.ebean.EbeanServer;
import io.ebean.Expression;
import io.ebean.ExpressionFactory;
@@ -195,7 +196,7 @@ public class DefaultOrmQuery implements SpiQuery {
private boolean excludeBeanCache;
- private Boolean useQueryCache;
+ private CacheMode useQueryCache = CacheMode.OFF;
private Boolean readOnly;
@@ -1094,9 +1095,13 @@ public class DefaultOrmQuery implements SpiQuery {
}
@Override
- public boolean isUseQueryCache() {
+ public CacheMode getUseQueryCache() {
// not using L2 cache for asDraft() query
- return !isAsDraft() && Boolean.TRUE.equals(useQueryCache);
+ if (isAsDraft()) {
+ return CacheMode.OFF;
+ } else {
+ return useQueryCache;
+ }
}
@Override
@@ -1106,7 +1111,7 @@ public class DefaultOrmQuery implements SpiQuery {
}
@Override
- public DefaultOrmQuery setUseQueryCache(boolean useQueryCache) {
+ public DefaultOrmQuery setUseQueryCache(CacheMode useQueryCache) {
this.useQueryCache = useQueryCache;
return this;
}
diff --git a/src/test/java/org/tests/cache/TestQueryCache.java b/src/test/java/org/tests/cache/TestQueryCache.java
index a947b8e45..d9f2dd5a8 100644
--- a/src/test/java/org/tests/cache/TestQueryCache.java
+++ b/src/test/java/org/tests/cache/TestQueryCache.java
@@ -1,6 +1,7 @@
package org.tests.cache;
import io.ebean.BaseTestCase;
+import io.ebean.CacheMode;
import io.ebean.Ebean;
import io.ebean.bean.BeanCollection;
import io.ebean.cache.ServerCache;
@@ -105,13 +106,13 @@ public class TestQueryCache extends BaseTestCase {
LoggedSqlCollector.start();
int count0 = Ebean.find(EColAB.class)
- .setUseQueryCache(true)
+ .setUseQueryCache(CacheMode.ON)
.where()
.eq("columnB", "count")
.findCount();
int count1 = Ebean.find(EColAB.class)
- .setUseQueryCache(true)
+ .setUseQueryCache(CacheMode.ON)
.where()
.eq("columnB", "count")
.findCount();
@@ -124,7 +125,7 @@ public class TestQueryCache extends BaseTestCase {
// and now, ensure that we hit the database
LoggedSqlCollector.start();
int count2 = Ebean.find(EColAB.class)
- .setUseQueryCache(false)
+ .setUseQueryCache(CacheMode.OFF)
.where()
.eq("columnB", "count")
.findCount();
@@ -140,13 +141,13 @@ public class TestQueryCache extends BaseTestCase {
LoggedSqlCollector.start();
int count0 = Ebean.find(EColAB.class)
- .setUseQueryCache(true)
+ .setUseQueryCache(CacheMode.ON)
.where()
.eq("columnB", "abc")
.findCount();
int count1 = Ebean.find(EColAB.class)
- .setUseQueryCache(true)
+ .setUseQueryCache(CacheMode.ON)
.where()
.eq("columnB", "def")
.findCount();
@@ -157,6 +158,58 @@ public class TestQueryCache extends BaseTestCase {
assertThat(sql).hasSize(2); // different queries
}
+
+ @Test
+ public void findCountFirstOnThenRecache() {
+
+
+ LoggedSqlCollector.start();
+
+ int count0 = Ebean.find(EColAB.class)
+ .setUseQueryCache(CacheMode.ON)
+ .where()
+ .eq("columnB", "uvw")
+ .findCount();
+
+ int count1 = Ebean.find(EColAB.class)
+ .setUseQueryCache(CacheMode.RECACHE)
+ .where()
+ .eq("columnB", "uvw")
+ .findCount();
+
+ List sql = LoggedSqlCollector.stop();
+
+ assertThat(count0).isEqualTo(count1);
+ assertThat(sql).hasSize(2); // try recache as second query - it must fetch it
+
+ }
+
+
+ @Test
+ public void findCountFirstRecacheThenOn() {
+
+
+ LoggedSqlCollector.start();
+
+ int count0 = Ebean.find(EColAB.class)
+ .setUseQueryCache(CacheMode.RECACHE)
+ .where()
+ .eq("columnB", "xyz")
+ .findCount();
+
+ int count1 = Ebean.find(EColAB.class)
+ .setUseQueryCache(CacheMode.ON)
+ .where()
+ .eq("columnB", "xyz")
+ .findCount();
+
+ List sql = LoggedSqlCollector.stop();
+
+ assertThat(count0).isEqualTo(count1);
+ assertThat(sql).hasSize(1); // try recache as first query - second "ON" query must fetch it.
+
+ }
+
@Test
@SuppressWarnings("unchecked")
public void test() {
@@ -170,7 +223,7 @@ public class TestQueryCache extends BaseTestCase {
.ilike("name", "Rob").findList();
BeanCollection bc = (BeanCollection) list;
- Assert.assertFalse(bc.isReadOnly());
+ Assert.assertTrue(bc.isReadOnly());
Assert.assertFalse(bc.isEmpty());
Assert.assertTrue(!list.isEmpty());
Assert.assertTrue(Ebean.getBeanState(list.get(0)).isReadOnly());
@@ -188,19 +241,60 @@ public class TestQueryCache extends BaseTestCase {
Assert.assertSame(list, list2B);
- // TODO: At this stage setReadOnly(false) does not
- // create a shallow copy of the List/Set/Map
+
-// List list3 = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where()
-// .ilike("name", "Rob").findList();
-//
-// Assert.assertNotSame(list, list3);
-// BeanCollection bc3 = (BeanCollection) list3;
-// Assert.assertFalse(bc3.isReadOnly());
-// Assert.assertFalse(bc3.isEmpty());
-// Assert.assertTrue(list3.size() > 0);
-// Assert.assertFalse(Ebean.getBeanState(list3.get(0)).isReadOnly());
+ List list3 = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where()
+ .ilike("name", "Rob").findList();
+
+ Assert.assertNotSame(list, list3);
+ BeanCollection bc3 = (BeanCollection) list3;
+ Assert.assertFalse(bc3.isReadOnly());
+ Assert.assertFalse(bc3.isEmpty());
+ Assert.assertTrue(list3.size() > 0);
+ // TODO: At this stage setReadOnly(false) does create a shallow copy of the List/Set/Map, but does not
+ // change the read only state in the entities.
+ // Assert.assertFalse(Ebean.getBeanState(list3.get(0)).isReadOnly());
}
+ @Test
+ public void findIds() {
+
+ new EColAB("03", "someId").save();
+ new EColAB("04", "someId").save();
+ new EColAB("05", "someId").save();
+
+
+ LoggedSqlCollector.start();
+
+ List colA_first = Ebean.find(EColAB.class)
+ .setUseQueryCache(CacheMode.ON)
+ .where()
+ .eq("columnB", "someId")
+ .findIds();
+
+ List colA_second = Ebean.find(EColAB.class)
+ .setUseQueryCache(CacheMode.ON)
+ .where()
+ .eq("columnB", "someId")
+ .findIds();
+
+ List sql = LoggedSqlCollector.stop();
+
+ assertThat(colA_first).isSameAs(colA_second);
+ assertThat(colA_first).hasSize(3);
+ assertThat(sql).hasSize(1);
+
+ // and now, ensure that we hit the database
+ LoggedSqlCollector.start();
+ colA_second = Ebean.find(EColAB.class)
+ .setUseQueryCache(CacheMode.RECACHE)
+ .where()
+ .eq("columnB", "someId")
+ .findIds();
+ sql = LoggedSqlCollector.stop();
+
+ assertThat(sql).hasSize(1);
+ }
+
}
diff --git a/src/test/java/org/tests/cache/TestQueryCacheReadOnly.java b/src/test/java/org/tests/cache/TestQueryCacheReadOnly.java
new file mode 100644
index 000000000..a8b1bb524
--- /dev/null
+++ b/src/test/java/org/tests/cache/TestQueryCacheReadOnly.java
@@ -0,0 +1,111 @@
+package org.tests.cache;
+
+import io.ebean.BaseTestCase;
+import io.ebean.CacheMode;
+import io.ebean.Ebean;
+import io.ebean.EbeanServer;
+import io.ebean.Query;
+
+import org.tests.model.basic.EBasicVer;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.*;
+
+public class TestQueryCacheReadOnly extends BaseTestCase {
+
+ @Test
+ public void testReadOnly() {
+
+ EbeanServer server = Ebean.getServer(null);
+ EBasicVer account = new EBasicVer("an other junk");
+ server.save(account);
+
+ Query baseQuery = server.find(EBasicVer.class).setUseQueryCache(CacheMode.ON);
+
+ List alist = baseQuery.findList();
+ assertThat(alist).isNotEmpty();
+ assertThatThrownBy(alist::clear).hasMessageContaining("This collection is in ReadOnly mode");
+ alist = baseQuery.findList();
+ assertThat(alist).isNotEmpty();
+ assertThatThrownBy(alist::clear).hasMessageContaining("This collection is in ReadOnly mode");
+
+ Map amap = baseQuery.setMapKey("name").findMap();
+ assertThat(amap).isNotEmpty();
+ assertThatThrownBy(amap::clear).hasMessageContaining("This collection is in ReadOnly mode");
+ amap = baseQuery.setMapKey("name").findMap();
+ assertThat(amap).isNotEmpty();
+ assertThatThrownBy(amap::clear).hasMessageContaining("This collection is in ReadOnly mode");
+
+ Set aset = baseQuery.findSet();
+ assertThat(aset).isNotEmpty();
+ assertThatThrownBy(aset::clear).hasMessageContaining("This collection is in ReadOnly mode");
+ aset = baseQuery.findSet();
+ assertThat(aset).isNotEmpty();
+ assertThatThrownBy(aset::clear).hasMessageContaining("This collection is in ReadOnly mode");
+
+ // we will get an unmodifiable collection here
+ List attributeList = baseQuery.select("name").findSingleAttributeList();
+ assertThat(attributeList).isNotEmpty();
+ assertThatThrownBy(attributeList::clear).isInstanceOf(UnsupportedOperationException.class);
+ attributeList = baseQuery.select("name").findSingleAttributeList();
+ assertThat(attributeList).isNotEmpty();
+ assertThatThrownBy(attributeList::clear).isInstanceOf(UnsupportedOperationException.class);
+
+ List idList = baseQuery.select("name").findIds();
+ assertThat(idList).isNotEmpty();
+ assertThatThrownBy(idList::clear).isInstanceOf(UnsupportedOperationException.class);
+ idList = baseQuery.select("name").findIds();
+ assertThat(idList).isNotEmpty();
+ assertThatThrownBy(idList::clear).isInstanceOf(UnsupportedOperationException.class);
+ }
+
+
+ @Test
+ public void testNotReadOnly() {
+
+ EbeanServer server = Ebean.getServer(null);
+ EBasicVer account = new EBasicVer("an other junk");
+ server.save(account);
+
+ Query baseQuery = server.find(EBasicVer.class).setUseQueryCache(CacheMode.ON).setReadOnly(false);
+
+ List alist = baseQuery.findList();
+ assertThat(alist).isNotEmpty();
+ alist.clear();
+ alist = baseQuery.findList();
+ assertThat(alist).isNotEmpty();
+ alist.clear();
+
+ Map amap = baseQuery.setMapKey("name").findMap();
+ assertThat(amap).isNotEmpty();
+ amap.clear();
+ amap = baseQuery.setMapKey("name").findMap();
+ assertThat(amap).isNotEmpty();
+ amap.clear();
+
+ Set aset = baseQuery.findSet();
+ assertThat(aset).isNotEmpty();
+ aset.clear();
+ aset = baseQuery.findSet();
+ assertThat(aset).isNotEmpty();
+ aset.clear();
+
+ List attributeList = baseQuery.select("name").findSingleAttributeList();
+ assertThat(attributeList).isNotEmpty();
+ attributeList.clear();
+ attributeList = baseQuery.select("name").findSingleAttributeList();
+ assertThat(attributeList).isNotEmpty();
+ attributeList.clear();
+
+ List idList = baseQuery.select("name").findIds();
+ assertThat(idList).isNotEmpty();
+ idList.clear();
+ idList = baseQuery.select("name").findIds();
+ assertThat(idList).isNotEmpty();
+ idList.clear();
+ }
+}