Merge pull request #2340 from ebean-orm/feature/2335

#2335 -  L2 cache be triggered by in ?
This commit is contained in:
Rob Bygrave
2021-09-02 19:08:36 +12:00
committed by GitHub
14 changed files with 187 additions and 130 deletions
@@ -1,7 +1,9 @@
package io.ebeaninternal.api;
import io.ebean.CacheMode;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebeaninternal.api.SpiQuery.Mode;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -19,30 +21,29 @@ public final class LoadBeanRequest extends LoadRequest {
private final LoadBeanBuffer loadBuffer;
private final String lazyLoadProperty;
private final boolean loadCache;
private boolean loadedFromCache;
private final boolean alreadyLoaded;
/**
* Construct for lazy load request.
*/
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, EntityBeanIntercept ebi, boolean loadCache) {
this(LoadBuffer, null, true, ebi.getLazyLoadProperty(), loadCache);
this.loadedFromCache = ebi.isLoadedFromCache();
public LoadBeanRequest(LoadBeanBuffer loadBuffer, EntityBeanIntercept ebi, boolean loadCache) {
this(loadBuffer, null, true, ebi.getLazyLoadProperty(), ebi.isLoaded(), loadCache || ebi.isLoadedFromCache());
}
/**
* Construct for secondary query.
*/
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, OrmQueryRequest<?> parentRequest) {
this(LoadBuffer, parentRequest, false, null, false);
public LoadBeanRequest(LoadBeanBuffer loadBuffer, OrmQueryRequest<?> parentRequest) {
this(loadBuffer, parentRequest, false, null, false, false);
}
private LoadBeanRequest(LoadBeanBuffer loadBuffer, OrmQueryRequest<?> parentRequest, boolean lazy,
String lazyLoadProperty, boolean loadCache) {
String lazyLoadProperty, boolean alreadyLoaded, boolean loadCache) {
super(parentRequest, lazy);
this.loadBuffer = loadBuffer;
this.batch = loadBuffer.getBatch();
this.lazyLoadProperty = lazyLoadProperty;
this.alreadyLoaded = alreadyLoaded;
this.loadCache = loadCache;
}
@@ -51,17 +52,6 @@ public final class LoadBeanRequest extends LoadRequest {
return loadBuffer.getBeanDescriptor().getBeanType();
}
/**
* Return true if the beans invoking lazy loading were previously loaded from cache.
*/
public boolean isLoadedFromCache() {
return loadedFromCache;
}
private boolean isLoadCache() {
return loadCache;
}
public String getDescription() {
return "path:" + loadBuffer.getFullPath() + " batch:" + batch.size();
}
@@ -100,16 +90,21 @@ public final class LoadBeanRequest extends LoadRequest {
* Configure the query for lazy loading execution.
*/
public void configureQuery(SpiQuery<?> query, List<Object> idList) {
query.setMode(SpiQuery.Mode.LAZYLOAD_BEAN);
query.setMode(Mode.LAZYLOAD_BEAN);
query.setPersistenceContext(loadBuffer.getPersistenceContext());
String mode = isLazy() ? "+lazy" : "+query";
query.setLoadDescription(mode, getDescription());
if (isLazy()) {
// cascade the batch size (if set) for further lazy loading
query.setLoadDescription(lazy ? "+lazy" : "+query", getDescription());
if (lazy) {
query.setLazyLoadBatchSize(getBatchSize());
if (alreadyLoaded) {
query.setBeanCacheMode(CacheMode.OFF);
}
} else {
query.setBeanCacheMode(CacheMode.OFF);
}
loadBuffer.configureQuery(query, lazyLoadProperty);
if (loadCache) {
query.setBeanCacheMode(CacheMode.PUT);
}
if (idList.size() == 1) {
query.where().idEq(idList.get(0));
} else {
@@ -128,7 +123,7 @@ public final class LoadBeanRequest extends LoadRequest {
EntityBean loadedBean = (EntityBean) bean;
loadedIds.add(desc.getId(loadedBean));
}
if (isLoadCache()) {
if (loadCache) {
desc.cacheBeanPutAll(list);
}
if (lazyLoadProperty != null) {
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.CacheMode;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.core.BindPadding;
@@ -62,17 +63,6 @@ public final class LoadManyRequest extends LoadRequest {
return batch;
}
/**
* Return true if lazy loading should only load the id values.
* <p>
* This for use when lazy loading is invoked on methods such as clear() and removeAll() where it
* generally makes sense to only fetch the Id values as the other property information is not
* used.
*/
private boolean isOnlyIds() {
return onlyIds;
}
/**
* Return true if we should load the Collection ids into the cache.
*/
@@ -111,31 +101,25 @@ public final class LoadManyRequest extends LoadRequest {
if (orderBy != null) {
query.order(orderBy);
}
String extraWhere = many.getExtraWhere();
if (extraWhere != null) {
// replace special ${ta} placeholder with the base table alias
// which is always t0 and add the extra where clause
query.where().raw(extraWhere.replace("${ta}", "t0").replace("${mta}", "int_"));
}
query.setLazyLoadForParents(many);
many.addWhereParentIdIn(query, parentIdList(server), loadContext.isUseDocStore());
query.setPersistenceContext(loadContext.getPersistenceContext());
String mode = isLazy() ? "+lazy" : "+query";
query.setLoadDescription(mode, getDescription());
if (isLazy()) {
// cascade the batch size (if set) for further lazy loading
query.setLoadDescription(lazy ? "+lazy" : "+query", getDescription());
if (lazy) {
query.setLazyLoadBatchSize(getBatchSize());
} else {
query.setBeanCacheMode(CacheMode.OFF);
}
// potentially changes the joins and selected properties
// potentially changes the joins, selected properties, cache mode
loadContext.configureQuery(query);
if (isOnlyIds()) {
// override to just select the Id values
if (onlyIds) {
// lazy loading invoked via clear() and removeAll()
query.select(many.getTargetIdProperty());
}
return query;
@@ -9,13 +9,10 @@ import io.ebeaninternal.server.core.OrmQueryRequest;
public abstract class LoadRequest {
protected final OrmQueryRequest<?> parentRequest;
protected final Transaction transaction;
protected final boolean lazy;
public LoadRequest(OrmQueryRequest<?> parentRequest, boolean lazy) {
this.parentRequest = parentRequest;
this.transaction = parentRequest == null ? null : parentRequest.getTransaction();
this.lazy = lazy;
@@ -147,10 +147,6 @@ final class DefaultBeanLoader {
SpiQuery<?> query = server.createQuery(loadRequest.getBeanType());
loadRequest.configureQuery(query, idList);
if (loadRequest.isLoadedFromCache()) {
query.setBeanCacheMode(CacheMode.PUT);
}
List<?> list = executeQuery(loadRequest, query);
loadRequest.postLoad(list);
}
@@ -133,7 +133,7 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
SpiQuery<T> query = request.getQuery();
if (request.isBeanCachePutMany()) {
if (result != null && request.isBeanCachePutMany()) {
// load the individual beans into the bean cache
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
Collection<T> c = result.getActualDetails();
@@ -1232,10 +1232,8 @@ public final class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<
@Override
public void resetBeanCacheAutoMode(boolean findOne) {
if (useBeanCache == CacheMode.AUTO) {
if (!findOne || useQueryCache != CacheMode.OFF) {
useBeanCache = CacheMode.OFF;
}
if (useBeanCache == CacheMode.AUTO && useQueryCache != CacheMode.OFF) {
useBeanCache = CacheMode.OFF;
}
}
@@ -1,8 +1,7 @@
package org.tests.basic;
import io.ebean.BaseTestCase;
import io.ebean.CacheMode;
import io.ebean.Ebean;
import io.ebean.DB;
import io.ebean.bean.BeanCollection;
import org.junit.Assert;
import org.junit.Test;
@@ -16,6 +15,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import static org.junit.Assert.assertNotNull;
public class TestReadOnlyPropagation extends BaseTestCase {
@Test
@@ -23,21 +24,20 @@ public class TestReadOnlyPropagation extends BaseTestCase {
ResetBasicData.reset();
Order order = Ebean.find(Order.class)
.setAutoTune(false)
.setBeanCacheMode(CacheMode.OFF)
Order order = DB.find(Order.class)
.setReadOnly(true)
.setId(1)
.findOne();
Assert.assertTrue(Ebean.getBeanState(order).isReadOnly());
Assert.assertTrue(DB.getBeanState(order).isReadOnly());
Customer customer = order.getCustomer();
Assert.assertTrue(Ebean.getBeanState(customer).isReadOnly());
Assert.assertTrue(DB.getBeanState(customer).isReadOnly());
Address billingAddress = customer.getBillingAddress();
Assert.assertTrue(Ebean.getBeanState(billingAddress).isReadOnly());
assertNotNull(billingAddress);
Assert.assertTrue(DB.getBeanState(billingAddress).isReadOnly());
List<OrderDetail> details = order.getDetails();
@@ -1,7 +1,7 @@
package org.tests.batchload;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.DB;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheStatistics;
import org.tests.model.basic.UUOne;
@@ -17,66 +17,53 @@ import static org.junit.Assert.assertNotNull;
public class TestBatchLazyWithCacheHits extends BaseTestCase {
private ServerCache beanCache = server().getServerCacheManager().getBeanCache(UUOne.class);
private final ServerCache beanCache = server().getServerCacheManager().getBeanCache(UUOne.class);
private UUOne insert(String name) {
UUOne one = new UUOne();
one.setName("testBLWCH" + name);
Ebean.save(one);
DB.save(one);
return one;
}
@Test
public void testOnCacheHit() {
ArrayList<UUOne> inserted = insertData();
List<UUOne> inserted = insertData();
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);
assertNotNull(DB.find(UUOne.class, inserted.get(1).getId()));
// cache hit
assertNotNull(DB.find(UUOne.class, inserted.get(1).getId()));
assertBeanCacheHits(1);
UUOne c = Ebean.find(UUOne.class)
.where().idEq(inserted.get(2).getId())
.findOne();
assertNotNull(c);
UUOne c2 = Ebean.find(UUOne.class)
.where().idEq(inserted.get(2).getId())
.findOne();
assertNotNull(c2);
assertNotNull(DB.find(UUOne.class).where().idEq(inserted.get(2).getId()).findOne());
// cache hit
assertNotNull(DB.find(UUOne.class).where().idEq(inserted.get(2).getId()).findOne());
assertBeanCacheHits(1);
LoggedSqlCollector.start();
List<UUOne> list = Ebean.find(UUOne.class)
//.setDefaultLazyLoadBatchSize(5)
List<UUOne> list = DB.find(UUOne.class)
.select("id")
.where().startsWith("name", "testBLWCH")
.order("name")
.findList();
// invoke lazy loading
for (UUOne uuOne : list) {
uuOne.getName();
}
list.get(0).getName();
List<String> sql = LoggedSqlCollector.stop();
System.out.println("sql:" + sql);
assertThat(sql).hasSize(2);
assertSql(sql.get(0)).contains("from uuone t0 where t0.name like ");
platformAssertIn(sql.get(1), "from uuone t0 where t0.id");
// not lazy loading into bean cache
int size = beanCache.getStatistics(true).getSize();
assertThat(size).isEqualTo(2);
final ServerCacheStatistics stats = beanCache.getStatistics(true);
assertThat(stats.getHitCount()).isEqualTo(10);
assertThat(stats.getSize()).isEqualTo(10);
}
private void assertBeanCacheHits(int hits) {
@@ -85,13 +72,12 @@ public class TestBatchLazyWithCacheHits extends BaseTestCase {
}
private void clearCacheAndStatistics() {
beanCache.clear();
beanCache.getStatistics(true);
}
private ArrayList<UUOne> insertData() {
ArrayList<UUOne> inserted = new ArrayList<>();
private List<UUOne> insertData() {
List<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));
@@ -27,9 +27,8 @@ public class TestQueryJoin extends BaseTestCase {
custCache.clear();
Query<Order> query = Ebean.find(Order.class).select("status")
// .join("details","+query(10)")
.fetchLazy("customer", "name, status").fetch("customer.contacts").order().asc("id");
// .join("customer.billingAddress");
.fetchLazy("customer", "name, status")
.fetch("customer.contacts").order().asc("id");
List<Order> list = query.findList();
@@ -1,10 +1,6 @@
package org.tests.batchload;
import io.ebean.Ebean;
import io.ebean.FetchConfig;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.TransactionalTestCase;
import io.ebean.*;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.tests.model.basic.Customer;
@@ -42,6 +38,7 @@ public class TestSecondaryQueries extends TransactionalTestCase {
public void fetchLazy() {
LoggedSqlCollector.start();
DB.getServerCacheManager().clearAll();
List<Order> orders = Ebean.find(Order.class)
.select("status")
@@ -22,10 +22,8 @@ public class TestBeanFetchJoinCache extends BaseTestCase {
@Test
public void fetchCache_when_allHits() {
initDataClearCache();
loadCustomerBeanCache();
customerBeanCache.getStatistics(true);
LoggedSqlCollector.start();
@@ -59,7 +57,6 @@ public class TestBeanFetchJoinCache extends BaseTestCase {
@Test
public void fetchCache_when_someHits() {
initDataClearCache();
DB.find(Customer.class)
@@ -94,7 +91,6 @@ public class TestBeanFetchJoinCache extends BaseTestCase {
@Test
public void fetchCache_when_hitsButBeanCachePartiallyLoaded() {
initDataClearCache();
DB.find(Customer.class)
@@ -110,6 +106,9 @@ public class TestBeanFetchJoinCache extends BaseTestCase {
.fetchCache("customer")
.findList();
for (Order order : orders) {
assertThat(order.getCustomer().getName()).isNotNull();
}
final List<String> sql0 = LoggedSqlCollector.current();
assertThat(sql0).hasSize(1);
@@ -121,17 +120,17 @@ public class TestBeanFetchJoinCache extends BaseTestCase {
for (Order order : orders) {
final Customer customer = order.getCustomer();
assertThat(customer.getName()).isNotNull();
assertThat(customer.getStatus()).isNotNull(); // We hit the DB here as previously hit bean cache
assertThat(customer.getStatus()).isNotNull(); // We cache miss on property(status)
}
// assert we didn't hit the L2 bean cache the second time around
final ServerCacheStatistics statistics1 = customerBeanCache.getStatistics(true);
assertThat(statistics1.getHitCount()).isEqualTo(0);
// assert we did hit the DB the second time around
// assert we hit the DB the second time around
final List<String> sql1 = LoggedSqlCollector.stop();
assertThat(sql1).hasSize(1);
assertThat(sql1.get(0)).contains(" from o_customer t0 ");
// assert we didn't hit the L2 bean cache the second time around
final ServerCacheStatistics statistics1 = customerBeanCache.getStatistics(true);
assertThat(statistics1.getHitCount()).isGreaterThan(0);
}
private final FetchGroup<Order> fgBasic = FetchGroup.of(Order.class)
@@ -140,9 +139,7 @@ public class TestBeanFetchJoinCache extends BaseTestCase {
@Test
public void fetchGroup_fetchCache() {
initDataClearCache();
loadCustomerBeanCache();
customerBeanCache.getStatistics(true);
@@ -160,12 +157,9 @@ public class TestBeanFetchJoinCache extends BaseTestCase {
.fetchCache("customer", "name")
.build();
@Test
public void fetchGroup_fetchCache_partial() {
initDataClearCache();
customerBeanCache.getStatistics(true);
LoggedSqlCollector.start();
@@ -0,0 +1,69 @@
package org.tests.model.basic.cache;
import io.ebean.Model;
import io.ebean.annotation.Cache;
import io.ebean.annotation.SoftDelete;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
@Cache
@Entity
@Table(name = "e_softwithcache")
public class ESoftWithCache extends Model {
@Id
long id;
String name;
String description;
@SoftDelete
boolean deleted;
@Version
long version;
public ESoftWithCache(String name) {
this.name = name;
}
public long id() {
return id;
}
public ESoftWithCache id(long id) {
this.id = id;
return this;
}
public String name() {
return name;
}
public ESoftWithCache name(String name) {
this.name = name;
return this;
}
public String description() {
return description;
}
public ESoftWithCache description(String description) {
this.description = description;
return this;
}
public long version() {
return version;
}
public ESoftWithCache version(long version) {
this.version = version;
return this;
}
}
@@ -0,0 +1,46 @@
package org.tests.model.basic.cache;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheStatistics;
import io.ebeantest.LoggedSql;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestCacheWithSoftDelete extends BaseTestCase {
private final ServerCache beanCache = DB.getServerCacheManager().getBeanCache(ESoftWithCache.class);
@Test
public void idIn_expect_hitCache() {
ESoftWithCache bean = new ESoftWithCache("hello");
DB.save(bean);
final List<ESoftWithCache> found = DB.find(ESoftWithCache.class)
.where().idIn(bean.id())
.findList();
assertThat(found).hasSize(1);
// try to hit the cache
assertThat(stats().getPutCount()).isEqualTo(1);
LoggedSql.start();
// cache hit success this time
final List<ESoftWithCache> foundAgain = DB.find(ESoftWithCache.class)
.where().idIn(bean.id())
.findList();
final List<String> sql = LoggedSql.stop();
assertThat(sql).isEmpty();
assertThat(foundAgain).hasSize(1);
assertThat(stats().getHitCount()).isEqualTo(1);
}
private ServerCacheStatistics stats() {
return beanCache.getStatistics(true);
}
}
@@ -1,9 +1,6 @@
package org.tests.rawsql.nativesql;
import io.ebean.BaseTestCase;
import io.ebean.BeanState;
import io.ebean.Ebean;
import io.ebean.Query;
import io.ebean.*;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
import org.ebeantest.LoggedSqlCollector;
@@ -157,11 +154,10 @@ public class TestNativeSqlBasic extends BaseTestCase {
@Test
public void partialAssocIncludingOracle() {
ResetBasicData.reset();
DB.getServerCacheManager().clearAll();
String nativeSql = "select o.id, o.status, o.kcustomer_id from o_order o";
List<Order> orders = Ebean.findNative(Order.class, nativeSql)
.findList();