Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optimized cache handling (QueryCache for findId, use QueryCache befor…
Browse files Browse the repository at this point in the history
…e BeanCache)
rPraml committed Oct 7, 2024
1 parent 932c8a9 commit 791e85a
Showing 12 changed files with 349 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -969,40 +969,6 @@ private <T> SpiOrmQueryRequest<T> buildQueryRequest(SpiQuery<T> query) {
return new OrmQueryRequest<>(this, queryEngine, query, transaction);
}

/**
* Try to get the object out of the persistence context.
*/
@Nullable
@SuppressWarnings("unchecked")
private <T> T findIdCheckPersistenceContextAndCache(SpiQuery<T> query, Object id) {
SpiTransaction t = query.transaction();
if (t == null) {
t = currentServerTransaction();
}
BeanDescriptor<T> desc = query.descriptor();
id = desc.convertId(id);
PersistenceContext pc = null;
if (t != null && useTransactionPersistenceContext(query)) {
// first look in the transaction scoped persistence context
pc = t.persistenceContext();
if (pc != null) {
WithOption o = desc.contextGetWithOption(pc, id);
if (o != null) {
if (o.isDeleted()) {
// Bean was previously deleted in the same transaction / persistence context
return null;
}
return (T) o.getBean();
}
}
}
if (!query.isBeanCacheGet() || (t != null && t.isSkipCache())) {
return null;
}
// Hit the L2 bean cache
return desc.cacheBeanGet(id, query.isReadOnly(), pc);
}

