Merge pull request #1682 from ebean-orm/feature/1679-region

Feature/1679 region
This commit is contained in:
Rob Bygrave
2019-04-26 12:03:11 +12:00
committed by GitHub
40 changed files with 880 additions and 183 deletions
+1 -1
View File
@@ -117,7 +117,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>4.7</version>
<version>4.9</version>
</dependency>
<dependency>
+29 -2
View File
@@ -1,5 +1,7 @@
package io.ebean.cache;
import java.util.List;
/**
* The cache service for server side caching of beans and query results.
*/
@@ -14,6 +16,31 @@ public interface ServerCacheManager {
*/
boolean isLocalL2Caching();
/**
* Return all the cache regions.
*/
List<ServerCacheRegion> allRegions();
/**
* Set the regions that are enabled.
* <p>
* Typically this is set on startup and at runtime (via dynamic configuration).
* </p>
*
* @param regions A region name or comma delimited list of region names.
*/
void setEnabledRegions(String regions);
/**
* Enable or disable all the cache regions.
*/
void setAllRegionsEnabled(boolean enabled);
/**
* Return the cache region by name. Typically to enable or disable the region.
*/
ServerCacheRegion getRegion(String name);
/**
* Return the cache for mapping natural keys to id values.
*/
@@ -46,7 +73,7 @@ public interface ServerCacheManager {
/**
* Clear all the local caches.
*
* <p>
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
*/
@@ -54,7 +81,7 @@ public interface ServerCacheManager {
/**
* Clear the local caches for this bean type.
*
* <p>
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
*/
+23
View File
@@ -0,0 +1,23 @@
package io.ebean.cache;
/**
* Cache region can be enabled independently.
*/
public interface ServerCacheRegion {
/**
* Return the region name.
*/
String getName();
/**
* Return true if the cache region is enabled.
*/
boolean isEnabled();
/**
* Set to true to enable the cache region.
*/
void setEnabled(boolean enabled);
}
@@ -479,6 +479,7 @@ public class ServerConfig {
*/
private boolean disableL2Cache;
private String enabledL2Regions;
/**
* Should the javax.validation.constraints.NotNull enforce a notNull column in DB.
@@ -2914,6 +2915,7 @@ public class ServerConfig {
collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
enabledL2Regions = p.get("enabledL2Regions", enabledL2Regions);
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
@@ -3136,6 +3138,20 @@ public class ServerConfig {
this.expressionNativeIlike = expressionNativeIlike;
}
/**
* Return the enabled L2 cache regions.
*/
public String getEnabledL2Regions() {
return enabledL2Regions;
}
/**
* Set the enabled L2 cache regions (comma delimited).
*/
public void setEnabledL2Regions(String enabledL2Regions) {
this.enabledL2Regions = enabledL2Regions;
}
/**
* Return true if L2 cache is disabled.
*/
@@ -0,0 +1,52 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.expression.IdInExpression;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Used for bean cache lookup with where ids in expression.
*/
public class CacheIdLookup<T> {
private final IdInExpression idInExpression;
private int remaining;
public CacheIdLookup(IdInExpression idInExpression) {
this.idInExpression = idInExpression;
}
/**
* Return the Id values for the in expression.
*/
public Collection<?> idValues() {
return idInExpression.idValues();
}
/**
* Process the hits returning the beans fetched from cache and
* adjusting the in expression (to not fetch the hits).
*/
public List<T> removeHits(BeanCacheResult<T> cacheResult) {
Set<Object> hitIds = new HashSet<>();
List<T> beans = new ArrayList<>(hitIds.size());
for (BeanCacheResult.Entry<T> hit : cacheResult.hits()) {
hitIds.add(hit.getKey());
beans.add(hit.getBean());
}
this.remaining = idInExpression.removeIds(hitIds);
return beans;
}
public boolean allHits() {
return remaining == 0;
}
}
@@ -69,18 +69,18 @@ public class NaturalKeyQueryData<T> {
/**
* Match for IN expression. We only allow one IN clause.
*/
public List<Object> matchIn(String propName, List<Object> sourceValues) {
public boolean matchIn(String propName, List<Object> inValues) {
if (hasIn) {
// only 1 IN allowed (to project naturalIds)
return null;
return false;
}
if (matchProperty(propName)) {
this.hasIn = true;
this.inProperty = propName;
this.inValues = new ArrayList<>(sourceValues);
return this.inValues;
this.inValues = inValues;
return true;
}
return null;
return false;
}
/**
@@ -198,21 +198,19 @@ public class NaturalKeyQueryData<T> {
this.hitCount = hits.size();
List<T> beans = new ArrayList<>(hitCount);
for (BeanCacheResult.Entry<T> hit : hits) {
if (inValues != null) {
Object naturalKey = hit.getKey();
Object inValue = set.getInValue(naturalKey);
inValues.remove(inValue);
} else if (inPairs != null) {
Object naturalKey = hit.getKey();
Pairs.Entry inValue = (Pairs.Entry)set.getInValue(naturalKey);
inPairs.remove(inValue);
}
removeKey(set.getInValue(hit.getKey()));
beans.add(hit.getBean());
}
return beans;
}
private void removeKey(Object inValue) {
if (inValues != null) {
inValues.remove(inValue);
} else if (inPairs != null) {
//noinspection SuspiciousMethodCalls
inPairs.remove(inValue);
}
}
}
@@ -0,0 +1,25 @@
package io.ebeaninternal.api;
public interface SpiCacheControl {
/**
* Return true if bean caching or query caching.
*/
boolean isCaching();
/**
* Return true if bean caching.
*/
boolean isBeanCaching();
/**
* Return true if natural key caching.
*/
boolean isNaturalKeyCaching();
/**
* Return true if query caching.
*/
boolean isQueryCaching();
}
@@ -0,0 +1,10 @@
package io.ebeaninternal.api;
import io.ebean.cache.ServerCacheRegion;
/**
* Cache region can be enabled independently.
*/
public interface SpiCacheRegion extends ServerCacheRegion {
}
@@ -429,6 +429,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
void resetBeanCacheAutoMode(boolean findOne);
/**
* Bean cache lookup for find by ids.
*/
CacheIdLookup<T> cacheIdLookup();
/**
* Collect natural key data for this query or null if the query does not match
* the requirements of natural key lookup.
@@ -2,6 +2,9 @@ package io.ebeaninternal.server.cache;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheManager;
import io.ebean.cache.ServerCacheRegion;
import java.util.List;
/**
* Adapts SpiCacheManager to ServerCacheManager.
@@ -22,6 +25,26 @@ public class DefaultCacheAdapter implements ServerCacheManager {
return cacheManager.isLocalL2Caching();
}
@Override
public List<ServerCacheRegion> allRegions() {
return cacheManager.allRegions();
}
@Override
public void setEnabledRegions(String regions) {
cacheManager.setEnabledRegions(regions);
}
@Override
public ServerCacheRegion getRegion(String region) {
return cacheManager.getRegion(region);
}
@Override
public void setAllRegionsEnabled(boolean enabled) {
cacheManager.setAllRegionsEnabled(enabled);
}
@Override
public ServerCache getNaturalKeyCache(Class<?> beanType) {
return cacheManager.getNaturalKeyCache(beanType);
@@ -1,14 +1,29 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheRegion;
import io.ebean.cache.ServerCacheType;
import io.ebeaninternal.api.SpiCacheRegion;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.deploy.DCacheRegion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Manages the bean and query caches.
*/
public class DefaultServerCacheManager implements SpiCacheManager {
private static final Logger log = LoggerFactory.getLogger("io.ebean.cache.REGION");
private final Map<String, SpiCacheRegion> regionMap = new HashMap<>();
private final ClusterManager clusterManager;
private final DefaultCacheHolder cacheHolder;
@@ -39,6 +54,51 @@ public class DefaultServerCacheManager implements SpiCacheManager {
return localL2Caching;
}
@Override
public List<ServerCacheRegion> allRegions() {
return new ArrayList<>(regionMap.values());
}
@Override
public void setEnabledRegions(String regions) {
if (regions != null) {
List<String> enabledRegionNames = Arrays.asList(regions.split(","));
List<String> disabled = new ArrayList<>();
List<String> enabled = new ArrayList<>();
for (SpiCacheRegion region : regionMap.values()) {
if (enabledRegionNames.contains(region.getName())) {
enabled.add(region.getName());
if (!region.isEnabled()) {
region.setEnabled(true);
log.debug("Cache region[{}] enabled", region.getName());
}
} else {
disabled.add(region.getName());
if (region.isEnabled()) {
region.setEnabled(false);
log.debug("Cache region[{}] disabled", region.getName());
}
}
}
log.info("Cache regions enabled:{} disabled:{}", enabled, disabled);
}
}
@Override
public void setAllRegionsEnabled(boolean enabled) {
log.debug("All cache regions enabled[{}]", enabled);
for (SpiCacheRegion region : regionMap.values()) {
region.setEnabled(enabled);
}
}
@Override
public SpiCacheRegion getRegion(String region) {
return regionMap.computeIfAbsent(region, DCacheRegion::new);
}
/**
* Clear all caches.
*/
@@ -1,6 +1,10 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheRegion;
import io.ebeaninternal.api.SpiCacheRegion;
import java.util.List;
/**
* The cache service for server side caching of beans and query results.
@@ -16,6 +20,28 @@ public interface SpiCacheManager {
*/
boolean isLocalL2Caching();
/**
* Return all the regions.
*/
List<ServerCacheRegion> allRegions();
/**
* Set the regions that are enabled.
*
* @param regions A single region name or a comma delimited list of region names.
*/
void setEnabledRegions(String regions);
/**
* Enable or disable all cache regions.
*/
void setAllRegionsEnabled(boolean enabled);
/**
* Return the cache region. We can enable L2 caching by region.
*/
SpiCacheRegion getRegion(String region);
/**
* Return the cache for mapping natural keys to id values.
*/
@@ -12,45 +12,70 @@ public class CacheOptions {
*/
public static final CacheOptions NO_CACHING = new CacheOptions();
public static final CacheOptions INVALIDATE_QUERY_CACHE = new CacheOptions(true);
private static final String R0 = "r0";
private static final CacheOptions INVALIDATE_QUERY_CACHE_R0 = new CacheOptions(true);
private final boolean invalidateQueryCache;
private final boolean enableBeanCache;
private final boolean enableQueryCache;
private final boolean readOnly;
private final String[] naturalKey;
private final String region;
public static CacheOptions invalidateQueryCache(String region) {
if (R0.equals(region)) {
return INVALIDATE_QUERY_CACHE_R0;
} else {
return new CacheOptions(true, region);
}
}
/**
* Construct for no caching.
*/
private CacheOptions() {
invalidateQueryCache = false;
enableBeanCache = false;
enableQueryCache = false;
readOnly = false;
naturalKey = null;
this.invalidateQueryCache = false;
this.enableBeanCache = false;
this.enableQueryCache = false;
this.readOnly = false;
this.naturalKey = null;
this.region = null;
}
/**
* Construct for invalidateQueryCache.
*/
private CacheOptions(boolean invalidateQueryCache) {
this(invalidateQueryCache, R0);
}
private CacheOptions(boolean invalidateQueryCache, String region) {
this.invalidateQueryCache = invalidateQueryCache;
enableBeanCache = false;
enableQueryCache = false;
readOnly = false;
naturalKey = null;
this.enableBeanCache = false;
this.enableQueryCache = false;
this.readOnly = false;
this.naturalKey = null;
this.region = region;
}
/**
* Construct with cache annotation.
*/
public CacheOptions(Cache cache, String[] naturalKey) {
invalidateQueryCache = false;
enableBeanCache = cache.enableBeanCache();
enableQueryCache = cache.enableQueryCache();
readOnly = cache.readOnly();
this.invalidateQueryCache = false;
this.enableBeanCache = cache.enableBeanCache();
this.enableQueryCache = cache.enableQueryCache();
this.readOnly = cache.readOnly();
this.naturalKey = naturalKey;
this.region = cache.region();
}
/**
* Return the cache region name.
*/
public String getRegion() {
return region;
}
/**
@@ -446,6 +446,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (encryptKeyManager != null) {
encryptKeyManager.initialise();
}
serverCacheManager.setEnabledRegions(serverConfig.getEnabledL2Regions());
}
/**
@@ -484,7 +485,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
private void shutdownInternal(boolean shutdownDataSource, boolean deregisterDriver) {
logger.debug("Shutting down EbeanServer {}", serverName);
logger.debug("Shutting down instance:{}", serverName);
if (shutdown) {
// already shutdown
return;
@@ -790,23 +791,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
transactionManager.exitScopedTransaction(returnOrThrowable, opCode);
}
/**
* Returns the current transaction (or null) from the scope.
*/
@Override
public SpiTransaction currentServerTransaction() {
return transactionManager.getActive();
}
/**
* Start a transaction with 'REQUIRED' semantics.
* <p>
* If a transaction already exists that transaction will be used.
* </p>
* <p>
* Note that the transaction is stored in a ThreadLocal variable.
* </p>
*/
@Override
public Transaction beginTransaction() {
return beginTransaction(TxScope.required());
@@ -817,12 +806,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return transactionManager.beginScopedTransaction(txScope);
}
/**
* Start a transaction with a specific Isolation Level.
* <p>
* Note that the transaction is stored in a ThreadLocal variable.
* </p>
*/
@Override
public Transaction beginTransaction(TxIsolation isolation) {
// start an explicit transaction
@@ -837,10 +820,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return t;
}
/**
* Return the current transaction or null if there is not one currently in
* scope.
*/
@Override
public Transaction currentTransaction() {
return transactionManager.getActive();
@@ -851,50 +830,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
currentTransaction().flush();
}
/**
* Commit the current transaction.
*/
@Override
public void commitTransaction() {
currentTransaction().commit();
}
/**
* Rollback the current transaction.
*/
@Override
public void rollbackTransaction() {
currentTransaction().rollback();
}
/**
* If the current transaction has already been committed do nothing otherwise
* rollback the transaction.
* <p>
* Useful to put in a finally block to ensure the transaction is ended, rather
* than a rollbackTransaction() in each catch block.
* </p>
* <p>
* Code example:<br />
* <p>
* <pre>
* &lt;code&gt;
* Ebean.startTransaction();
* try {
* // do some fetching and or persisting
*
* // commit at the end
* Ebean.commitTransaction();
*
* } finally {
* // if commit didn't occur then rollback the transaction
* Ebean.endTransaction();
* }
* &lt;/code&gt;
* </pre>
* <p>
* </p>
*/
@Override
public void endTransaction() {
Transaction transaction = transactionManager.getInScope();
@@ -903,13 +848,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
/**
* return the next unique identity value.
* <p>
* Uses the BeanDescriptor deployment information to determine the sequence to
* use.
* </p>
*/
@Override
public Object nextId(Class<?> beanType) {
BeanDescriptor<?> desc = getBeanDescriptor(beanType);
@@ -1133,14 +1071,21 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
<T> SpiOrmQueryRequest<T> createQueryRequest(Type type, Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = buildQueryRequest(type, query, t);
request.prepareQuery();
return request;
}
<T> SpiOrmQueryRequest<T> buildQueryRequest(Type type, Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(type);
spiQuery.checkNamedParameters();
return createQueryRequest(spiQuery, t);
return buildQueryRequest(spiQuery, t);
}
private <T> SpiOrmQueryRequest<T> createQueryRequest(SpiQuery<T> query, Transaction t) {
private <T> SpiOrmQueryRequest<T> buildQueryRequest(SpiQuery<T> query, Transaction t) {
if (t == null) {
t = currentServerTransaction();
@@ -1163,10 +1108,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
query.setOrigin(createCallStack());
}
OrmQueryRequest<T> request = new OrmQueryRequest<>(this, queryEngine, query, (SpiTransaction) t);
request.prepareQuery();
return request;
return new OrmQueryRequest<>(this, queryEngine, query, (SpiTransaction) t);
}
/**
@@ -1237,7 +1179,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
SpiOrmQueryRequest<T> request = createQueryRequest(spiQuery, t);
SpiOrmQueryRequest<T> request = buildQueryRequest(spiQuery, t);
request.prepareQuery();
if (request.isUseDocStore()) {
return docStore().find(request);
}
@@ -1590,15 +1533,18 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@SuppressWarnings("unchecked")
private <T> List<T> findList(Query<T> query, Transaction t, boolean findOne) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, t);
SpiOrmQueryRequest<T> request = buildQueryRequest(Type.LIST, query, t);
request.resetBeanCacheAutoMode(findOne);
if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) {
// hit bean cache and got all results from cache
return request.getBeanCacheHits();
}
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (List<T>) result;
}
if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) {
return request.getBeanCacheHits();
}
if (request.isUseDocStore()) {
return docStore().findList(request);
}
@@ -16,6 +16,7 @@ import io.ebean.event.BeanQueryAdapter;
import io.ebean.text.json.JsonReadOptions;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.CacheIdLookup;
import io.ebeaninternal.api.HashQuery;
import io.ebeaninternal.api.LoadContext;
import io.ebeaninternal.api.NaturalKeyQueryData;
@@ -208,7 +209,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
/**
* Prepare the query and calculate the query plan key.
*/
void prepareQuery() {
@Override
public void prepareQuery() {
beanDescriptor.prepareQuery(query);
adapterPreQuery();
this.secondaryQueries = query.convertJoins();
@@ -598,6 +600,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
// - merge the 2 results and return
//
CacheIdLookup<T> idLookup = query.cacheIdLookup();
if (idLookup != null) {
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 (!beanDescriptor.isNaturalKeyCaching()) {
return false;
}
@@ -29,6 +29,11 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequ
*/
BeanDescriptor<T> getBeanDescriptor();
/**
* Prepare the query for execution.
*/
void prepareQuery();
/**
* This will create a local (readOnly) transaction if no current transaction
* exists.
@@ -1490,6 +1490,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return cacheHelp.beanCacheLoad(bean, ebi, cacheKey(id), context);
}
public BeanCacheResult<T> cacheIdLookup(PersistenceContext context, Collection<?> ids) {
return cacheHelp.cacheIdLookup(context, ids);
}
/**
* Use natural key lookup to hit the bean cache.
*/
@@ -7,6 +7,8 @@ import io.ebean.bean.PersistenceContext;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.cache.ServerCache;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.SpiCacheControl;
import io.ebeaninternal.api.SpiCacheRegion;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.server.cache.CacheChangeSet;
@@ -72,6 +74,9 @@ final class BeanDescriptorCacheHelp<T> {
private final boolean noCaching;
private final SpiCacheControl cacheControl;
private final SpiCacheRegion cacheRegion;
/**
* Set to true if all persist changes need to notify the cache.
*/
@@ -113,6 +118,13 @@ final class BeanDescriptorCacheHelp<T> {
this.naturalKeyCache = null;
}
this.noCaching = (beanCache == null && queryCache == null);
if (noCaching) {
this.cacheControl = DCacheControlNone.INSTANCE;
this.cacheRegion = (invalidateQueryCache) ? cacheManager.getRegion(cacheOptions.getRegion()) : DCacheRegionNone.INSTANCE;
} else {
this.cacheRegion = cacheManager.getRegion(cacheOptions.getRegion());
this.cacheControl = new DCacheControl(cacheRegion, (beanCache != null), (naturalKeyCache != null), (queryCache != null));
}
}
/**
@@ -123,8 +135,8 @@ final class BeanDescriptorCacheHelp<T> {
cacheNotifyOnDelete = !cacheNotifyOnAll && isNotifyOnDeletes();
if (logger.isDebugEnabled()) {
if (isBeanCaching() || isQueryCaching() || cacheNotifyOnAll || cacheNotifyOnDelete) {
String notifyMode = cacheNotifyOnAll ? "All" : (cacheNotifyOnDelete ? "Delete" : "None");
if (cacheNotifyOnAll || cacheNotifyOnDelete) {
String notifyMode = cacheNotifyOnAll ? "All" : "Delete";
logger.debug("l2 caching on {} - beanCaching:{} queryCaching:{} notifyMode:{} ",
desc.getFullName(), isBeanCaching(), isQueryCaching(), notifyMode);
}
@@ -148,36 +160,36 @@ final class BeanDescriptorCacheHelp<T> {
* Return true if the persist request needs to notify the cache.
*/
boolean isCacheNotify(PersistRequest.Type type) {
return cacheNotifyOnAll
|| cacheNotifyOnDelete && (type == PersistRequest.Type.DELETE || type == PersistRequest.Type.DELETE_PERMANENT);
return cacheRegion.isEnabled()
&& (cacheNotifyOnAll || cacheNotifyOnDelete && (type == PersistRequest.Type.DELETE || type == PersistRequest.Type.DELETE_PERMANENT));
}
/**
* Return true if there is currently query caching for this type of bean.
*/
boolean isQueryCaching() {
return queryCache != null;
return cacheControl.isQueryCaching();
}
/**
* Return true if there is currently bean caching for this type of bean.
*/
boolean isBeanCaching() {
return beanCache != null;
return cacheControl.isBeanCaching();
}
/**
* Return true if there is natural key caching for this type of bean.
*/
boolean isNaturalKeyCaching() {
return naturalKeyCache != null;
return cacheControl.isNaturalKeyCaching();
}
/**
* Return true if there is bean or query caching on this type.
*/
boolean isCaching() {
return beanCache != null || queryCache != null;
return cacheControl.isCaching();
}
/**
@@ -206,7 +218,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Add query cache clear to the changeSet.
*/
void queryCacheClear(CacheChangeSet changeSet) {
private void queryCacheClear(CacheChangeSet changeSet) {
if (queryCache != null) {
changeSet.addClearQuery(desc);
}
@@ -292,8 +304,8 @@ final class BeanDescriptorCacheHelp<T> {
return false;
}
Object ownerBean = bc.getOwnerBean();
EntityBeanIntercept ebi = ((EntityBean) ownerBean)._ebean_getIntercept();
EntityBean ownerBean = bc.getOwnerBean();
EntityBeanIntercept ebi = ownerBean._ebean_getIntercept();
PersistenceContext persistenceContext = ebi.getPersistenceContext();
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
@@ -362,6 +374,31 @@ final class BeanDescriptorCacheHelp<T> {
return new CachedManyIds(idList);
}
/**
* Hit the bean cache with the given ids returning the hits.
*/
BeanCacheResult<T> cacheIdLookup(PersistenceContext context, Collection<?> ids) {
Set<Object> keys = new HashSet<>(ids.size());
for (Object id : ids) {
keys.add(desc.cacheKey(id));
}
Map<Object, Object> beanDataMap = beanCache.getAll(keys);
if (beanLog.isTraceEnabled()) {
beanLog.trace(" GET MANY {}({}) - hits:{}", cacheName, ids, beanDataMap.keySet());
}
BeanCacheResult<T> result = new BeanCacheResult<>();
for (Map.Entry<Object, Object> entry : beanDataMap.entrySet()) {
CachedBeanData cachedBeanData = (CachedBeanData) entry.getValue();
T bean = convertToBean(entry.getKey(), false, context, cachedBeanData);
result.add(bean, desc.getBeanId(bean));
}
return result;
}
/**
* Use natural keys to hit the bean cache and return resulting hits.
*/
@@ -0,0 +1,42 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.api.SpiCacheControl;
import io.ebeaninternal.api.SpiCacheRegion;
/**
* Default implementation of SpiCacheControl.
*/
class DCacheControl implements SpiCacheControl {
private final SpiCacheRegion region;
private final boolean bean;
private final boolean naturalKey;
private final boolean query;
DCacheControl(SpiCacheRegion region, boolean bean, boolean naturalKey, boolean query) {
this.region = region;
this.bean = bean;
this.naturalKey = naturalKey;
this.query = query;
}
@Override
public boolean isCaching() {
return (bean || query) && region.isEnabled();
}
@Override
public boolean isBeanCaching() {
return bean && region.isEnabled();
}
@Override
public boolean isNaturalKeyCaching() {
return naturalKey && region.isEnabled();
}
@Override
public boolean isQueryCaching() {
return query && region.isEnabled();
}
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.api.SpiCacheControl;
/**
* Cache control used when no caching is on the bean type.
*/
class DCacheControlNone implements SpiCacheControl {
static final SpiCacheControl INSTANCE = new DCacheControlNone();
@Override
public boolean isCaching() {
return false;
}
@Override
public boolean isBeanCaching() {
return false;
}
@Override
public boolean isNaturalKeyCaching() {
return false;
}
@Override
public boolean isQueryCaching() {
return false;
}
}
@@ -0,0 +1,29 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.api.SpiCacheRegion;
public class DCacheRegion implements SpiCacheRegion {
private boolean enabled = true;
private final String name;
public DCacheRegion(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
@@ -0,0 +1,23 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.api.SpiCacheRegion;
class DCacheRegionNone implements SpiCacheRegion {
static SpiCacheRegion INSTANCE = new DCacheRegionNone();
@Override
public String getName() {
return "<none>";
}
@Override
public boolean isEnabled() {
return false;
}
@Override
public void setEnabled(boolean enabled) {
throw new IllegalStateException("Not expected");
}
}
@@ -473,8 +473,8 @@ public class DeployBeanDescriptor<T> {
/**
* Set that this type invalidates query caches.
*/
public void setInvalidateQueryCache() {
this.cacheOptions = CacheOptions.INVALIDATE_QUERY_CACHE;
public void setInvalidateQueryCache(String region) {
this.cacheOptions = CacheOptions.invalidateQueryCache(region);
}
/**
@@ -202,13 +202,12 @@ public class AnnotationClass extends AnnotationParser {
} else {
InvalidateQueryCache invalidateQueryCache = AnnotationUtil.findAnnotationRecursive(cls, InvalidateQueryCache.class);
if (invalidateQueryCache != null) {
descriptor.setInvalidateQueryCache();
descriptor.setInvalidateQueryCache(invalidateQueryCache.region());
}
}
}
Set<NamedQuery> namedQueries = AnnotationUtil.findAnnotationsRecursive(cls, NamedQuery.class);
for (NamedQuery namedQuery : namedQueries) {
for (NamedQuery namedQuery : AnnotationUtil.findAnnotationsRecursive(cls, NamedQuery.class)) {
descriptor.addNamedQuery(namedQuery.name(), namedQuery.query());
}
}
@@ -9,20 +9,38 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.id.IdBinder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* In a collection of Id values.
*/
public class IdInExpression extends NonPrepareExpression {
private final Collection<?> idCollection;
private final List<Object> idCollection;
private boolean multiValueIdSupported;
public IdInExpression(Collection<?> idCollection) {
this.idCollection = idCollection;
this.idCollection = new ArrayList<>(idCollection);
}
/**
* Return the ids this expression is looking to fetch.
*/
public Collection<Object> idValues() {
return idCollection;
}
/**
* Remove Ids that where obtained from l2 cache. Don't fetch these from DB.
*/
public int removeIds(Set<Object> hitIds) {
idCollection.removeAll(hitIds);
return idCollection.size();
}
@Override
@@ -63,23 +63,25 @@ class InExpression extends AbstractExpression {
return vals;
}
private List<Object> initBindValues() {
if (bindValues == null) {
bindValues = values();
}
return bindValues;
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache for NOT IN or when "empty"
if (not || empty) {
return false;
}
List<Object> copy = data.matchIn(propName, bindValues);
if (copy == null) {
return false;
}
bindValues = copy;
return true;
return data.matchIn(propName, initBindValues());
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
bindValues = values();
initBindValues();
if (bindValues.size() > 0) {
multiValueSupported = request.isMultiValueSupported((bindValues.get(0)).getClass());
}
@@ -146,9 +148,7 @@ class InExpression extends AbstractExpression {
if (prop != null) {
request.append(prop.getAssocIdInExpr(propName));
String inClause = prop.getAssocIdInValueExpr(not, bindValues.size());
request.append(inClause);
request.append(prop.getAssocIdInValueExpr(not, bindValues.size()));
} else {
request.append(propName);
request.appendInExpression(not, bindValues);
@@ -54,7 +54,6 @@ class InPairsExpression extends AbstractExpression {
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
// at this stage translating pairs into varchar via DB concat
multiValueSupported = request.isMultiValueSupported(String.class);
}
@@ -60,7 +60,7 @@ class CQueryBindCapture {
DQueryPlanOutput queryPlan = planLogger.logQueryPlan(request.getConnection(), cQueryPlan, last);
if (queryPlan != null) {
queryPlan.with(queryTimeMicros, captureCount, cQueryPlan.getPlanKey().toString());
queryPlan.with(queryTimeMicros, captureCount, cQueryPlan.getSqlHash());
request.process(queryPlan);
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetaQueryPlan;
/**
@@ -9,29 +10,28 @@ class DQueryPlanOutput implements MetaQueryPlan {
private final Class<?> beanType;
private final String label;
private final ProfileLocation profileLocation;
private final String sql;
private final String bind;
private final String plan;
private String planHash;
private String sqlHash;
private long queryTimeMicros;
private long captureCount;
DQueryPlanOutput(Class<?> beanType, String label, String sql, String bind, String plan) {
DQueryPlanOutput(Class<?> beanType, String label, String sql, String bind, String plan, ProfileLocation profileLocation) {
this.beanType = beanType;
this.label = label;
this.sql = sql;
this.bind = bind;
this.plan = plan;
this.profileLocation = profileLocation;
}
@Override
public String getQueryPlanHash() {
return planHash;
return sqlHash;
}
/**
@@ -94,15 +94,15 @@ class DQueryPlanOutput implements MetaQueryPlan {
@Override
public String toString() {
return " BeanType:" + ((beanType == null) ? "" : beanType.getSimpleName()) + " planHash:" + planHash + " label:" + label + " queryTimeMicros:" + queryTimeMicros + " captureCount:" + captureCount + "\n SQL:" + sql + "\nBIND:" + bind + "\nPLAN:" + plan;
return " BeanType:" + ((beanType == null) ? "" : beanType.getSimpleName()) + " planHash:" + sqlHash + " label:" + label + " queryTimeMicros:" + queryTimeMicros + " captureCount:" + captureCount + "\n SQL:" + sql + "\nBIND:" + bind + "\nPLAN:" + plan;
}
/**
* Additionally set the query execution time and the number of bind captures.
*/
void with(long queryTimeMicros, long captureCount, String planHash) {
void with(long queryTimeMicros, long captureCount, String sqlHash) {
this.queryTimeMicros = queryTimeMicros;
this.captureCount = captureCount;
this.planHash = planHash;
this.sqlHash = sqlHash;
}
}
@@ -26,7 +26,7 @@ public abstract class QueryPlanLogger {
}
protected DQueryPlanOutput createPlan(CQueryPlan plan, String bind, String planString) {
return new DQueryPlanOutput(plan.getBeanType(), plan.getName(), plan.getSql(), bind, planString);
return new DQueryPlanOutput(plan.getBeanType(), plan.getName(), plan.getSql(), bind, planString, plan.getProfileLocation());
}
DQueryPlanOutput readQueryPlanBasic(CQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
@@ -33,6 +33,7 @@ import io.ebean.event.readaudit.ReadEvent;
import io.ebean.plugin.BeanType;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.CacheIdLookup;
import io.ebeaninternal.api.HashQuery;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
@@ -50,6 +51,7 @@ import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.expression.DefaultExpressionList;
import io.ebeaninternal.server.expression.IdInExpression;
import io.ebeaninternal.server.expression.SimpleExpression;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.query.NativeSqlQueryPlanKey;
@@ -733,6 +735,21 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return !manualId && !distinct && !singleAttribute;
}
@Override
public CacheIdLookup<T> cacheIdLookup() {
if (whereExpressions == null) {
return null;
}
List<SpiExpression> underlyingList = whereExpressions.getUnderlyingList();
if (underlyingList.size() == 1) {
SpiExpression singleExpression = underlyingList.get(0);
if (singleExpression instanceof IdInExpression) {
return new CacheIdLookup<>((IdInExpression)singleExpression);
}
}
return null;
}
@Override
public NaturalKeyQueryData<T> naturalKey() {
@@ -1848,8 +1865,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
@Override
public void checkNamedParameters() {
if (namedParams != null) {
Collection<ONamedParam> values = namedParams.values();
for (ONamedParam value : values) {
for (ONamedParam value : namedParams.values()) {
value.checkValueSet();
}
}
+1 -1
View File
@@ -48,7 +48,7 @@ public abstract class BaseTestCase {
DB_CLOCK_DELTA = 100;
}
logger.debug("... preStart");
if (!AgentLoader.loadAgentFromClasspath("ebean-agent", "debug=1")) {
if (!AgentLoader.loadAgentByMainClass("io.ebean.enhance.Transformer", "debug=1")) {
logger.info("avaje-ebeanorm-agent not found in classpath - not dynamically loaded");
}
try {
@@ -0,0 +1,48 @@
package io.ebean.cache;
import io.ebean.DB;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ServerCacheManagerTest {
@Test
public void test() {
ServerCacheManager cacheManager = DB.getDefault().getServerCacheManager();
for (ServerCacheRegion region : cacheManager.allRegions()) {
assertTrue(region.isEnabled());
}
cacheManager.setEnabledRegions("r0,doesNotExist");
assertThat(cacheManager.getRegion("email").isEnabled()).isFalse();
assertThat(cacheManager.getRegion("r0").isEnabled()).isTrue();
cacheManager.setEnabledRegions("r0");
assertThat(cacheManager.getRegion("email").isEnabled()).isFalse();
assertThat(cacheManager.getRegion("r0").isEnabled()).isTrue();
cacheManager.setEnabledRegions(null);
assertThat(cacheManager.getRegion("email").isEnabled()).isFalse();
assertThat(cacheManager.getRegion("r0").isEnabled()).isTrue();
cacheManager.setEnabledRegions("email");
assertThat(cacheManager.getRegion("email").isEnabled()).isTrue();
assertThat(cacheManager.getRegion("r0").isEnabled()).isFalse();
cacheManager.setAllRegionsEnabled(false);
for (ServerCacheRegion region : cacheManager.allRegions()) {
assertFalse(region.isEnabled());
}
cacheManager.setAllRegionsEnabled(true);
for (ServerCacheRegion region : cacheManager.allRegions()) {
assertTrue(region.isEnabled());
}
}
}
@@ -68,6 +68,7 @@ public class ServerConfigTest {
props.setProperty("mappingLocations", "classpath:/foo;bar");
props.setProperty("namingConvention", "io.ebean.config.MatchingNamingConvention");
props.setProperty("idGeneratorAutomatic", "true");
props.setProperty("enabledL2Regions", "r0,users,orgs");
serverConfig.loadFromProperties(props);
@@ -85,6 +86,7 @@ public class ServerConfigTest {
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
assertEquals(PlatformConfig.DbUuid.BINARY, serverConfig.getPlatformConfig().getDbUuid());
assertEquals(JsonConfig.DateTime.ISO8601, serverConfig.getJsonDateTime());
assertEquals("r0,users,orgs", serverConfig.getEnabledL2Regions());
assertEquals(42, serverConfig.getJdbcFetchSizeFindEach());
assertEquals(43, serverConfig.getJdbcFetchSizeFindList());
@@ -111,6 +113,9 @@ public class ServerConfigTest {
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
serverConfig.setEnabledL2Regions("r0,orgs");
assertEquals("r0,orgs", serverConfig.getEnabledL2Regions());
}
@Test
+107
View File
@@ -1,13 +1,21 @@
package org.tests.cache;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.Ebean;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheStatistics;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tests.model.basic.Country;
import org.tests.model.basic.OCachedBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
@@ -17,6 +25,10 @@ import static org.junit.Assert.assertNotNull;
*/
public class TestBeanCache extends BaseTestCase {
private static final Logger log = LoggerFactory.getLogger(TestBeanCache.class);
private ServerCache beanCache = DB.getDefault().getServerCacheManager().getBeanCache(OCachedBean.class);
@Test
public void findById_when_idTypeConverted() {
@@ -40,7 +52,102 @@ public class TestBeanCache extends BaseTestCase {
sql = LoggedSqlCollector.stop();
assertNotNull(bean2);
assertThat(sql).isEmpty();
}
@Test
public void idsInExpression() {
List<OCachedBean> beans = createBeans();
List<Long> ids = beans.stream().map(OCachedBean::getId).collect(Collectors.toList());
beanCache.clear();
beanCache.getStatistics(true);
LoggedSqlCollector.start();
log.info("All misses (0 of 3) ...");
List<OCachedBean> list = DB.find(OCachedBean.class)
.where().idIn(ids)
.setUseCache(true)
.findList();
assertThat(list).hasSize(3);
assertBeanCacheHitMiss(0, 3);
List<String> sql = LoggedSqlCollector.current();
assertThat(sql).hasSize(1);
if (isH2()) {
assertThat(sql.get(0)).contains("from o_cached_bean t0 where t0.id in (?, ?, ? )");
}
log.info("All hits (3 of 3) ...");
list = DB.find(OCachedBean.class)
.where().idIn(ids)
.setUseCache(true)
.findList();
assertBeanCacheHitMiss(3, 0);
assertThat(list).hasSize(3);
sql = LoggedSqlCollector.current();
assertThat(sql).hasSize(0); // no misses
// remove a bean so that we get a "partial" hit (2 out of 3 in cache)
beanCache.remove(beans.get(0).getId().toString());
log.info("Partial hits (2 of 3) ...");
list = DB.find(OCachedBean.class)
.where().idIn(ids)
.setUseCache(true)
.findList();
assertBeanCacheHitMiss(2, 1);
assertThat(list).hasSize(3);
sql = LoggedSqlCollector.current();
assertThat(sql).hasSize(1);
if (isH2()) {
// fetch the miss from DB
assertThat(sql.get(0)).contains("from o_cached_bean t0 where t0.id in (? )");
}
// remove beans so that we get a "partial" hit (1 out of 3 in cache)
beanCache.remove(beans.get(1).getId().toString());
beanCache.remove(beans.get(2).getId().toString());
log.info("Partial hits (1 of 3) ...");
list = DB.find(OCachedBean.class)
.where().idIn(ids)
.setUseCache(true)
.findList();
assertBeanCacheHitMiss(1, 2);
assertThat(list).hasSize(3);
sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
if (isH2()) {
// fetch the misses from DB
assertThat(sql.get(0)).contains("from o_cached_bean t0 where t0.id in (?, ? )");
}
}
private void assertBeanCacheHitMiss(int hitCount, int missCount) {
ServerCacheStatistics statistics = beanCache.getStatistics(true);
assertThat(statistics.getHitCount()).isEqualTo(hitCount);
assertThat(statistics.getMissCount()).isEqualTo(missCount);
}
private List<OCachedBean> createBeans() {
List<String> names = Arrays.asList("z0", "z1", "z2");
List<OCachedBean> beans = new ArrayList<>();
for (String name : names) {
OCachedBean bean = new OCachedBean();
bean.setName(name);
beans.add(bean);
}
DB.saveAll(beans);
return beans;
}
@Test
@@ -8,7 +8,7 @@ import javax.persistence.ManyToOne;
import javax.validation.constraints.Size;
@Entity
@Cache
@Cache(region = "email")
public class PersonCacheEmail {
@Id
@@ -7,7 +7,7 @@ import javax.persistence.Id;
import javax.validation.constraints.Size;
@Entity
@Cache
@Cache(region = "email")
public class PersonCacheInfo {
@Id
@@ -1,17 +1,31 @@
package org.tests.cache.personinfo;
import io.ebean.Ebean;
import io.ebean.DB;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheRegion;
import io.ebean.cache.ServerCacheStatistics;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue;
public class PersonCacheTests {
private static final Logger log = LoggerFactory.getLogger(PersonCacheTests.class);
private final List<Object> ids = Arrays.asList("E001", "E002", "E003");
private ServerCache beanCacheInfo = DB.getDefault().getServerCacheManager().getBeanCache(PersonCacheInfo.class);
private ServerCache beanCacheEmail = DB.getDefault().getServerCacheManager().getBeanCache(PersonCacheEmail.class);
private ServerCacheRegion region = DB.getDefault().getServerCacheManager().getRegion("email");
private void insert(int id, String email) {
PersonCacheInfo person = new PersonCacheInfo("P00" + id, "P00" + id);
@@ -19,8 +33,8 @@ public class PersonCacheTests {
PersonCacheEmail personEmail = new PersonCacheEmail("E00" + id, email);
personEmail.setPersonInfo(person);
Ebean.save(person);
Ebean.save(personEmail);
DB.save(person);
DB.save(personEmail);
}
private void addTestData() {
@@ -37,18 +51,15 @@ public class PersonCacheTests {
LoggedSqlCollector.start();
Ebean.find(PersonCacheInfo.class)
DB.find(PersonCacheInfo.class)
.select("personId") // do not fetch name
.setUseCache(true)
.findList();
ServerCache beanCache = Ebean.getDefaultServer().getServerCacheManager().getBeanCache(PersonCacheInfo.class);
beanCache.getStatistics(true);
List<Object> ids = Arrays.asList(new String[]{"E001", "E002", "E003"});
beanCacheInfo.getStatistics(true);
List<PersonCacheEmail> emailList =
Ebean.find(PersonCacheEmail.class)
DB.find(PersonCacheEmail.class)
.where().idIn(ids)
.setUseCache(true)
.findList();
@@ -57,23 +68,23 @@ public class PersonCacheTests {
for (PersonCacheEmail email : emailList) {
// get property that isn't in the cached data
assertThat(email.getPersonInfo().getName()).isNotNull();
System.out.println(email.getPersonInfo().getName());
}
List<String> sql = LoggedSqlCollector.current();
assertThat(sql).hasSize(3);
assertThat(beanCache.getStatistics(true).getHitCount()).isEqualTo(3);
assertThat(beanCacheInfo.getStatistics(true).getHitCount()).isEqualTo(3);
System.out.println("Fetch again ...");
// force cache misses on PersonCacheEmail
beanCacheEmail.clear();
log.info("Fetch again - cache missing on PersonCacheEmail and cache hits on PersonCacheInfo ...");
emailList =
Ebean.find(PersonCacheEmail.class)
DB.find(PersonCacheEmail.class)
.where().idIn(ids)
.setUseCache(true)
.findList();
for (PersonCacheEmail email : emailList) {
// get property but it is in the cache data now
System.out.println(email.getPersonInfo().getName());
@@ -83,7 +94,45 @@ public class PersonCacheTests {
sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(beanCache.getStatistics(true).getHitCount()).isEqualTo(3);
assertThat(beanCacheInfo.getStatistics(true).getHitCount()).isEqualTo(3);
turnOffRegion_expect_noCacheUse();
}
private void turnOffRegion_expect_noCacheUse() {
assertTrue(region.isEnabled());
beanCacheEmail.getStatistics(true);
log.info("Disabled region ...");
region.setEnabled(false);
for (Object id : ids) {
DB.find(PersonCacheEmail.class)
.where().idEq(id)
.setUseCache(true)
.findOne();
}
// assert that we didn't hit the cache
ServerCacheStatistics statistics = beanCacheEmail.getStatistics(true);
assertThat(statistics.getHitCount()).isEqualTo(0);
assertThat(statistics.getMissCount()).isEqualTo(0);
log.info("Enabled region ...");
region.setEnabled(true);
for (Object id : ids) {
DB.find(PersonCacheEmail.class)
.where().idEq(id)
.setUseCache(true)
.findOne();
}
// assert that we DID hit the cache
statistics = beanCacheEmail.getStatistics(true);
assertThat(statistics.getHitCount()).isEqualTo(3);
assertThat(statistics.getMissCount()).isEqualTo(0);
}
}
@@ -8,6 +8,8 @@ import io.ebean.cache.ServerCacheManager;
import io.ebean.cache.ServerCacheStatistics;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
@@ -17,6 +19,8 @@ import static org.assertj.core.api.Assertions.assertThat;
public class TestCacheViaComplexNaturalKey extends BaseTestCase {
private static final Logger log = LoggerFactory.getLogger(TestCacheViaComplexNaturalKey.class);
private ServerCache beanCache = cacheManager().getBeanCache(OCachedNatKeyBean.class);
private ServerCache natKeyCache = cacheManager().getNaturalKeyCache(OCachedNatKeyBean.class);
@@ -29,11 +33,8 @@ public class TestCacheViaComplexNaturalKey extends BaseTestCase {
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) {
for (String store : Arrays.asList("abc", "def")) {
for (String sku : Arrays.asList("1", "2", "3", "4")) {
saveBean(store, sku);
}
}
@@ -92,10 +93,11 @@ public class TestCacheViaComplexNaturalKey extends BaseTestCase {
loadSomeIntoCache();
String storeId = "abc";
List<String> skus = new ArrayList<>(Arrays.asList("1", "2", "3"));
List<String> skus = Arrays.asList("1", "2", "3");
LoggedSqlCollector.start();
log.info("Partial (2 of 3) ...");
List<OCachedNatKeyBean> list = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", storeId)
@@ -104,7 +106,7 @@ public class TestCacheViaComplexNaturalKey extends BaseTestCase {
.orderBy("sku desc")
.findList();
List<String> sql = LoggedSqlCollector.stop();
List<String> sql = LoggedSqlCollector.current();
assertThat(sql).hasSize(1);
if (isH2()) {
// in clause with only 1 bind param - (sku=3 ... we got hits on sku 1 and 2)
@@ -112,9 +114,45 @@ public class TestCacheViaComplexNaturalKey extends BaseTestCase {
}
assertThat(list).hasSize(3);
assertNaturalKeyHitMiss(2, 1);
assertBeanCacheHitMiss(2, 0);
list = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", storeId)
.in("sku", skus)
.setUseCache(true)
.orderBy("sku desc")
.findList();
sql = LoggedSqlCollector.current();
assertThat(list).hasSize(3);
assertThat(sql).hasSize(0);
assertNaturalKeyHitMiss(3, 0);
assertBeanCacheHitMiss(3, 0);
natKeyCache.remove("abc;1;");
natKeyCache.remove("abc;3;");
log.info("Partial (1 of 3) ...");
list = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", storeId)
.in("sku", skus)
.setUseCache(true)
.orderBy("sku desc")
.findList();
sql = LoggedSqlCollector.stop();
assertThat(list).hasSize(3);
assertThat(sql).hasSize(1);
if (isH2()) {
assertThat(sql.get(0)).contains("where t0.store = ? and t0.sku in (?, ? ) order by t0.sku desc; --bind(abc,Array[2]={1,3})");
}
assertNaturalKeyHitMiss(1, 2);
assertBeanCacheHitMiss(1, 0);
}
@Test
+6 -5
View File
@@ -86,12 +86,13 @@
<logger name="io.ebean.TXN" level="TRACE"/>
<logger name="io.ebean.SUM" level="TRACE"/>
<!--<logger name="io.ebean.cache.TABLEMOD" level="TRACE"/>-->
<!-- <logger name="io.ebean.cache.REGION" level="TRACE"/>-->
<!-- <logger name="io.ebean.cache.TABLEMOD" level="TRACE"/>-->
<!--<logger name="io.ebean.cache.QUERY" level="TRACE"/>-->
<!--<logger name="io.ebean.cache.BEAN" level="TRACE"/>-->
<!--<logger name="io.ebean.cache.NATKEY" level="TRACE"/>-->
<!--<logger name="io.ebean.cache.COLL" level="TRACE"/>-->
<!-- <logger name="io.ebean.cache.QUERY" level="TRACE"/>-->
<!-- <logger name="io.ebean.cache.BEAN" level="TRACE"/>-->
<!-- <logger name="io.ebean.cache.NATKEY" level="TRACE"/>-->
<!-- <logger name="io.ebean.cache.COLL" level="TRACE"/>-->
<!--<logger name="io.ebeaninternal.server.deploy" level="TRACE"/>-->