Repository: cayenne Updated Branches: refs/heads/querycache [created] 0a62b2be5
Added getQueryCache to the ObjectContext interface since this is already implemented by BaseContext anyway and makes accessing the cache much easier. Revised signature for QueryCache.remove(String) to be remove(QueryMetadata) to increase understandability. It was never clear how to use this method before. Added QueryCache.clearLocalCache method. This is now called automatically at the end of the request-response loop in StatelessContextRequestHandler and by BaseContext.finalize. This will prevent memory leaking from locally cached data in cases where the cache is not configured to expire entries based on time. Added QueryCache.debugListCacheKeys method to list all keys (prefixed by cache group) in the cache for debugging purposes. Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/be513314 Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/be513314 Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/be513314 Branch: refs/heads/querycache Commit: be51331499f8dbdb9c897db90602f491a711715f Parents: 15b7f69 Author: John Huss <johnth...@gmail.com> Authored: Tue May 1 17:02:56 2018 -0500 Committer: John Huss <johnth...@apache.org> Committed: Wed May 2 10:27:37 2018 -0500 ---------------------------------------------------------------------- .../apache/cayenne/jcache/JCacheQueryCache.java | 68 ++++++++++++++++---- .../java/org/apache/cayenne/BaseContext.java | 10 +++ .../java/org/apache/cayenne/ObjectContext.java | 3 + .../cayenne/access/DataContextQueryAction.java | 6 +- .../cayenne/access/DataDomainQueryAction.java | 2 +- .../org/apache/cayenne/cache/MapQueryCache.java | 44 +++++++++++-- .../apache/cayenne/cache/NestedQueryCache.java | 21 ++++-- .../org/apache/cayenne/cache/QueryCache.java | 21 ++++-- .../apache/cayenne/cache/MockQueryCache.java | 16 ++++- .../web/StatelessContextRequestHandler.java | 6 ++ 10 files changed, 164 insertions(+), 33 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java ---------------------------------------------------------------------- diff --git a/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java b/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java index a6d0e17..c7dcfe3 100644 --- a/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java +++ b/cayenne-jcache/src/main/java/org/apache/cayenne/jcache/JCacheQueryCache.java @@ -19,21 +19,26 @@ package org.apache.cayenne.jcache; -import org.apache.cayenne.cache.QueryCache; -import org.apache.cayenne.cache.QueryCacheEntryFactory; -import org.apache.cayenne.di.BeforeScopeEnd; -import org.apache.cayenne.di.Inject; -import org.apache.cayenne.query.QueryMetadata; - -import javax.cache.Cache; -import javax.cache.CacheException; -import javax.cache.CacheManager; +import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import javax.cache.Cache; +import javax.cache.Cache.Entry; +import javax.cache.CacheException; +import javax.cache.CacheManager; + +import org.apache.cayenne.cache.QueryCache; +import org.apache.cayenne.cache.QueryCacheEntryFactory; +import org.apache.cayenne.di.BeforeScopeEnd; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.query.QueryMetadata; + /** * @since 4.0 */ @@ -75,14 +80,16 @@ public class JCacheQueryCache implements QueryCache { } @Override - public void remove(String key) { - if (key != null) { - for (String cache : cacheManager.getCacheNames()) { - getCache(cache).remove(key); + public void remove(QueryMetadata metadata) { + if (metadata != null) { + Cache<String, List> cache = getCache(metadata.getCacheGroup()); + if (cache != null) { + String key = metadata.getCacheKey(); + cache.remove(key); } } } - + @Override public void removeGroup(String groupKey) { Cache<String, List> cache = getCache(groupKey); @@ -156,4 +163,37 @@ public class JCacheQueryCache implements QueryCache { public void shutdown() { cacheManager.close(); } + + @Override + public void clearLocalCache(Optional<String> namespace) { + if (!namespace.isPresent()) { + return; + } + + for (String name : cacheManager.getCacheNames()) { + Cache<String, List> cache = getCache(name); + Iterator<Entry<String, List>> it = cache.iterator(); + while (it.hasNext()) { + Entry<String, List> entry = (Entry<String, List>) it.next(); + if (entry.getKey().startsWith(namespace.get())) { + it.remove(); + } + } + } + } + + @Override + public List<String> debugListCacheKeys() { + List<String> result = new ArrayList<>(); + for (String name : cacheManager.getCacheNames()) { + Cache<String, List> cache = getCache(name); + Iterator<Entry<String, List>> it = cache.iterator(); + while (it.hasNext()) { + Entry<String, List> entry = (Entry<String, List>) it.next(); + result.add(name + "." + entry.getKey()); + } + } + return result; + } + } http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java b/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java index ac9cc7a..d38cce0 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/BaseContext.java @@ -48,6 +48,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; /** @@ -120,6 +121,14 @@ public abstract class BaseContext implements ObjectContext { graphAction = new ObjectContextGraphAction(this); } + @Override + protected void finalize() throws Throwable { + if (queryCache != null) { + queryCache.clearLocalCache(Optional.empty()); + } + super.finalize(); + } + /** * Checks whether this context is attached to Cayenne runtime stack and if * not, attempts to attach itself to the runtime using Injector returned @@ -469,6 +478,7 @@ public abstract class BaseContext implements ObjectContext { @Override public abstract Collection<?> uncommittedObjects(); + @Override public QueryCache getQueryCache() { attachToRuntimeIfNeeded(); return queryCache; http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java b/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java index dc535f8..d4ee643 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/ObjectContext.java @@ -19,6 +19,7 @@ package org.apache.cayenne; +import org.apache.cayenne.cache.QueryCache; import org.apache.cayenne.graph.GraphManager; import org.apache.cayenne.map.EntityResolver; import org.apache.cayenne.query.Query; @@ -261,6 +262,8 @@ public interface ObjectContext extends DataChannel, Serializable { */ DataChannel getChannel(); + QueryCache getQueryCache(); + /** * Returns <code>true</code> if there are any modified, deleted or new * objects registered with this ObjectContext, <code>false</code> otherwise. http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java index 87a7b80..1f53c9c 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java @@ -31,6 +31,7 @@ import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.query.EntityResultSegment; import org.apache.cayenne.query.ObjectIdQuery; import org.apache.cayenne.query.Query; +import org.apache.cayenne.query.QueryMetadata; import org.apache.cayenne.query.RefreshQuery; import org.apache.cayenne.util.ListResponse; import org.apache.cayenne.util.ObjectContextQueryAction; @@ -176,9 +177,8 @@ class DataContextQueryAction extends ObjectContextQueryAction { Query cachedQuery = refreshQuery.getQuery(); if (cachedQuery != null) { - String cacheKey = cachedQuery - .getMetaData(context.getEntityResolver()) - .getCacheKey(); + QueryMetadata cacheKey = cachedQuery + .getMetaData(context.getEntityResolver()); context.getQueryCache().remove(cacheKey); this.response = context.performGenericQuery(cachedQuery); http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java index a87de83..be7f180 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java @@ -344,7 +344,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { if (refreshQuery.getQuery() != null) { Query cachedQuery = refreshQuery.getQuery(); - String cacheKey = cachedQuery.getMetaData(context.getEntityResolver()).getCacheKey(); + QueryMetadata cacheKey = cachedQuery.getMetaData(context.getEntityResolver()); context.getQueryCache().remove(cacheKey); this.response = domain.onQuery(context, cachedQuery); http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java b/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java index e2fb64d..3f85328 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/cache/MapQueryCache.java @@ -19,8 +19,12 @@ package org.apache.cayenne.cache; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.cayenne.CayenneRuntimeException; @@ -97,15 +101,15 @@ public class MapQueryCache implements QueryCache, Serializable { } } - public void remove(String key) { + public void remove(QueryMetadata metadata) { + String key = metadata.getCacheKey(); if (key == null) { return; } - - for(Map<String, List<?>> map : cacheGroups.values()) { - synchronized (map) { - map.remove(key); - } + + Map<String, List<?>> map = createIfAbsent(metadata); + synchronized (map) { + map.remove(key); } } @@ -172,4 +176,32 @@ public class MapQueryCache implements QueryCache, Serializable { // no explicit cache group return DEFAULT_CACHE_NAME; } + + @Override + public void clearLocalCache(Optional<String> namespace) { + if (!namespace.isPresent()) { + return; + } + + for (Entry<String, Map<String, List<?>>> cacheGroup : cacheGroups.entrySet()) { + Set<String> keys = cacheGroup.getValue().keySet(); + for (String key : keys) { + if (key.startsWith(namespace.get())) { + keys.remove(key); + } + } + } + } + + @Override + public List<String> debugListCacheKeys() { + List<String> result = new ArrayList<>(); + for (Entry<String, Map<String, List<?>>> cacheGroup : cacheGroups.entrySet()) { + for (String key : cacheGroup.getValue().keySet()) { + result.add(cacheGroup.getKey() + "." + key); + } + } + return result; + } + } http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java b/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java index 7f50e5d..7dd3236 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java @@ -18,11 +18,12 @@ ****************************************************************/ package org.apache.cayenne.cache; +import java.util.List; +import java.util.Optional; + import org.apache.cayenne.query.QueryMetadata; import org.apache.cayenne.query.QueryMetadataProxy; -import java.util.List; - /** * A {@link QueryCache} wrapper that introduces a key namespace on top of a * delegate shared cache. This way multiple cache users can share the same @@ -97,8 +98,8 @@ public class NestedQueryCache implements QueryCache { * Removes an entry for key in the current namespace. */ @Override - public void remove(String key) { - delegate.remove(qualifiedKey(key)); + public void remove(QueryMetadata metadata) { + delegate.remove(qualifiedMetadata(metadata)); } /** @@ -126,4 +127,16 @@ public class NestedQueryCache implements QueryCache { } }; } + + @Override + public void clearLocalCache(Optional<String> namespace) { + // ignore passed in namespace + delegate.clearLocalCache(Optional.of(this.namespace)); + } + + @Override + public List<String> debugListCacheKeys() { + return delegate.debugListCacheKeys(); + } + } http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java b/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java index de74ea6..8aa242a 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/cache/QueryCache.java @@ -21,6 +21,7 @@ package org.apache.cayenne.cache; import org.apache.cayenne.query.QueryMetadata; import java.util.List; +import java.util.Optional; /** * Defines API of a cache that stores query results. @@ -29,6 +30,7 @@ import java.util.List; */ public interface QueryCache { + /** * Returns a cached query result for the given QueryMetadata or null if the result is * not cached or is expired. @@ -51,11 +53,13 @@ public interface QueryCache { @SuppressWarnings("rawtypes") void put(QueryMetadata metadata, List results); - + /** - * Removes a single entry from cache. + * Removes a single query result from the cache that matches the given metadata. + * The metadata can be obtained like so: + * <pre>query.getMetaData(context.getEntityResolver())</pre> */ - void remove(String key); + void remove(QueryMetadata metadata); /** * Removes a group of entries identified by group key. Note that depending on @@ -65,7 +69,7 @@ public interface QueryCache { * will not change after calling this method. */ void removeGroup(String groupKey); - + /** * Removes a group of entries identified by group key. * Can be used if cache provider supports strictly typed caches. @@ -82,4 +86,13 @@ public interface QueryCache { */ @Deprecated void clear(); + + /** + * Clears all entries in the LOCAL cache. + * @param namespace this parameter is for internal use only - client should always pass Optional.empty() + */ + void clearLocalCache(Optional<String> namespace); + + List<String> debugListCacheKeys(); + } http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java b/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java index 0705a60..7ed263c 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/cache/MockQueryCache.java @@ -18,8 +18,12 @@ ****************************************************************/ package org.apache.cayenne.cache; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Optional; +import org.apache.cayenne.query.QueryCacheStrategy; import org.apache.cayenne.query.QueryMetadata; public class MockQueryCache implements QueryCache { @@ -38,7 +42,7 @@ public class MockQueryCache implements QueryCache { public void put(QueryMetadata metadata, List results) { } - public void remove(String key) { + public void remove(QueryMetadata metadata) { } public void removeGroup(String groupKey) { @@ -51,4 +55,14 @@ public class MockQueryCache implements QueryCache { public int size() { return 0; } + + @Override + public void clearLocalCache(Optional<String> namespace) { + } + + @Override + public List<String> debugListCacheKeys() { + return Collections.emptyList(); + } + } http://git-wip-us.apache.org/repos/asf/cayenne/blob/be513314/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java ---------------------------------------------------------------------- diff --git a/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java b/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java index 0e7651e..66e8768 100644 --- a/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java +++ b/cayenne-web/src/main/java/org/apache/cayenne/configuration/web/StatelessContextRequestHandler.java @@ -18,6 +18,8 @@ ****************************************************************/ package org.apache.cayenne.configuration.web; +import java.util.Optional; + import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -67,6 +69,10 @@ public class StatelessContextRequestHandler implements RequestHandler { public void requestEnd(ServletRequest request, ServletResponse response) { CayenneRuntime.bindThreadInjector(null); + ObjectContext context = BaseContext.getThreadObjectContext(); + if (context != null) { + ((BaseContext)context).getQueryCache().clearLocalCache(Optional.empty()); + } BaseContext.bindThreadObjectContext(null); }