allRegions();
+
+ /**
+ * Set the regions that are enabled.
+ *
+ * Typically this is set on startup and at runtime (via dynamic configuration).
+ *
+ *
+ * @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.
- *
+ *
* 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.
- *
+ *
* 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.
*/
diff --git a/src/main/java/io/ebean/cache/ServerCacheRegion.java b/src/main/java/io/ebean/cache/ServerCacheRegion.java
new file mode 100644
index 000000000..b63668d91
--- /dev/null
+++ b/src/main/java/io/ebean/cache/ServerCacheRegion.java
@@ -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);
+
+}
diff --git a/src/main/java/io/ebean/config/ServerConfig.java b/src/main/java/io/ebean/config/ServerConfig.java
index 9efda763d..9b394a03e 100644
--- a/src/main/java/io/ebean/config/ServerConfig.java
+++ b/src/main/java/io/ebean/config/ServerConfig.java
@@ -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.
*/
diff --git a/src/main/java/io/ebeaninternal/api/CacheIdLookup.java b/src/main/java/io/ebeaninternal/api/CacheIdLookup.java
new file mode 100644
index 000000000..0ae791747
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/api/CacheIdLookup.java
@@ -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 {
+
+ 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 removeHits(BeanCacheResult cacheResult) {
+
+ Set hitIds = new HashSet<>();
+ List beans = new ArrayList<>(hitIds.size());
+
+ for (BeanCacheResult.Entry hit : cacheResult.hits()) {
+ hitIds.add(hit.getKey());
+ beans.add(hit.getBean());
+ }
+
+ this.remaining = idInExpression.removeIds(hitIds);
+ return beans;
+ }
+
+ public boolean allHits() {
+ return remaining == 0;
+ }
+}
diff --git a/src/main/java/io/ebeaninternal/api/NaturalKeyQueryData.java b/src/main/java/io/ebeaninternal/api/NaturalKeyQueryData.java
index 37ff3d2f2..30a1ff984 100644
--- a/src/main/java/io/ebeaninternal/api/NaturalKeyQueryData.java
+++ b/src/main/java/io/ebeaninternal/api/NaturalKeyQueryData.java
@@ -69,18 +69,18 @@ public class NaturalKeyQueryData {
/**
* Match for IN expression. We only allow one IN clause.
*/
- public List matchIn(String propName, List sourceValues) {
+ public boolean matchIn(String propName, List 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 {
this.hitCount = hits.size();
List beans = new ArrayList<>(hitCount);
-
for (BeanCacheResult.Entry 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);
+ }
+ }
}
diff --git a/src/main/java/io/ebeaninternal/api/SpiCacheControl.java b/src/main/java/io/ebeaninternal/api/SpiCacheControl.java
new file mode 100644
index 000000000..17e2fbe17
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/api/SpiCacheControl.java
@@ -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();
+
+}
diff --git a/src/main/java/io/ebeaninternal/api/SpiCacheRegion.java b/src/main/java/io/ebeaninternal/api/SpiCacheRegion.java
new file mode 100644
index 000000000..78595b110
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/api/SpiCacheRegion.java
@@ -0,0 +1,10 @@
+package io.ebeaninternal.api;
+
+import io.ebean.cache.ServerCacheRegion;
+
+/**
+ * Cache region can be enabled independently.
+ */
+public interface SpiCacheRegion extends ServerCacheRegion {
+
+}
diff --git a/src/main/java/io/ebeaninternal/api/SpiQuery.java b/src/main/java/io/ebeaninternal/api/SpiQuery.java
index 05f4c0e79..f162e950a 100644
--- a/src/main/java/io/ebeaninternal/api/SpiQuery.java
+++ b/src/main/java/io/ebeaninternal/api/SpiQuery.java
@@ -429,6 +429,11 @@ public interface SpiQuery extends Query, TxnProfileEventCodes {
*/
void resetBeanCacheAutoMode(boolean findOne);
+ /**
+ * Bean cache lookup for find by ids.
+ */
+ CacheIdLookup cacheIdLookup();
+
/**
* Collect natural key data for this query or null if the query does not match
* the requirements of natural key lookup.
diff --git a/src/main/java/io/ebeaninternal/server/cache/DefaultCacheAdapter.java b/src/main/java/io/ebeaninternal/server/cache/DefaultCacheAdapter.java
index 0c3cfa9e5..37e89b864 100644
--- a/src/main/java/io/ebeaninternal/server/cache/DefaultCacheAdapter.java
+++ b/src/main/java/io/ebeaninternal/server/cache/DefaultCacheAdapter.java
@@ -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 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);
diff --git a/src/main/java/io/ebeaninternal/server/cache/DefaultServerCacheManager.java b/src/main/java/io/ebeaninternal/server/cache/DefaultServerCacheManager.java
index bdb10d453..773846b79 100644
--- a/src/main/java/io/ebeaninternal/server/cache/DefaultServerCacheManager.java
+++ b/src/main/java/io/ebeaninternal/server/cache/DefaultServerCacheManager.java
@@ -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 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 allRegions() {
+ return new ArrayList<>(regionMap.values());
+ }
+
+ @Override
+ public void setEnabledRegions(String regions) {
+ if (regions != null) {
+ List enabledRegionNames = Arrays.asList(regions.split(","));
+
+ List disabled = new ArrayList<>();
+ List 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.
*/
diff --git a/src/main/java/io/ebeaninternal/server/cache/SpiCacheManager.java b/src/main/java/io/ebeaninternal/server/cache/SpiCacheManager.java
index 3e83203bd..5ef06b991 100644
--- a/src/main/java/io/ebeaninternal/server/cache/SpiCacheManager.java
+++ b/src/main/java/io/ebeaninternal/server/cache/SpiCacheManager.java
@@ -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 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.
*/
diff --git a/src/main/java/io/ebeaninternal/server/core/CacheOptions.java b/src/main/java/io/ebeaninternal/server/core/CacheOptions.java
index 24d441828..b2ef8073c 100644
--- a/src/main/java/io/ebeaninternal/server/core/CacheOptions.java
+++ b/src/main/java/io/ebeaninternal/server/core/CacheOptions.java
@@ -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;
}
/**
diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
index 4606d7fde..e1d35fcef 100644
--- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
@@ -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.
- *
- * If a transaction already exists that transaction will be used.
- *
- *
- * Note that the transaction is stored in a ThreadLocal variable.
- *
- */
@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.
- *
- * Note that the transaction is stored in a ThreadLocal variable.
- *
- */
@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.
- *
- * Useful to put in a finally block to ensure the transaction is ended, rather
- * than a rollbackTransaction() in each catch block.
- *
- *
- * Code example:
- *
- *
- * <code>
- * 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();
- * }
- * </code>
- *
- *
- *
- */
@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.
- *
- * Uses the BeanDescriptor deployment information to determine the sequence to
- * use.
- *
- */
@Override
public Object nextId(Class> beanType) {
BeanDescriptor> desc = getBeanDescriptor(beanType);
@@ -1133,14 +1071,21 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiOrmQueryRequest createQueryRequest(Type type, Query query, Transaction t) {
+ SpiOrmQueryRequest request = buildQueryRequest(type, query, t);
+ request.prepareQuery();
+ return request;
+ }
+
+ SpiOrmQueryRequest buildQueryRequest(Type type, Query query, Transaction t) {
+
SpiQuery spiQuery = (SpiQuery) query;
spiQuery.setType(type);
spiQuery.checkNamedParameters();
- return createQueryRequest(spiQuery, t);
+ return buildQueryRequest(spiQuery, t);
}
- private SpiOrmQueryRequest createQueryRequest(SpiQuery query, Transaction t) {
+ private SpiOrmQueryRequest buildQueryRequest(SpiQuery query, Transaction t) {
if (t == null) {
t = currentServerTransaction();
@@ -1163,10 +1108,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
query.setOrigin(createCallStack());
}
- OrmQueryRequest 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 request = createQueryRequest(spiQuery, t);
+ SpiOrmQueryRequest 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 List findList(Query query, Transaction t, boolean findOne) {
- SpiOrmQueryRequest request = createQueryRequest(Type.LIST, query, t);
+ SpiOrmQueryRequest 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) result;
}
- if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) {
- return request.getBeanCacheHits();
- }
if (request.isUseDocStore()) {
return docStore().findList(request);
}
diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
index af0f73443..7f6bb858f 100644
--- a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
@@ -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 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 extends BeanRequest implements SpiOrmQuery
// - merge the 2 results and return
//
+ CacheIdLookup idLookup = query.cacheIdLookup();
+ if (idLookup != null) {
+ BeanCacheResult 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;
}
diff --git a/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java
index d4195701b..81a0f520f 100644
--- a/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java
+++ b/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java
@@ -29,6 +29,11 @@ public interface SpiOrmQueryRequest extends BeanQueryRequest, DocQueryRequ
*/
BeanDescriptor getBeanDescriptor();
+ /**
+ * Prepare the query for execution.
+ */
+ void prepareQuery();
+
/**
* This will create a local (readOnly) transaction if no current transaction
* exists.
diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
index ed600488a..f190d5e3c 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -1490,6 +1490,10 @@ public class BeanDescriptor implements BeanType, STreeType {
return cacheHelp.beanCacheLoad(bean, ebi, cacheKey(id), context);
}
+ public BeanCacheResult cacheIdLookup(PersistenceContext context, Collection> ids) {
+ return cacheHelp.cacheIdLookup(context, ids);
+ }
+
/**
* Use natural key lookup to hit the bean cache.
*/
diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java
index 88c816040..398b4a31b 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java
@@ -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 {
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 {
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 {
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 {
* 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 {
/**
* 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 {
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 {
return new CachedManyIds(idList);
}
+ /**
+ * Hit the bean cache with the given ids returning the hits.
+ */
+ BeanCacheResult cacheIdLookup(PersistenceContext context, Collection> ids) {
+
+ Set keys = new HashSet<>(ids.size());
+ for (Object id : ids) {
+ keys.add(desc.cacheKey(id));
+ }
+
+ Map beanDataMap = beanCache.getAll(keys);
+ if (beanLog.isTraceEnabled()) {
+ beanLog.trace(" GET MANY {}({}) - hits:{}", cacheName, ids, beanDataMap.keySet());
+ }
+
+ BeanCacheResult result = new BeanCacheResult<>();
+ for (Map.Entry 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.
*/
diff --git a/src/main/java/io/ebeaninternal/server/deploy/DCacheControl.java b/src/main/java/io/ebeaninternal/server/deploy/DCacheControl.java
new file mode 100644
index 000000000..a99dabf89
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/server/deploy/DCacheControl.java
@@ -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();
+ }
+}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/DCacheControlNone.java b/src/main/java/io/ebeaninternal/server/deploy/DCacheControlNone.java
new file mode 100644
index 000000000..8f4797f47
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/server/deploy/DCacheControlNone.java
@@ -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;
+ }
+}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/DCacheRegion.java b/src/main/java/io/ebeaninternal/server/deploy/DCacheRegion.java
new file mode 100644
index 000000000..2056833b7
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/server/deploy/DCacheRegion.java
@@ -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;
+ }
+}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/DCacheRegionNone.java b/src/main/java/io/ebeaninternal/server/deploy/DCacheRegionNone.java
new file mode 100644
index 000000000..50c9ba33a
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/server/deploy/DCacheRegionNone.java
@@ -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 "";
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ throw new IllegalStateException("Not expected");
+ }
+}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
index b43b0d6fc..bf51ceea6 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
@@ -473,8 +473,8 @@ public class DeployBeanDescriptor {
/**
* 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);
}
/**
diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java
index 657c94797..a04b8db5e 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java
@@ -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 namedQueries = AnnotationUtil.findAnnotationsRecursive(cls, NamedQuery.class);
- for (NamedQuery namedQuery : namedQueries) {
+ for (NamedQuery namedQuery : AnnotationUtil.findAnnotationsRecursive(cls, NamedQuery.class)) {
descriptor.addNamedQuery(namedQuery.name(), namedQuery.query());
}
}
diff --git a/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java b/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java
index 533388153..e37e9d150 100644
--- a/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java
+++ b/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java
@@ -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 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 idValues() {
+ return idCollection;
+ }
+
+ /**
+ * Remove Ids that where obtained from l2 cache. Don't fetch these from DB.
+ */
+ public int removeIds(Set hitIds) {
+ idCollection.removeAll(hitIds);
+ return idCollection.size();
}
@Override
diff --git a/src/main/java/io/ebeaninternal/server/expression/InExpression.java b/src/main/java/io/ebeaninternal/server/expression/InExpression.java
index 656b32fe8..fa796f486 100644
--- a/src/main/java/io/ebeaninternal/server/expression/InExpression.java
+++ b/src/main/java/io/ebeaninternal/server/expression/InExpression.java
@@ -63,23 +63,25 @@ class InExpression extends AbstractExpression {
return vals;
}
+ private List 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 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);
diff --git a/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java b/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java
index 07dbc2945..3dbbd10a1 100644
--- a/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java
+++ b/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java
@@ -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);
}
diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryBindCapture.java b/src/main/java/io/ebeaninternal/server/query/CQueryBindCapture.java
index 644f236fe..90c4a1e72 100644
--- a/src/main/java/io/ebeaninternal/server/query/CQueryBindCapture.java
+++ b/src/main/java/io/ebeaninternal/server/query/CQueryBindCapture.java
@@ -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);
}
}
diff --git a/src/main/java/io/ebeaninternal/server/query/DQueryPlanOutput.java b/src/main/java/io/ebeaninternal/server/query/DQueryPlanOutput.java
index cb68aa7b2..4f7716ef3 100644
--- a/src/main/java/io/ebeaninternal/server/query/DQueryPlanOutput.java
+++ b/src/main/java/io/ebeaninternal/server/query/DQueryPlanOutput.java
@@ -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;
}
}
diff --git a/src/main/java/io/ebeaninternal/server/query/QueryPlanLogger.java b/src/main/java/io/ebeaninternal/server/query/QueryPlanLogger.java
index f3bbb267c..0455aaa5b 100644
--- a/src/main/java/io/ebeaninternal/server/query/QueryPlanLogger.java
+++ b/src/main/java/io/ebeaninternal/server/query/QueryPlanLogger.java
@@ -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 {
diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
index d79455f5d..e5e1639a7 100644
--- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -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 implements SpiQuery {
return !manualId && !distinct && !singleAttribute;
}
+ @Override
+ public CacheIdLookup cacheIdLookup() {
+ if (whereExpressions == null) {
+ return null;
+ }
+ List 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 naturalKey() {
@@ -1848,8 +1865,7 @@ public class DefaultOrmQuery implements SpiQuery {
@Override
public void checkNamedParameters() {
if (namedParams != null) {
- Collection values = namedParams.values();
- for (ONamedParam value : values) {
+ for (ONamedParam value : namedParams.values()) {
value.checkValueSet();
}
}
diff --git a/src/test/java/io/ebean/BaseTestCase.java b/src/test/java/io/ebean/BaseTestCase.java
index 17dc0efc1..ee10521e9 100644
--- a/src/test/java/io/ebean/BaseTestCase.java
+++ b/src/test/java/io/ebean/BaseTestCase.java
@@ -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 {
diff --git a/src/test/java/io/ebean/cache/ServerCacheManagerTest.java b/src/test/java/io/ebean/cache/ServerCacheManagerTest.java
new file mode 100644
index 000000000..337a80116
--- /dev/null
+++ b/src/test/java/io/ebean/cache/ServerCacheManagerTest.java
@@ -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());
+ }
+
+ }
+}
diff --git a/src/test/java/io/ebean/config/ServerConfigTest.java b/src/test/java/io/ebean/config/ServerConfigTest.java
index e5487b2b9..52965604d 100644
--- a/src/test/java/io/ebean/config/ServerConfigTest.java
+++ b/src/test/java/io/ebean/config/ServerConfigTest.java
@@ -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
diff --git a/src/test/java/org/tests/cache/TestBeanCache.java b/src/test/java/org/tests/cache/TestBeanCache.java
index ed9aae7ad..42af7612f 100644
--- a/src/test/java/org/tests/cache/TestBeanCache.java
+++ b/src/test/java/org/tests/cache/TestBeanCache.java
@@ -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 beans = createBeans();
+ List ids = beans.stream().map(OCachedBean::getId).collect(Collectors.toList());
+
+ beanCache.clear();
+ beanCache.getStatistics(true);
+
+ LoggedSqlCollector.start();
+
+ log.info("All misses (0 of 3) ...");
+ List list = DB.find(OCachedBean.class)
+ .where().idIn(ids)
+ .setUseCache(true)
+ .findList();
+
+ assertThat(list).hasSize(3);
+ assertBeanCacheHitMiss(0, 3);
+ List 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 createBeans() {
+
+ List names = Arrays.asList("z0", "z1", "z2");
+
+ List beans = new ArrayList<>();
+ for (String name : names) {
+ OCachedBean bean = new OCachedBean();
+ bean.setName(name);
+ beans.add(bean);
+ }
+
+ DB.saveAll(beans);
+
+ return beans;
}
@Test
diff --git a/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java b/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java
index e78463fe9..58ddea0a2 100644
--- a/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java
+++ b/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java
@@ -8,7 +8,7 @@ import javax.persistence.ManyToOne;
import javax.validation.constraints.Size;
@Entity
-@Cache
+@Cache(region = "email")
public class PersonCacheEmail {
@Id
diff --git a/src/test/java/org/tests/cache/personinfo/PersonCacheInfo.java b/src/test/java/org/tests/cache/personinfo/PersonCacheInfo.java
index bc24ce960..25d8e4d51 100644
--- a/src/test/java/org/tests/cache/personinfo/PersonCacheInfo.java
+++ b/src/test/java/org/tests/cache/personinfo/PersonCacheInfo.java
@@ -7,7 +7,7 @@ import javax.persistence.Id;
import javax.validation.constraints.Size;
@Entity
-@Cache
+@Cache(region = "email")
public class PersonCacheInfo {
@Id
diff --git a/src/test/java/org/tests/cache/personinfo/PersonCacheTests.java b/src/test/java/org/tests/cache/personinfo/PersonCacheTests.java
index 5cc192eca..20935a319 100644
--- a/src/test/java/org/tests/cache/personinfo/PersonCacheTests.java
+++ b/src/test/java/org/tests/cache/personinfo/PersonCacheTests.java
@@ -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 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 ids = Arrays.asList(new String[]{"E001", "E002", "E003"});
+ beanCacheInfo.getStatistics(true);
List 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 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);
+ }
}
diff --git a/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey.java b/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey.java
index d1a95de0b..c9352f5fc 100644
--- a/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey.java
+++ b/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey.java
@@ -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 stores = new ArrayList<>(Arrays.asList("abc", "def"));
- for (String store : stores) {
- List 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 skus = new ArrayList<>(Arrays.asList("1", "2", "3"));
+ List skus = Arrays.asList("1", "2", "3");
LoggedSqlCollector.start();
+ log.info("Partial (2 of 3) ...");
List list = Ebean.find(OCachedNatKeyBean.class)
.where()
.eq("store", storeId)
@@ -104,7 +106,7 @@ public class TestCacheViaComplexNaturalKey extends BaseTestCase {
.orderBy("sku desc")
.findList();
- List sql = LoggedSqlCollector.stop();
+ List 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
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
index 7236805d5..82935fc18 100644
--- a/src/test/resources/logback-test.xml
+++ b/src/test/resources/logback-test.xml
@@ -86,12 +86,13 @@
-
+
+
-
-
-
-
+
+
+
+