#1211 - ENH: Support L2 cache complex natural keys include findList() queries

This commit is contained in:
Rob Bygrave
2017-11-18 22:20:42 +13:00
parent c5a11aa5a8
commit d5555ed9a9
44 changed files with 1427 additions and 152 deletions
@@ -85,7 +85,7 @@ public class TDSpiExpressionRequest implements SpiExpressionRequest {
}
@Override
public void appendInExpression(boolean not, Object[] bindValues) {
public void appendInExpression(boolean not, List<Object> bindValues) {
}
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.querydefn;
import io.ebean.BaseTestCase;
import io.ebean.CacheMode;
import io.ebean.Ebean;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.OrmQueryRequest;
@@ -19,7 +20,7 @@ public class DefaultOrmQueryTest extends BaseTestCase {
DefaultOrmQuery<Customer> q1 = (DefaultOrmQuery<Customer>) Ebean.find(Customer.class)
.setForUpdate(true).where().eq("id", 42).query();
assertThat(q1.isExcludeBeanCache()).isTrue();
assertThat(q1.getUseBeanCache()).isSameAs(CacheMode.OFF);
}
@Test
@@ -3,7 +3,6 @@ package org.tests.batchload;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheManager;
import io.ebean.cache.ServerCacheStatistics;
import org.tests.model.basic.UUOne;
import org.ebeantest.LoggedSqlCollector;
@@ -18,6 +17,8 @@ import static org.junit.Assert.assertNotNull;
public class TestBatchLazyWithCacheHits extends BaseTestCase {
private ServerCache beanCache = server().getServerCacheManager().getBeanCache(UUOne.class);
private UUOne insert(String name) {
UUOne one = new UUOne();
one.setName("testBLWCH" + name);
@@ -28,24 +29,17 @@ public class TestBatchLazyWithCacheHits extends BaseTestCase {
@Test
public void testOnCacheHit() {
ArrayList<UUOne> inserted = new ArrayList<>();
String[] names = "A,B,C,D,E,F,G,H,I,J".split(",");
for (String name : names) {
inserted.add(insert(name));
}
ArrayList<UUOne> inserted = insertData();
ServerCacheManager serverCacheManager = Ebean.getDefaultServer().getServerCacheManager();
ServerCache beanCache = serverCacheManager.getBeanCache(UUOne.class);
beanCache.clear();
beanCache.getStatistics(true); // Reset statistics - otherwise other tests will interfere
clearCacheAndStatistics();
UUOne b = Ebean.find(UUOne.class, inserted.get(1).getId());
assertNotNull(b);
UUOne b2 = Ebean.find(UUOne.class, inserted.get(1).getId());
assertNotNull(b2);
ServerCacheStatistics statistics = beanCache.getStatistics(true);
assertEquals(statistics.getHitCount(), 1);
assertBeanCacheHits(1);
UUOne c = Ebean.find(UUOne.class)
.where().idEq(inserted.get(2).getId())
@@ -55,9 +49,9 @@ public class TestBatchLazyWithCacheHits extends BaseTestCase {
UUOne c2 = Ebean.find(UUOne.class)
.where().idEq(inserted.get(2).getId())
.findOne();
assertNotNull(c2);
statistics = beanCache.getStatistics(true);
assertEquals(statistics.getHitCount(), 1);
assertBeanCacheHits(1);
LoggedSqlCollector.start();
@@ -76,13 +70,33 @@ public class TestBatchLazyWithCacheHits extends BaseTestCase {
List<String> sql = LoggedSqlCollector.stop();
System.out.println("sql:" + sql);
// batch lazy loading into cache
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("from uuone t0 where t0.name like ");
platformAssertIn(sql.get(1), "from uuone t0 where t0.id");
statistics = beanCache.getStatistics(true);
assertThat(statistics.getSize()).isGreaterThan(3);
// not lazy loading into bean cache
int size = beanCache.getStatistics(true).getSize();
assertThat(size).isEqualTo(2);
}
private void assertBeanCacheHits(int hits) {
ServerCacheStatistics statistics = beanCache.getStatistics(true);
assertEquals(statistics.getHitCount(), hits);
}
private void clearCacheAndStatistics() {
beanCache.clear();
beanCache.getStatistics(true);
}
private ArrayList<UUOne> insertData() {
ArrayList<UUOne> inserted = new ArrayList<>();
String[] names = "A,B,C,D,E,F,G,H,I,J".split(",");
for (String name : names) {
inserted.add(insert(name));
}
return inserted;
}
}
@@ -0,0 +1,62 @@
package org.tests.model.basic.cache;
import io.ebean.annotation.Cache;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* Cached bean for testing caching implementation.
*/
@Cache(naturalKey = {"store","sku"})
@Entity
@Table(name = "o_cached_natkey")
public class OCachedNatKeyBean {
@Id
Long id;
String store;
String sku;
String description;
OCachedNatKeyBean(String store, String sku) {
this.store = store;
this.sku = sku;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getStore() {
return store;
}
public void setStore(String store) {
this.store = store;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
@@ -0,0 +1,65 @@
package org.tests.model.basic.cache;
import io.ebean.annotation.Cache;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* Cached bean for compound natural key.
*/
@Cache(naturalKey = {"store","code","sku"})
@Entity
@Table(name = "o_cached_natkey3")
public class OCachedNatKeyBean3 {
@Id
Long id;
String store;
int code;
String sku;
String description;
OCachedNatKeyBean3(String store, int code, String sku) {
this.store = store;
this.code = code;
this.sku = sku;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getStore() {
return store;
}
public void setStore(String store) {
this.store = store;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
@@ -0,0 +1,238 @@
package org.tests.model.basic.cache;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheManager;
import io.ebean.cache.ServerCacheStatistics;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestCacheViaComplexNaturalKey extends BaseTestCase {
private ServerCache beanCache = cacheManager().getBeanCache(OCachedNatKeyBean.class);
private ServerCache natKeyCache = cacheManager().getNaturalKeyCache(OCachedNatKeyBean.class);
private static boolean loadOnce;
private ServerCacheManager cacheManager() {
return server().getServerCacheManager();
}
private static synchronized void insertSome() {
if (!loadOnce) {
Ebean.find(OCachedNatKeyBean.class).delete();
List<String> stores = new ArrayList<>(Arrays.asList("abc", "def"));
for (String store : stores) {
List<String> skus = new ArrayList<>(Arrays.asList("1", "2", "3", "4"));
for (String sku : skus) {
saveBean(store, sku);
}
}
loadOnce = true;
}
}
private static void saveBean(String abc, String sku) {
Ebean.save(new OCachedNatKeyBean(abc, sku));
}
private void clearCacheAndStatistics() {
Ebean.getServerCacheManager().clearAll();
clearStatistics();
}
private void clearStatistics() {
beanCache.getStatistics(true);
natKeyCache.getStatistics(true);
}
private void assertNaturalKeyHitMiss(int expectedHit, int expectedMiss) {
ServerCacheStatistics stats = natKeyCache.getStatistics(true);
assertHitMiss(expectedHit, expectedMiss, stats);
}
private void assertBeanCacheHitMiss(int expectedHit, int expectedMiss) {
ServerCacheStatistics stats = beanCache.getStatistics(true);
assertHitMiss(expectedHit, expectedMiss, stats);
}
private void assertHitMiss(int expectedHit, int expectedMiss, ServerCacheStatistics stats) {
assertThat(stats.getHitCount()).isEqualTo(expectedHit);
assertThat(stats.getMissCount()).isEqualTo(expectedMiss);
}
private void setup() {
insertSome();
clearCacheAndStatistics();
}
private void loadSomeIntoCache() {
Ebean.find(OCachedNatKeyBean.class)
.setLoadBeanCache(true)
.where().le("sku", "2")
.findList();
clearStatistics();
}
@Test
public void findList_inClause_someHits() {
setup();
loadSomeIntoCache();
String storeId = "abc";
List<String> skus = new ArrayList<>(Arrays.asList("1", "2", "3"));
LoggedSqlCollector.start();
List<OCachedNatKeyBean> list = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", storeId)
.in("sku", skus)
.setUseCache(true)
.orderBy("sku desc")
.findList();
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
if (isH2()) {
// in clause with only 1 bind param - (sku=3 ... we got hits on sku 1 and 2)
assertThat(sql.get(0)).contains("from o_cached_natkey t0 where t0.store = ? and t0.sku in (? ) order by t0.sku desc; --bind(abc,Array[1]={3})");
}
assertThat(list).hasSize(3);
assertNaturalKeyHitMiss(2, 1);
assertBeanCacheHitMiss(2, 0);
}
@Test
public void findList_inClause_allHits() {
setup();
loadSomeIntoCache();
String storeId = "abc";
List<String> skus = new ArrayList<>(Arrays.asList("1", "2"));
LoggedSqlCollector.start();
List<OCachedNatKeyBean> list = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", storeId)
.in("sku", skus)
.setUseCache(true)
.orderBy("sku desc")
.findList();
List<String> sql = LoggedSqlCollector.stop();
// no SQL - all beans from cache
assertThat(sql).isEmpty();
assertThat(list).hasSize(2);
assertNaturalKeyHitMiss(2, 0);
assertBeanCacheHitMiss(2, 0);
}
@Test
public void findList_inClause_noHits() {
setup();
loadSomeIntoCache();
String storeId = "abc";
List<String> skus = new ArrayList<>(Arrays.asList("3", "4"));
LoggedSqlCollector.start();
List<OCachedNatKeyBean> list = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", storeId)
.in("sku", skus)
.setUseCache(true)
.orderBy("sku desc")
.findList();
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
if (isH2()) {
// in clause with 2 bind params as we got not hits on the cache
assertThat(sql.get(0)).contains("from o_cached_natkey t0 where t0.store = ? and t0.sku in (?, ? ) order by t0.sku desc; --bind(abc,Array[2]={3,4})");
}
assertThat(list).hasSize(2);
assertNaturalKeyHitMiss(0, 2);
assertBeanCacheHitMiss(0, 0);
}
@Test
public void hit() {
setup();
loadSomeIntoCache();
// simple case - no IN clause
OCachedNatKeyBean hit = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", "abc")
.eq("sku", "2")
.findOne();
assertThat(hit).isNotNull();
assertNaturalKeyHitMiss(1, 0);
assertBeanCacheHitMiss(1, 0);
}
@Test
public void miss() {
setup();
OCachedNatKeyBean miss = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", "def")
.eq("sku", "4")
.findOne();
assertThat(miss).isNotNull();
// miss on natural key
assertNaturalKeyHitMiss(0, 1);
// no activity against bean cache
assertBeanCacheHitMiss(0, 0);
}
@Test
public void explicitUseCacheFalse_expect_noUseOfCache() {
setup();
OCachedNatKeyBean miss = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", "def")
.eq("sku", "4")
.setUseCache(false) // explicitly not use cache
.findOne();
assertThat(miss).isNotNull();
// no activity against either cache
assertNaturalKeyHitMiss(0, 0);
assertBeanCacheHitMiss(0, 0);
}
}
@@ -0,0 +1,250 @@
package org.tests.model.basic.cache;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheManager;
import io.ebean.cache.ServerCacheStatistics;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestCacheViaComplexNaturalKey3 extends BaseTestCase {
private ServerCache beanCache = cacheManager().getBeanCache(OCachedNatKeyBean3.class);
private ServerCache natKeyCache = cacheManager().getNaturalKeyCache(OCachedNatKeyBean3.class);
private static boolean loadOnce;
private ServerCacheManager cacheManager() {
return server().getServerCacheManager();
}
private static synchronized void insertSome() {
if (!loadOnce) {
Ebean.find(OCachedNatKeyBean3.class).delete();
List<String> stores = new ArrayList<>(Arrays.asList("abc", "def"));
for (String store : stores) {
List<String> skus = new ArrayList<>(Arrays.asList("1", "2", "3"));
for (String sku : skus) {
int[] codes = {1000,1001,1002,1003,1004};
for (int code : codes) {
saveBean(store, code, sku);
}
}
}
loadOnce = true;
}
}
private static void saveBean(String store, int code, String sku) {
Ebean.save(new OCachedNatKeyBean3(store, code, sku));
}
private void clearCacheAndStatistics() {
Ebean.getServerCacheManager().clearAll();
clearStatistics();
}
private void clearStatistics() {
beanCache.getStatistics(true);
natKeyCache.getStatistics(true);
}
private void assertNaturalKeyHitMiss(int expectedHit, int expectedMiss) {
ServerCacheStatistics stats = natKeyCache.getStatistics(true);
assertHitMiss(expectedHit, expectedMiss, stats);
}
private void assertBeanCacheHitMiss(int expectedHit, int expectedMiss) {
ServerCacheStatistics stats = beanCache.getStatistics(true);
assertHitMiss(expectedHit, expectedMiss, stats);
}
private void assertHitMiss(int expectedHit, int expectedMiss, ServerCacheStatistics stats) {
assertThat(stats.getHitCount()).isEqualTo(expectedHit);
assertThat(stats.getMissCount()).isEqualTo(expectedMiss);
}
private void setup() {
insertSome();
clearCacheAndStatistics();
}
private void loadSomeIntoCache() {
Ebean.find(OCachedNatKeyBean3.class)
.setLoadBeanCache(true)
.where()
.ge("sku", "2")
.eq("store", "def")
.ge("code", 1001)
.findList();
clearStatistics();
}
@Test
public void findList_inClause_someHits() {
setup();
loadSomeIntoCache();
List<Integer> codes = new ArrayList<>(Arrays.asList(1001, 1000, 1002, 1003));
LoggedSqlCollector.start();
List<OCachedNatKeyBean3> list = Ebean.find(OCachedNatKeyBean3.class)
.where()
.eq("store", "def")
.in("code", codes)
.eq("sku", "2")
.setUseCache(true)
.order().desc("sku")
.order().asc("code")
.findList();
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
if (isH2()) {
// in clause with only 1 bind param - (sku=3 ... we got hits on sku 1 and 2)
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and t0.code in (? ) and t0.sku = ? order by t0.sku desc, t0.code; --bind(def,Array[1]={1000},2)");
}
assertThat(list).hasSize(4);
assertNaturalKeyHitMiss(3, 1);
assertBeanCacheHitMiss(3, 0);
}
@Test
public void findList_inClause_allHits() {
setup();
loadSomeIntoCache();
List<String> skus = new ArrayList<>(Arrays.asList("2", "3"));
LoggedSqlCollector.start();
List<OCachedNatKeyBean3> list = Ebean.find(OCachedNatKeyBean3.class)
.where()
.eq("store", "def")
.in("sku", skus)
.eq("code", 1001)
.setUseCache(true)
.orderBy("sku desc")
.findList();
List<String> sql = LoggedSqlCollector.stop();
// no SQL - all beans from cache
assertThat(sql).isEmpty();
assertThat(list).hasSize(2);
assertNaturalKeyHitMiss(2, 0);
assertBeanCacheHitMiss(2, 0);
}
@Test
public void findList_inClause_noHits() {
setup();
loadSomeIntoCache();
String storeId = "abc";
List<String> skus = new ArrayList<>(Arrays.asList("3", "2", "4"));
LoggedSqlCollector.start();
List<OCachedNatKeyBean3> list = Ebean.find(OCachedNatKeyBean3.class)
.where()
.eq("store", storeId)
.in("sku", skus)
.eq("code", 1001)
.setUseCache(true)
.orderBy("sku desc")
.findList();
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
if (isH2()) {
// in clause with 2 bind params as we got not hits on the cache
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and t0.sku in (?, ?, ? ) and t0.code = ? order by t0.sku desc; --bind(abc,Array[3]={3,2,4},1001)");
}
assertThat(list).hasSize(2);
assertNaturalKeyHitMiss(0, 3);
assertBeanCacheHitMiss(0, 0);
}
@Test
public void hit() {
setup();
loadSomeIntoCache();
// simple case - no IN clause
OCachedNatKeyBean3 hit = Ebean.find(OCachedNatKeyBean3.class)
.where()
.eq("store", "def")
.eq("sku", "2")
.eq("code", 1002)
.findOne();
assertThat(hit).isNotNull();
assertNaturalKeyHitMiss(1, 0);
assertBeanCacheHitMiss(1, 0);
}
@Test
public void miss() {
setup();
OCachedNatKeyBean3 miss = Ebean.find(OCachedNatKeyBean3.class)
.where()
.eq("store", "abc")
.eq("sku", "2")
.eq("code", 1000)
.findOne();
assertThat(miss).isNotNull();
// miss on natural key
assertNaturalKeyHitMiss(0, 1);
// no activity against bean cache
assertBeanCacheHitMiss(0, 0);
}
@Test
public void explicitUseCacheFalse_expect_noUseOfCache() {
setup();
OCachedNatKeyBean3 miss = Ebean.find(OCachedNatKeyBean3.class)
.where()
.eq("store", "abc")
.eq("sku", "2")
.eq("code", 1000)
.setUseCache(false) // explicitly not use cache
.findOne();
assertThat(miss).isNotNull();
// no activity against either cache
assertNaturalKeyHitMiss(0, 0);
assertBeanCacheHitMiss(0, 0);
}
}