From 96747115165794c329fca4346ab4135e566d400c Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Mon, 19 Aug 2019 21:36:19 +1200 Subject: [PATCH] #1796 - ENH: add query.fetchCache(path) ... for explicitly building part of graph from L2 cache --- pom.xml | 4 +- src/main/java/io/ebean/FetchConfig.java | 22 +++ src/main/java/io/ebean/FetchGroupBuilder.java | 14 ++ src/main/java/io/ebean/Query.java | 13 ++ .../io/ebean/bean/EntityBeanIntercept.java | 23 ++- .../io/ebeaninternal/api/LoadBeanRequest.java | 15 +- .../server/cache/CachedBeanDataToBean.java | 2 + .../server/core/DefaultBeanLoader.java | 12 +- .../server/core/DefaultServer.java | 6 - .../server/core/OrmQueryRequest.java | 1 + .../server/deploy/BeanDescriptor.java | 7 + .../deploy/BeanDescriptorCacheHelp.java | 31 +-- .../server/deploy/id/IdBinderFactory.java | 2 - .../server/loadcontext/DLoadBeanContext.java | 2 +- .../server/query/DFetchGroupBuilder.java | 14 ++ .../server/query/DefaultFetchGroupQuery.java | 12 ++ .../server/querydefn/DefaultOrmQuery.java | 11 ++ .../server/querydefn/OrmQueryProperties.java | 3 + .../tests/cache/TestBeanFetchJoinCache.java | 177 ++++++++++++++++-- src/test/resources/ebean.properties | 2 +- 20 files changed, 319 insertions(+), 54 deletions(-) diff --git a/pom.xml b/pom.xml index 0b6f28be0..d29290d7f 100644 --- a/pom.xml +++ b/pom.xml @@ -237,7 +237,7 @@ io.ebean.test ebean-test-docker - 2.10.1 + 2.10.3 test @@ -330,7 +330,7 @@ io.ebean ebean-maven-plugin - 11.42.1 + 11.43.2 test diff --git a/src/main/java/io/ebean/FetchConfig.java b/src/main/java/io/ebean/FetchConfig.java index 76324c33c..39823b2c7 100644 --- a/src/main/java/io/ebean/FetchConfig.java +++ b/src/main/java/io/ebean/FetchConfig.java @@ -148,6 +148,8 @@ public class FetchConfig implements Serializable { private boolean queryAll; + private boolean cache; + /** * Construct the fetch configuration object. */ @@ -188,6 +190,17 @@ public class FetchConfig implements Serializable { return this; } + /** + * Eagerly fetch the beans fetching the beans from the L2 bean cache + * and using the DB for beans not in the cache. + */ + public FetchConfig cache() { + this.cache = true; + this.queryBatchSize = 0; + this.queryAll = true; + return this; + } + /** * Eagerly fetch the beans in this path as a separate query (rather than as * part of the main query). @@ -247,6 +260,13 @@ public class FetchConfig implements Serializable { return queryAll; } + /** + * Return true if this uses L2 bean cache. + */ + public boolean isCache() { + return cache; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -255,6 +275,7 @@ public class FetchConfig implements Serializable { FetchConfig that = (FetchConfig) o; if (lazyBatchSize != that.lazyBatchSize) return false; if (queryBatchSize != that.queryBatchSize) return false; + if (cache != that.cache) return false; return queryAll == that.queryAll; } @@ -263,6 +284,7 @@ public class FetchConfig implements Serializable { int result = lazyBatchSize; result = 92821 * result + queryBatchSize; result = 92821 * result + (queryAll ? 1 : 0); + result = 92821 * result + (cache ? 1 : 0); return result; } } diff --git a/src/main/java/io/ebean/FetchGroupBuilder.java b/src/main/java/io/ebean/FetchGroupBuilder.java index 5b4f8ad3a..121cceb53 100644 --- a/src/main/java/io/ebean/FetchGroupBuilder.java +++ b/src/main/java/io/ebean/FetchGroupBuilder.java @@ -67,12 +67,26 @@ public interface FetchGroupBuilder { @Nonnull FetchGroupBuilder fetchQuery(String path); + /** + * Fetch the path including all its properties using L2 cache. + * Cache misses fallback to fetchQuery(). + */ + @Nonnull + FetchGroupBuilder fetchCache(String path); + /** * Fetch the path including specified properties using a query join. */ @Nonnull FetchGroupBuilder fetchQuery(String path, String properties); + /** + * Fetch the path including specified properties using L2 cache. + * Cache misses fallback to fetchQuery(). + */ + @Nonnull + FetchGroupBuilder fetchCache(String path, String properties); + /** * Fetch the path including all its properties lazily. */ diff --git a/src/main/java/io/ebean/Query.java b/src/main/java/io/ebean/Query.java index 218f12a43..0ed9f4622 100644 --- a/src/main/java/io/ebean/Query.java +++ b/src/main/java/io/ebean/Query.java @@ -501,6 +501,14 @@ public interface Query { */ Query fetchQuery(String path, String fetchProperties); + /** + * Fetch the path and properties using L2 bean cache. + * + * @param path The path of the beans we are fetching from L2 cache. + * @param fetchProperties The properties that should be loaded. + */ + Query fetchCache(String path, String fetchProperties); + /** * Fetch the path and properties lazily (via batch lazy loading). *