/**
* Return true if transactions PersistenceContext should be used.
*/
@@ -1023,15 +989,59 @@ public PersistenceContextScope persistenceContextScope(SpiQuery<?> query) {
@SuppressWarnings("unchecked")
private <T> T findId(SpiQuery<T> query) {
query.setType(Type.BEAN);
SpiOrmQueryRequest<T> request = null;
if (SpiQuery.Mode.NORMAL == query.mode() && !query.isForceHitDatabase()) {
// See if we can skip doing the fetch completely by getting the bean from the
// persistence context or the bean cache
T bean = findIdCheckPersistenceContextAndCache(query, query.getId());
if (bean != null) {
return bean;
SpiTransaction t = (SpiTransaction) query.transaction();
if (t == null) {
t = currentServerTransaction();
}
BeanDescriptor<T> desc = query.descriptor();
Object id = desc.convertId(query.getId());
PersistenceContext pc = null;
if (t != null && useTransactionPersistenceContext(query)) {
// first look in the transaction scoped persistence context
pc = t.persistenceContext();
if (pc != null) {
WithOption o = desc.contextGetWithOption(pc, id);
if (o != null) {
// We have found a hit. This could be also one with o.deleted() == true
// if bean was previously deleted in the same transaction / persistence context
return (T) o.getBean();
}
}
}
if (t == null || !t.isSkipCache()) {
if (query.queryCacheMode() != CacheMode.OFF) {
request = buildQueryRequest(query);
if (request.isQueryCacheActive()) {
// Hit the query cache
request.prepareQuery();
T bean = request.getFromQueryCache();
if (bean != null) {
return bean;
}
}
}
if (query.isBeanCacheGet()) {
// Hit the L2 bean cache
T bean = desc.cacheBeanGet(id, query.isReadOnly(), pc);
if (bean != null) {
if (request != null && request.isQueryCachePut()) {
// copy bean from the L2 cache to the faster query cache, if caching is enabled
request.prepareQuery();
request.putToQueryCache(bean);
}
return bean;
}
}
}
}

if (request == null) {
request = buildQueryRequest(query);
}
SpiOrmQueryRequest<T> request = buildQueryRequest(query);
request.prepareQuery();
if (request.isUseDocStore()) {
return docStore().find(request);
@@ -1077,15 +1087,18 @@ private <T> T extractUnique(List<T> list) {
public <T> Set<T> findSet(SpiQuery<T> query) {
SpiOrmQueryRequest request = buildQueryRequest(Type.SET, query);
request.resetBeanCacheAutoMode(false);
if (request.isQueryCacheActive()) {
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (Set<T>) result;
}
}
if (request.isGetAllFromBeanCache()) {
// hit bean cache and got all results from cache
return request.beanCacheHitsAsSet();
}
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (Set<T>) result;
}
try {
request.initTransIfRequired();
return request.findSet();
@@ -1098,16 +1111,19 @@ public <T> Set<T> findSet(SpiQuery<T> query) {
@SuppressWarnings({"unchecked", "rawtypes"})
public <K, T> Map<K, T> findMap(SpiQuery<T> query) {
SpiOrmQueryRequest request = buildQueryRequest(Type.MAP, query);
if (request.isQueryCacheActive()) {
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (Map<K, T>) result;
}
}
request.resetBeanCacheAutoMode(false);
if (request.isGetAllFromBeanCache()) {
// hit bean cache and got all results from cache
return request.beanCacheHitsAsMap();
}
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (Map<K, T>) result;
}
try {
request.initTransIfRequired();
return request.findMap();
@@ -1413,15 +1429,18 @@ public <T> List<T> findList(SpiQuery<T> query) {
private <T> List<T> findList(SpiQuery<T> query, boolean findOne) {
SpiOrmQueryRequest<T> request = buildQueryRequest(Type.LIST, query);
request.resetBeanCacheAutoMode(findOne);
if (request.isQueryCacheActive()) {
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (List<T>) result;
}
}
if (request.isGetAllFromBeanCache()) {
// hit bean cache and got all results from cache
return request.beanCacheHits();
}
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (List<T>) result;
}
if (request.isUseDocStore()) {
return docStore().findList(request);
}
Original file line number Diff line number Diff line change
@@ -39,10 +39,12 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private PersistenceContext persistenceContext;
private HashQuery cacheKey;
private CQueryPlanKey queryPlanKey;
// The queryPlan during the request.
private CQueryPlan queryPlan;
private SpiQuerySecondary secondaryQueries;
private List<T> cacheBeans;
private boolean inlineCountDistinct;
private Set<String> dependentTables;
private boolean prepared;
private SpiQueryManyJoin manyJoin;

public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery<T> query, SpiTransaction t) {
@@ -71,6 +73,7 @@ public boolean isDeleteByStatement() {
} else {
// delete by ids due to cascading delete needs
queryPlanKey = query.setDeleteByIdsPlan();
queryPlan = null;
return false;
}
}
@@ -168,11 +171,24 @@ private void adapterPreQuery() {
*/
@Override
public void prepareQuery() {
manyJoin = query.convertJoins();
secondaryQueries = query.secondaryQuery();
beanDescriptor.prepareQuery(query);
adapterPreQuery();
queryPlanKey = query.prepare(this);
if (!prepared) {
secondaryQueries = query.secondaryQuery();
beanDescriptor.prepareQuery(query);
adapterPreQuery();
queryPlanKey = query.prepare(this);
prepared = true;
}

}

/**
* The queryPlanKey has to be updated, if elements are removed from an already prepared query.
*/
private void updateQueryPlanKey() {
if (prepared) {
queryPlanKey = query.prepare(this);
queryPlan = null;
}
}

public boolean isNativeSql() {
@@ -467,7 +483,10 @@ public boolean includeManyJoin() {
* query plan for this query exists.
*/
public CQueryPlan queryPlan() {
return beanDescriptor.queryPlan(queryPlanKey);
if (queryPlan == null) {
queryPlan = beanDescriptor.queryPlan(queryPlanKey);
}
return queryPlan;
}

/**
@@ -485,6 +504,7 @@ public CQueryPlanKey queryPlanKey() {
* Put the QueryPlan into the cache.
*/
public void putQueryPlan(CQueryPlan queryPlan) {
this.queryPlan = queryPlan;
beanDescriptor.queryPlan(queryPlanKey, queryPlan);
}

@@ -493,8 +513,16 @@ public void resetBeanCacheAutoMode(boolean findOne) {
query.resetBeanCacheAutoMode(findOne);
}

@Override
public boolean isQueryCacheActive() {
return query.queryCacheMode() != CacheMode.OFF
&& (transaction == null || !transaction.isSkipCache())
&& !server.isDisableL2Cache();
}

@Override
public boolean isQueryCachePut() {
return cacheKey != null && query.queryCacheMode().isPut();
return cacheKey != null && queryPlan != null && query.queryCacheMode().isPut();
}

public boolean isBeanCachePutMany() {
@@ -603,7 +631,14 @@ public boolean getFromBeanCache() {
BeanCacheResult<T> cacheResult = beanDescriptor.cacheIdLookup(persistenceContext, idLookup.idValues());
// adjust the query (IN clause) based on the cache hits
this.cacheBeans = idLookup.removeHits(cacheResult);
return idLookup.allHits();
if (idLookup.allHits()) {
return true;
} else {
if (!this.cacheBeans.isEmpty()) {
updateQueryPlanKey();
}
return false;
}
}
if (!beanDescriptor.isNaturalKeyCaching()) {
return false;
@@ -616,7 +651,14 @@ public boolean getFromBeanCache() {
BeanCacheResult<T> cacheResult = beanDescriptor.naturalKeyLookup(persistenceContext, naturalKeySet.keys());
// adjust the query (IN clause) based on the cache hits
this.cacheBeans = data.removeHits(cacheResult);
return data.allHits();
if (data.allHits()) {
return true;
} else {
if (!this.cacheBeans.isEmpty()) {
updateQueryPlanKey();
}
return false;
}
}
}
return false;
@@ -628,9 +670,7 @@ public boolean getFromBeanCache() {
@Override
@SuppressWarnings("unchecked")
public Object getFromQueryCache() {
if (query.queryCacheMode() == CacheMode.OFF
|| (transaction != null && transaction.isSkipCache())
|| server.isDisableL2Cache()) {
if (!isQueryCacheActive()) {
return null;
} else {
cacheKey = query.queryHash();
@@ -684,8 +724,9 @@ private boolean readAuditQueryType() {
}
}

@Override
public void putToQueryCache(Object result) {
beanDescriptor.queryCachePut(cacheKey, new QueryCacheEntry(result, dependentTables, transaction.startNanoTime()));
beanDescriptor.queryCachePut(cacheKey, new QueryCacheEntry(result, queryPlan.dependentTables(), transaction.startNanoTime()));
}

/**
@@ -755,15 +796,6 @@ public boolean isInlineCountDistinct() {
return inlineCountDistinct;
}

public void addDependentTables(Set<String> tables) {
if (tables != null && !tables.isEmpty()) {
if (dependentTables == null) {
dependentTables = new LinkedHashSet<>();
}
dependentTables.addAll(tables);
}
}

/**
* Return true if no MaxRows or use LIMIT in SQL update.
*/
Original file line number Diff line number Diff line change
@@ -135,6 +135,21 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequ
*/
<A> A getFromQueryCache();

/**
* Return if query cache is active.
*/
boolean isQueryCacheActive();

/**
* Return if results should be put to query cache.
*/
boolean isQueryCachePut();

/**
* Put the result to the query cache.
*/
void putToQueryCache(Object result);

/**
* Maybe hit the bean cache returning true if everything was obtained from the
* cache (that there were no misses).
Original file line number Diff line number Diff line change
@@ -754,7 +754,4 @@ public void handleLoadError(String fullName, Exception e) {
query.handleLoadError(fullName, e);
}

public Set<String> dependentTables() {
return queryPlan.dependentTables();
}
}
Original file line number Diff line number Diff line change
@@ -103,7 +103,6 @@ private <A extends Collection<?>> A findAttributeCollection(OrmQueryRequest<?> r
request.transaction().logSummary(rcQuery.summary());
}
if (request.isQueryCachePut()) {
request.addDependentTables(rcQuery.dependentTables());
if (collection instanceof List) {
collection = (A) Collections.unmodifiableList((List<?>) collection);
request.putToQueryCache(collection);
@@ -167,7 +166,6 @@ public <T> int findCount(OrmQueryRequest<T> request) {
request.transaction().logSummary(rcQuery.summary());
}
if (request.isQueryCachePut()) {
request.addDependentTables(rcQuery.dependentTables());
request.putToQueryCache(count);
}
return count;
@@ -355,9 +353,6 @@ <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
cquery.auditFindMany();
}
request.executeSecondaryQueries(false);
if (request.isQueryCachePut()) {
request.addDependentTables(cquery.dependentTables());
}
return beanCollection;

} catch (SQLException e) {
Original file line number Diff line number Diff line change
@@ -168,10 +168,6 @@ public void profile() {
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), rowCount, query.profileId());
}

Set<String> dependentTables() {
return queryPlan.dependentTables();
}

@Override
public void cancel() {
lock.lock();
Original file line number Diff line number Diff line change
@@ -140,10 +140,6 @@ public void profile() {
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), rowCount, query.profileId());
}

Set<String> dependentTables() {
return queryPlan.dependentTables();
}

@Override
public void cancel() {
lock.lock();
Original file line number Diff line number Diff line change
@@ -164,8 +164,13 @@ public <T> T findId(OrmQueryRequest<T> request) {
if (finder != null) {
result = finder.postProcess(request, result);
}
if (result != null && request.isBeanCachePut()) {
request.descriptor().cacheBeanPut((EntityBean) result);
if (result != null) {
if (request.isBeanCachePut()) {
request.descriptor().cacheBeanPut((EntityBean) result);
}
if (request.isQueryCachePut()) {
request.putToQueryCache(result);
}
}
return result;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.tests.basic;

import io.ebean.DB;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.base.TransactionalTestCase;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
@@ -10,6 +11,7 @@
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;

@@ -41,8 +43,10 @@ public void test() {
DB.deleteAll(Customer.class, ids);
awaitL2Cache();

LoggedSql.start();
c0Back = DB.find(Customer.class, c0.getId());
c1Back = DB.find(Customer.class, "" + c1.getId());
assertThat(LoggedSql.stop()).isEmpty();

assertNull(c0Back);
assertNull(c1Back);
113 changes: 110 additions & 3 deletions ebean-test/src/test/java/org/tests/cache/TestBeanCache.java
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@@ -67,7 +68,7 @@ public void idsInFindMap() {

// Test findIds
LoggedSql.start();
query.copy()
Map<Object, OCachedBean> map1 = query.copy()
.where().idIn(ids.subList(0, 1))
.findMap(); // cache key is: 3/d[{/c1000}]/w[List[IdIn[?1],]]
if (isPostgresCompatible()) {
@@ -77,7 +78,7 @@ public void idsInFindMap() {
}

LoggedSql.start();
query.copy()
Map<Object, OCachedBean> map2 = query.copy()
.where().idIn(ids.subList(0, 4))
.findMap(); // cache key is: 3/d[{/c1000}]/w[List[IdIn[?5],]]
if (isPostgresCompatible()) {
@@ -87,14 +88,120 @@ public void idsInFindMap() {
}

LoggedSql.start();
query.copy()
Map<Object, OCachedBean> map3 = query.copy()
.where().idIn(ids.subList(2, 6))
.findMap(); // same cache key as above and same SQL above
if (isPostgresCompatible()) {
assertThat(LoggedSql.stop().get(0)).contains("t0.id = any(?)");
} else {
assertThat(LoggedSql.stop().get(0)).contains("in (?,?,?,?,?)");
}

ServerCache bc = DB.getDefault().pluginApi().cacheManager().beanCache(OCachedBean.class);
bc.statistics(true);

ServerCache qc = DB.getDefault().pluginApi().cacheManager().queryCache(OCachedBean.class);
qc.statistics(true);

LoggedSql.start();
assertThat(query.copy()
.where().idIn(ids.subList(0, 1))
.findMap()).isEqualTo(map1).isNotSameAs(map1);


assertThat(query.copy()
.where().idIn(ids.subList(0, 4))
.findMap()).isEqualTo(map2).isNotSameAs(map2);

assertThat(query.copy()
.where().idIn(ids.subList(2, 6))
.findMap()).isEqualTo(map3).isNotSameAs(map3);

assertThat(LoggedSql.stop()).isEmpty(); // we should have no DB-hits

// we should have all beans in the bean cache, but no hits in the query cache
assertThat(bc.statistics(true).getHitCount()).isEqualTo(map1.size() + map2.size() + map3.size());
assertThat(qc.statistics(true).getHitCount()).isEqualTo(0);

// check with a different set, that should be in the cache
query.copy().where().idIn(ids.subList(2, 5)).findMap();
assertThat(bc.statistics(true).getHitCount()).isEqualTo(3);
assertThat(qc.statistics(true).getHitCount()).isEqualTo(0);

}


@Test
public void idsInFindMapWithBeanAndQueryCache() {

List<OCachedBean> beans = createBeans(Arrays.asList("m0", "m1", "m2", "m3", "m4", "m5", "m6"));
List<Long> ids = beans.stream().map(OCachedBean::getId).collect(Collectors.toList());
beanCache.clear();
beanCache.statistics(true);
Query<OCachedBean> query = DB.find(OCachedBean.class)
.setUseCache(true)
.setUseQueryCache(true)
.setReadOnly(true);

// Test findIds
LoggedSql.start();
Map<Object, OCachedBean> map1 = query.copy()
.where().idIn(ids.subList(0, 1))
.findMap(); // cache key is: 3/d[{/c1000}]/w[List[IdIn[?1],]]
if (isPostgresCompatible()) {
assertThat(LoggedSql.stop().get(0)).contains("t0.id = any(?)");
} else {
assertThat(LoggedSql.stop().get(0)).contains("in (?)");
}

LoggedSql.start();
Map<Object, OCachedBean> map2 = query.copy()
.where().idIn(ids.subList(0, 4))
.findMap(); // cache key is: 3/d[{/c1000}]/w[List[IdIn[?5],]]
if (isPostgresCompatible()) {
assertThat(LoggedSql.stop().get(0)).contains("t0.id = any(?)");
} else {
assertThat(LoggedSql.stop().get(0)).contains("in (?,?,?,?,?)");
}

LoggedSql.start();
Map<Object, OCachedBean> map3 = query.copy()
.where().idIn(ids.subList(2, 6))
.findMap(); // same cache key as above and same SQL above
if (isPostgresCompatible()) {
assertThat(LoggedSql.stop().get(0)).contains("t0.id = any(?)");
} else {
assertThat(LoggedSql.stop().get(0)).contains("in (?,?,?,?,?)");
}

ServerCache bc = DB.getDefault().pluginApi().cacheManager().beanCache(OCachedBean.class);
bc.statistics(true);

ServerCache qc = DB.getDefault().pluginApi().cacheManager().queryCache(OCachedBean.class);
qc.statistics(true);

LoggedSql.start();
assertThat(query.copy()
.where().idIn(ids.subList(0, 1))
.findMap()).isEqualTo(map1).isSameAs(map1);

assertThat(query.copy()
.where().idIn(ids.subList(0, 4))
.findMap()).isEqualTo(map2).isSameAs(map2);

assertThat(query.copy()
.where().idIn(ids.subList(2, 6))
.findMap()).isEqualTo(map3).isSameAs(map3);

assertThat(LoggedSql.stop()).isEmpty(); // we should have no DB-hits

assertThat(bc.statistics(true).getHitCount()).isEqualTo(0);
assertThat(qc.statistics(true).getHitCount()).isEqualTo(3);

// check with a different set, that should be in the cache
query.copy().where().idIn(ids.subList(2, 5)).findMap();
assertThat(bc.statistics(true).getHitCount()).isEqualTo(3);
assertThat(qc.statistics(true).getHitCount()).isEqualTo(0);
}

@Test
88 changes: 88 additions & 0 deletions ebean-test/src/test/java/org/tests/cache/TestQueryCache.java
Original file line number Diff line number Diff line change
@@ -3,12 +3,14 @@
import io.ebean.CacheMode;
import io.ebean.DB;
import io.ebean.ExpressionList;
import io.ebean.Query;
import io.ebean.annotation.Transactional;
import io.ebean.annotation.TxIsolation;
import io.ebean.bean.BeanCollection;
import io.ebean.cache.ServerCache;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
@@ -302,6 +304,92 @@ public void findCountFirstRecacheThenOn() {

}

/**
* This test primarily checks, if the query cache on findById will work properly.
*
* It also checks some special cases, if query + bean cache are combined with other queries.
* It is important to know, that a "findId" query, that is not yet compiled will use the bean cache.
*/
@Test
@SuppressWarnings("unchecked")
public void testFindByIdWihtBothCaches() {

ResetBasicData.reset();

List<Object> ids = DB.find(Customer.class).setMaxRows(5).findIds();
Customer c = DB.find(Customer.class).setMaxRows(1).findOne();

DB.getDefault().pluginApi().cacheManager().clearAll();

ServerCache bc = DB.getDefault().pluginApi().cacheManager().beanCache(Customer.class);
ServerCache qc = DB.getDefault().pluginApi().cacheManager().queryCache(Customer.class);
bc.statistics(true);
qc.statistics(true);

// 1. load the bean cache with some beans
DB.find(Customer.class).where().idIn(ids).findList();

Query<Customer> q = DB.find(Customer.class).setUseQueryCache(true).setUseCache(true).setReadOnly(true);
LoggedSql.start();
Customer c1 = q.copy().where().eq("name", c.getName()).findOne();
Customer c2 = q.copy().where().eq("name", c.getName()).findOne();
assertThat(LoggedSql.stop()).hasSize(1);

assertTrue(DB.beanState(c1).isReadOnly());
assertTrue(DB.beanState(c2).isReadOnly());
assertThat(c1).isSameAs(c2);
assertThat(bc.statistics(true).getHitCount()).isEqualTo(0);
assertThat(qc.statistics(true).getHitCount()).isEqualTo(1);


LoggedSql.start();
c1 = q.copy().where().eq("id", c.getId()).findOne();
c2 = q.copy().where().eq("id", c.getId()).findOne();
assertThat(LoggedSql.stop()).isEmpty();
assertThat(c1).isNotSameAs(c2);

LoggedSql.start();
c1 = q.copy().setId(c.getId()).findOne();
c2 = q.copy().setId(c.getId()).findOne();
assertThat(LoggedSql.stop()).isEmpty();
assertThat(c1).isNotSameAs(c2);

LoggedSql.start();
List<Customer> l1 = q.copy().where().idIn(ids.subList(0,2)).findList();
List<Customer> l2 = q.copy().where().idIn(ids.subList(0,2)).findList();
assertThat(LoggedSql.stop()).isEmpty();
assertThat(l1).hasSize(2).isNotSameAs(l2);

assertThat(bc.statistics(true).getHitCount()).isEqualTo(8); // 4x findOne and 2x findList with 2 elements
assertThat(qc.statistics(true).getHitCount()).isEqualTo(0);
// Note: The ID queries are immediately handled by the BeanCache, because the underlying queries are never compiled
// and so they have no query plan, which is required for cache access.
//
// So we clear the cache and try it again
DB.getDefault().pluginApi().cacheManager().clearAll();

LoggedSql.start();
c1 = q.copy().where().eq("id", c.getId()).findOne();
c2 = q.copy().where().eq("id", c.getId()).findOne();
assertThat(LoggedSql.stop()).hasSize(1);
assertThat(c1).isSameAs(c2);

LoggedSql.start();
c1 = q.copy().setId(c.getId()).findOne();
c2 = q.copy().setId(c.getId()).findOne();
assertThat(LoggedSql.stop()).isEmpty(); // setId(..) query has same queryPlan as eq("id", ..)
assertThat(c1).isSameAs(c2);

LoggedSql.start();
l1 = q.copy().where().idIn(1, 2).findList();
l2 = q.copy().where().idIn(1, 2).findList();
assertThat(LoggedSql.stop()).hasSize(1); // we have to hit DB for bean#2
assertThat(l1).hasSize(2).isSameAs(l2);

assertThat(bc.statistics(true).getHitCount()).isEqualTo(1); // findList can get one bean from bc
assertThat(qc.statistics(true).getHitCount()).isEqualTo(4); // 6 queries, 2 of them hit db
}

@Test
@SuppressWarnings("unchecked")
public void testReadOnlyFind() {
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
/**
* Cached bean for testing caching implementation.
*/
@Cache
@Cache(enableQueryCache = true)
@Entity
@Table(name = "o_cached_bean")
public class OCachedBean {

0 comments on commit 791e85a

Please sign in to comment.