@@ -587,6 +595,11 @@ public interface Query { */ Query fetchQuery(String path); + /** + * Fetch the path eagerly using L2 cache. + */ + Query fetchCache(String path); + /** * Fetch the path lazily (via batch lazy loading). *

diff --git a/src/main/java/io/ebean/bean/EntityBeanIntercept.java b/src/main/java/io/ebean/bean/EntityBeanIntercept.java index 2a6f3e720..37922364b 100644 --- a/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -95,13 +95,10 @@ public final class EntityBeanIntercept implements Serializable { private final byte[] flags; private boolean fullyLoadedBean; - + private boolean loadedFromCache; private Object[] origValues; - private Exception[] loadErrors; - private int lazyLoadProperty = -1; - private Object ownerId; private int sortOrder; @@ -311,6 +308,22 @@ public final class EntityBeanIntercept implements Serializable { } } + /** + * Set true when the bean has been loaded from L2 bean cache. + * The effect of this is that we should skip the cache if there + * is subsequent lazy loading (bean cache partially populated). + */ + public void setLoadedFromCache(boolean loadedFromCache) { + this.loadedFromCache = loadedFromCache; + } + + /** + * Return true if this bean was loaded from L2 bean cache. + */ + public boolean isLoadedFromCache() { + return loadedFromCache; + } + /** * Return true if the bean should be treated as readOnly. If a setter method * is called when it is readOnly an Exception is thrown. @@ -780,7 +793,7 @@ public final class EntityBeanIntercept implements Serializable { } public boolean[] getLoaded() { - boolean[] ret= new boolean[flags.length]; + boolean[] ret = new boolean[flags.length]; for (int i = 0; i < ret.length; i++) { ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0; } diff --git a/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java b/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java index 5f40bc89b..fef666a63 100644 --- a/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java +++ b/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java @@ -23,11 +23,14 @@ public class LoadBeanRequest extends LoadRequest { private final boolean loadCache; + private boolean loadedFromCache; + /** * Construct for lazy load request. */ - public LoadBeanRequest(LoadBeanBuffer LoadBuffer, String lazyLoadProperty, boolean loadCache) { - this(LoadBuffer, null, true, lazyLoadProperty, loadCache); + public LoadBeanRequest(LoadBeanBuffer LoadBuffer, EntityBeanIntercept ebi, boolean loadCache) { + this(LoadBuffer, null, true, ebi.getLazyLoadProperty(), loadCache); + this.loadedFromCache = ebi.isLoadedFromCache(); } /** @@ -52,11 +55,17 @@ public 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(); } diff --git a/src/main/java/io/ebeaninternal/server/cache/CachedBeanDataToBean.java b/src/main/java/io/ebeaninternal/server/cache/CachedBeanDataToBean.java index bb3bc4b75..6dbdae1ff 100644 --- a/src/main/java/io/ebeaninternal/server/cache/CachedBeanDataToBean.java +++ b/src/main/java/io/ebeaninternal/server/cache/CachedBeanDataToBean.java @@ -13,6 +13,8 @@ public class CachedBeanDataToBean { public static void load(BeanDescriptor desc, EntityBean bean, CachedBeanData cacheBeanData, PersistenceContext context) { EntityBeanIntercept ebi = bean._ebean_getIntercept(); + // any future lazy loading skips L2 bean cache + ebi.setLoadedFromCache(true); BeanProperty idProperty = desc.getIdProperty(); if (desc.getInheritInfo() != null) { diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultBeanLoader.java b/src/main/java/io/ebeaninternal/server/core/DefaultBeanLoader.java index 0a4487c52..fc8d42027 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultBeanLoader.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultBeanLoader.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.core; +import io.ebean.CacheMode; import io.ebean.ExpressionList; import io.ebean.Transaction; import io.ebean.bean.BeanCollection; @@ -38,10 +39,6 @@ class DefaultBeanLoader { this.onIterateUseExtraTxn = server.getDatabasePlatform().useExtraTransactionOnIterateSecondaryQueries(); } - void refreshMany(EntityBean parentBean, String propertyName) { - refreshMany(parentBean, propertyName, null); - } - void loadMany(LoadManyRequest loadRequest) { SpiQuery query = loadRequest.createQuery(server); @@ -57,8 +54,8 @@ class DefaultBeanLoader { loadManyInternal(parentBean, propertyName, null, false, onlyIds); } - private void refreshMany(EntityBean parentBean, String propertyName, Transaction t) { - loadManyInternal(parentBean, propertyName, t, true, false); + void refreshMany(EntityBean parentBean, String propertyName) { + loadManyInternal(parentBean, propertyName, null, true, false); } private void loadManyInternal(EntityBean parentBean, String propertyName, Transaction t, boolean refresh, boolean onlyIds) { @@ -159,6 +156,9 @@ 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); diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 7f141146a..87adc2066 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -570,37 +570,31 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public void refreshMany(Object parentBean, String propertyName) { - beanLoader.refreshMany(checkEntityBean(parentBean), propertyName); } @Override public void loadMany(LoadManyRequest loadRequest) { - beanLoader.loadMany(loadRequest); } @Override public void loadMany(BeanCollection bc, boolean onlyIds) { - beanLoader.loadMany(bc, onlyIds); } @Override public void refresh(Object bean) { - beanLoader.refresh(checkEntityBean(bean)); } @Override public void loadBean(LoadBeanRequest loadRequest) { - beanLoader.loadBean(loadRequest); } @Override public void loadBean(EntityBeanIntercept ebi) { - beanLoader.loadBean(ebi); } diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index 4a9fa0625..d48539a6a 100644 --- a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -97,6 +97,7 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery this.queryEngine = queryEngine; this.query = query; this.readOnly = query.isReadOnly(); + this.persistenceContext = query.getPersistenceContext(); } public PersistenceException translate(String bindLog, String sql, SQLException e) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index ae11750bb..de4bf14e6 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -2316,6 +2316,13 @@ public class BeanDescriptor implements BeanType, STreeType { return idBinder.convertSetId(idValue, bean); } + /** + * Set the Id value to the bean (without type conversion). + */ + public void setId(Object idValue, EntityBean bean) { + idProperty.setValueIntercept(bean, idValue); + } + @Override public Property getProperty(String propName) { return findProperty(propName); diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java index 329846ff2..5c6c72ced 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java @@ -675,25 +675,30 @@ final class BeanDescriptorCacheHelp { */ EntityBean loadBeanDirect(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) { + id = desc.convertId(id); + EntityBean bean = null; if (context == null) { context = new DefaultPersistenceContext(); + } else { + bean = (EntityBean)desc.contextGet(context, id); + } + + if (bean == null) { + bean = desc.createEntityBean(); + desc.setId(id, bean); + desc.contextPut(context, id, bean); + + EntityBeanIntercept ebi = bean._ebean_getIntercept(); + // Not using loadContext here so no batch lazy loading for these beans + ebi.setBeanLoader(desc.getEbeanServer()); + if (Boolean.TRUE.equals(readOnly)) { + ebi.setReadOnly(true); + } + ebi.setPersistenceContext(context); } - EntityBean bean = desc.createEntityBean(); - id = desc.convertSetId(id, bean); CachedBeanDataToBean.load(desc, bean, data, context); - EntityBeanIntercept ebi = bean._ebean_getIntercept(); - - // Not using a loadContext for beans coming out of L2 cache - // so that means no batch lazy loading for these beans - ebi.setBeanLoader(desc.getEbeanServer()); - if (Boolean.TRUE.equals(readOnly)) { - ebi.setReadOnly(true); - } - ebi.setPersistenceContext(context); - desc.contextPut(context, id, bean); - if (desc.isReadAuditing()) { desc.readAuditBean("l2", "", bean); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderFactory.java b/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderFactory.java index d5140f589..70f8a1175 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderFactory.java +++ b/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderFactory.java @@ -24,11 +24,9 @@ public class IdBinderFactory { * Create the IdConvertSet for the given type of Id properties. */ public IdBinder createIdBinder(BeanProperty id) { - if (id == null) { // for report type beans that don't need an id return EMPTY; - } if (id.isEmbedded()) { return new IdBinderEmbedded(idInExpandedForm, (BeanPropertyAssocOne) id); diff --git a/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java index 492a0902c..96c696c21 100644 --- a/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -191,7 +191,7 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext { } } - LoadBeanRequest req = new LoadBeanRequest(this, ebi.getLazyLoadProperty(), context.hitCache); + LoadBeanRequest req = new LoadBeanRequest(this, ebi, context.hitCache); context.desc.getEbeanServer().loadBean(req); } } diff --git a/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java b/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java index 15c876b0d..1c858f3ab 100644 --- a/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java +++ b/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java @@ -11,6 +11,8 @@ import io.ebeaninternal.server.querydefn.SpiFetchGroup; */ class DFetchGroupBuilder implements FetchGroupBuilder { + private static final FetchConfig FETCH_CACHE = new FetchConfig().cache(); + private static final FetchConfig FETCH_QUERY = new FetchConfig().query(); private static final FetchConfig FETCH_LAZY = new FetchConfig().lazy(); @@ -61,6 +63,12 @@ class DFetchGroupBuilder implements FetchGroupBuilder { return this; } + @Override + public FetchGroupBuilder fetchCache(String path) { + detail.fetch(path, null, FETCH_CACHE); + return this; + } + @Override public FetchGroupBuilder fetchLazy(String path) { detail.fetch(path, null, FETCH_LAZY); @@ -79,6 +87,12 @@ class DFetchGroupBuilder implements FetchGroupBuilder { return this; } + @Override + public FetchGroupBuilder fetchCache(String path, String properties) { + detail.fetch(path, properties, FETCH_CACHE); + return this; + } + @Override public FetchGroupBuilder fetchLazy(String path, String properties) { detail.fetch(path, properties, FETCH_LAZY); diff --git a/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java b/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java index ff0632949..e7912546f 100644 --- a/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java +++ b/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java @@ -43,6 +43,8 @@ import java.util.function.Predicate; */ class DefaultFetchGroupQuery implements SpiFetchGroupQuery { + private static final FetchConfig FETCH_CACHE = new FetchConfig().cache(); + private static final FetchConfig FETCH_QUERY = new FetchConfig().query(); private static final FetchConfig FETCH_LAZY = new FetchConfig().lazy(); @@ -76,6 +78,11 @@ class DefaultFetchGroupQuery implements SpiFetchGroupQuery { return fetch(property, null, FETCH_QUERY); } + @Override + public Query fetchCache(String property) { + return fetch(property, null, FETCH_CACHE); + } + @Override public Query fetchLazy(String property) { return fetch(property, null, FETCH_LAZY); @@ -96,6 +103,11 @@ class DefaultFetchGroupQuery implements SpiFetchGroupQuery { return fetch(property, columns, FETCH_QUERY); } + @Override + public Query fetchCache(String property, String columns) { + return fetch(property, columns, FETCH_CACHE); + } + @Override public Query fetchLazy(String property, String columns) { return fetch(property, columns, FETCH_LAZY); diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index b03596175..dfdb3708d 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -79,6 +79,8 @@ public class DefaultOrmQuery implements SpiQuery { private static final String DEFAULT_QUERY_NAME = "default"; + private static final FetchConfig FETCH_CACHE = new FetchConfig().cache(); + private static final FetchConfig FETCH_QUERY = new FetchConfig().query(); private static final FetchConfig FETCH_LAZY = new FetchConfig().lazy(); @@ -1405,6 +1407,10 @@ public class DefaultOrmQuery implements SpiQuery { return fetch(property, null, FETCH_QUERY); } + public Query fetchCache(String property) { + return fetch(property, null, FETCH_CACHE); + } + @Override public Query fetchLazy(String property) { return fetch(property, null, FETCH_LAZY); @@ -1425,6 +1431,11 @@ public class DefaultOrmQuery implements SpiQuery { return fetch(property, columns, FETCH_QUERY); } + @Override + public Query fetchCache(String property, String columns) { + return fetch(property, columns, FETCH_CACHE); + } + @Override public Query fetchLazy(String property, String columns) { return fetch(property, columns, FETCH_LAZY); diff --git a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java index bc4d5d005..cf03b0188 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -107,6 +107,9 @@ public class OrmQueryProperties implements Serializable { this.readOnly = response.readOnly; if (fetchConfig != null) { this.fetchConfig = fetchConfig; + if (fetchConfig.isCache()) { + this.cache = true; + } } else { this.fetchConfig = response.fetchConfig; } diff --git a/src/test/java/org/tests/cache/TestBeanFetchJoinCache.java b/src/test/java/org/tests/cache/TestBeanFetchJoinCache.java index 118d0e114..25eb97ca1 100644 --- a/src/test/java/org/tests/cache/TestBeanFetchJoinCache.java +++ b/src/test/java/org/tests/cache/TestBeanFetchJoinCache.java @@ -2,6 +2,10 @@ package org.tests.cache; import io.ebean.BaseTestCase; import io.ebean.DB; +import io.ebean.FetchGroup; +import io.ebean.cache.ServerCache; +import io.ebean.cache.ServerCacheStatistics; +import org.ebeantest.LoggedSqlCollector; import org.junit.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; @@ -10,36 +14,179 @@ import org.tests.model.basic.ResetBasicData; import java.util.List; import static io.ebean.CacheMode.ON; +import static org.assertj.core.api.Assertions.assertThat; public class TestBeanFetchJoinCache extends BaseTestCase { + private final ServerCache customerBeanCache = server().getServerCacheManager().getBeanCache(Customer.class); + @Test - public void test() { + public void fetchCache_when_allHits() { - ResetBasicData.reset(); + initDataClearCache(); + loadCustomerBeanCache(); -// DB.find(Customer.class) -// .setBeanCacheMode(ON) -// .findList(); + customerBeanCache.getStatistics(true); - - DB.find(Customer.class) - .where().idIn(1,2,3) - //.setBeanCacheMode(ON) - .findList(); + LoggedSqlCollector.start(); List orders = DB.find(Order.class) - .fetchQuery("customer", "name") + .fetchCache("customer", "status, name") .findList(); + final List sql0 = LoggedSqlCollector.current(); + assertThat(sql0).hasSize(1); - orders = DB.find(Order.class) - .fetchQuery("customer", "+cache, name") - .findList(); + final ServerCacheStatistics statistics = customerBeanCache.getStatistics(true); + assertThat(statistics.getHitCount()).isEqualTo(2); + + assertThat(trimSql(sql0.get(0))).doesNotContain("t1.status"); for (Order order : orders) { - order.getCustomer().getName(); + final Customer customer = order.getCustomer(); + assertThat(customer.getName()).isNotNull(); + assertThat(customer.getStatus()).isNotNull(); } + assertThat(LoggedSqlCollector.stop()).isEmpty(); + } + + private void loadCustomerBeanCache() { + DB.find(Customer.class) + .setBeanCacheMode(ON) + .findList(); + } + + @Test + public void fetchCache_when_someHits() { + + initDataClearCache(); + + DB.find(Customer.class) + .setBeanCacheMode(ON) + .where().lt("id", 2) + .findList(); + + customerBeanCache.getStatistics(true); + + LoggedSqlCollector.start(); + + List orders = DB.find(Order.class) + .fetchCache("customer") + .findList(); + + final List sql0 = LoggedSqlCollector.current(); + assertThat(sql0).hasSize(2); + assertThat(sql0.get(0)).contains(" from o_order "); + assertThat(sql0.get(1)).contains(" from o_customer t0 where t0.id "); + + final ServerCacheStatistics statistics = customerBeanCache.getStatistics(true); + assertThat(statistics.getHitCount()).isEqualTo(1); + + for (Order order : orders) { + final Customer customer = order.getCustomer(); + assertThat(customer.getName()).isNotNull(); + assertThat(customer.getStatus()).isNotNull(); + } + + assertThat(LoggedSqlCollector.stop()).isEmpty(); + } + + @Test + public void fetchCache_when_hitsButBeanCachePartiallyLoaded() { + + initDataClearCache(); + + DB.find(Customer.class) + .setBeanCacheMode(ON) + .select("name") // not included status in bean cache + .findList(); + + customerBeanCache.getStatistics(true); + + LoggedSqlCollector.start(); + + List orders = DB.find(Order.class) + .fetchCache("customer") + .findList(); + + final List sql0 = LoggedSqlCollector.current(); + assertThat(sql0).hasSize(1); + + final ServerCacheStatistics statistics = customerBeanCache.getStatistics(true); + assertThat(statistics.getHitCount()).isEqualTo(2); + + assertThat(trimSql(sql0.get(0))).doesNotContain("t1.status"); + + 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 + } + + // 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 + final List sql1 = LoggedSqlCollector.stop(); + assertThat(sql1).hasSize(1); + assertThat(sql1.get(0)).contains(" from o_customer t0 "); + } + + private final FetchGroup fgBasic = FetchGroup.of(Order.class) + .fetchCache("customer") + .build(); + + @Test + public void fetchGroup_fetchCache() { + + initDataClearCache(); + + loadCustomerBeanCache(); + customerBeanCache.getStatistics(true); + + List orders = DB.find(Order.class) + .select(fgBasic) + .findList(); + + assertThat(orders).isNotEmpty(); + + final ServerCacheStatistics statistics1 = customerBeanCache.getStatistics(true); + assertThat(statistics1.getHitCount()).isEqualTo(2); + } + + private final FetchGroup fgCachePartial = FetchGroup.of(Order.class) + .fetchCache("customer", "name") + .build(); + + + @Test + public void fetchGroup_fetchCache_partial() { + + initDataClearCache(); + + customerBeanCache.getStatistics(true); + + LoggedSqlCollector.start(); + + List orders = DB.find(Order.class) + .select(fgCachePartial) + .findList(); + + assertThat(orders).isNotEmpty(); + + final ServerCacheStatistics statistics1 = customerBeanCache.getStatistics(true); + assertThat(statistics1.getMissCount()).isEqualTo(2); + + final List sql = LoggedSqlCollector.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains(" from o_order "); + assertThat(sql.get(1)).contains(" from o_customer "); + } + + private void initDataClearCache() { + ResetBasicData.reset(); + server().getServerCacheManager().clearAll(); } } diff --git a/src/test/resources/ebean.properties b/src/test/resources/ebean.properties index e1c4edbc6..85fd153cc 100644 --- a/src/test/resources/ebean.properties +++ b/src/test/resources/ebean.properties @@ -176,7 +176,7 @@ datasource.sqlserver.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver datasource.nuodb.schema=test_user datasource.nuodb.username=test_user datasource.nuodb.password=test -datasource.nuodb.url=jdbc:com.nuodb://localhost/unit +datasource.nuodb.url=jdbc:com.nuodb://localhost/testdb datasource.nuodb.driver=com.nuodb.jdbc.Driver datasource.db2.username=db2admin