mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1427 - QueryCache should be cleared, if one of a dependent bean is updated
Initial work, does not include propagation across cluster.
This commit is contained in:
+30
-19
@@ -17,22 +17,26 @@ public class CacheChangeSet {
|
||||
|
||||
private final List<CacheChange> entries = new ArrayList<>();
|
||||
|
||||
private final Set<String> touchedTables = new HashSet<>();
|
||||
|
||||
private final Set<BeanDescriptor<?>> queryCaches = new HashSet<>();
|
||||
|
||||
private final Map<ManyKey, ManyChange> manyChangeMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Set of "base tables" modified used to invalidate entities based on views.
|
||||
*/
|
||||
private final Set<String> viewInvalidation = new HashSet<>();
|
||||
|
||||
private final boolean viewEntityInvalidation;
|
||||
private final long modificationTimestamp;
|
||||
|
||||
/**
|
||||
* Construct specifying if we also need to process invalidation for entities based on views.
|
||||
*/
|
||||
public CacheChangeSet(boolean viewEntityInvalidation) {
|
||||
this.viewEntityInvalidation = viewEntityInvalidation;
|
||||
public CacheChangeSet(long modificationTimestamp) {
|
||||
this.modificationTimestamp = modificationTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the touched tables.
|
||||
*/
|
||||
public Set<String> touchedTables() {
|
||||
return touchedTables;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,7 +44,7 @@ public class CacheChangeSet {
|
||||
* <p>
|
||||
* Return the set of table changes to process invalidation for entities based on views.
|
||||
*/
|
||||
public Set<String> apply() {
|
||||
public void apply() {
|
||||
for (BeanDescriptor<?> entry : queryCaches) {
|
||||
entry.clearQueryCache();
|
||||
}
|
||||
@@ -50,7 +54,13 @@ public class CacheChangeSet {
|
||||
for (CacheChange entry : manyChangeMap.values()) {
|
||||
entry.apply();
|
||||
}
|
||||
return viewInvalidation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entry to clear a query cache.
|
||||
*/
|
||||
public void addInvalidate(BeanDescriptor<?> descriptor) {
|
||||
touchedTables.add(descriptor.getBaseTable());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,29 +95,23 @@ public class CacheChangeSet {
|
||||
* On bean insert register table for view based entity invalidation.
|
||||
*/
|
||||
public void addBeanInsert(String baseTable) {
|
||||
if (viewEntityInvalidation) {
|
||||
viewInvalidation.add(baseTable);
|
||||
}
|
||||
touchedTables.add(baseTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a bean from the cache.
|
||||
*/
|
||||
public <T> void addBeanRemove(BeanDescriptor<T> desc, Object id) {
|
||||
touchedTables.add(desc.getBaseTable());
|
||||
entries.add(new CacheChangeBeanRemove(desc, id));
|
||||
if (viewEntityInvalidation) {
|
||||
viewInvalidation.add(desc.getBaseTable());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a bean entry.
|
||||
*/
|
||||
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
touchedTables.add(desc.getBaseTable());
|
||||
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
|
||||
if (viewEntityInvalidation) {
|
||||
viewInvalidation.add(desc.getBaseTable());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,6 +129,13 @@ public class CacheChangeSet {
|
||||
return manyChangeMap.computeIfAbsent(key, ManyChange::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the modification timestamp for these changes.
|
||||
*/
|
||||
public long modificationTimestamp() {
|
||||
return modificationTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes for a specific many property.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.QueryCacheEntryValidate;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
@@ -19,6 +20,8 @@ public class CacheManagerOptions {
|
||||
|
||||
private CurrentTenantProvider currentTenantProvider;
|
||||
|
||||
private QueryCacheEntryValidate queryCacheEntryValidate;
|
||||
|
||||
private ServerCacheFactory cacheFactory = new DefaultServerCacheFactory();
|
||||
private ServerCacheOptions beanDefault = new ServerCacheOptions();
|
||||
private ServerCacheOptions queryDefault = new ServerCacheOptions();
|
||||
@@ -45,8 +48,9 @@ public class CacheManagerOptions {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CacheManagerOptions with(ServerCacheFactory cacheFactory) {
|
||||
public CacheManagerOptions with(ServerCacheFactory cacheFactory, QueryCacheEntryValidate queryCacheEntryValidate) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.queryCacheEntryValidate = queryCacheEntryValidate;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -82,4 +86,8 @@ public class CacheManagerOptions {
|
||||
public ClusterManager getClusterManager() {
|
||||
return clusterManager;
|
||||
}
|
||||
|
||||
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
|
||||
return queryCacheEntryValidate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.annotation.CacheBeanTuning;
|
||||
import io.ebean.annotation.CacheQueryTuning;
|
||||
import io.ebean.cache.QueryCacheEntryValidate;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheConfig;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
@@ -33,22 +35,14 @@ class DefaultCacheHolder {
|
||||
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
DefaultCacheHolder(CacheManagerOptions builder) {
|
||||
this(builder.getCacheFactory(), builder.getBeanDefault(), builder.getQueryDefault(), builder.getCurrentTenantProvider());
|
||||
}
|
||||
private final QueryCacheEntryValidate queryCacheEntryValidate;
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*
|
||||
* @param cacheFactory the factory for creating the cache
|
||||
* @param beanDefault the default options for tuning bean caches
|
||||
* @param queryDefault the default options for tuning query caches
|
||||
*/
|
||||
DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions beanDefault, ServerCacheOptions queryDefault, CurrentTenantProvider tenantProvider) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.beanDefault = beanDefault;
|
||||
this.queryDefault = queryDefault;
|
||||
this.tenantProvider = tenantProvider;
|
||||
DefaultCacheHolder(CacheManagerOptions builder) {
|
||||
this.cacheFactory = builder.getCacheFactory();
|
||||
this.beanDefault = builder.getBeanDefault();
|
||||
this.queryDefault = builder.getQueryDefault();
|
||||
this.tenantProvider = builder.getCurrentTenantProvider();
|
||||
this.queryCacheEntryValidate = builder.getQueryCacheEntryValidate();
|
||||
}
|
||||
|
||||
ServerCache getCache(Class<?> beanType, String cacheKey, ServerCacheType type) {
|
||||
@@ -76,7 +70,7 @@ class DefaultCacheHolder {
|
||||
collectIdCaches.computeIfAbsent(beanType.getName(), s -> new ConcurrentSkipListSet<>()).add(key);
|
||||
}
|
||||
}
|
||||
return cacheFactory.createCache(type, key, tenantProvider, options);
|
||||
return cacheFactory.createCache(new ServerCacheConfig(type, key, options, tenantProvider, queryCacheEntryValidate));
|
||||
}
|
||||
|
||||
void clearAll() {
|
||||
|
||||
@@ -2,10 +2,8 @@ package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheStatistics;
|
||||
import io.ebean.cache.TenantAwareKey;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -15,7 +13,6 @@ import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
@@ -65,47 +62,14 @@ public class DefaultServerCache implements ServerCache {
|
||||
|
||||
protected TenantAwareKey tenantAwareKey;
|
||||
|
||||
/**
|
||||
* Construct using a ConcurrentHashMap and cache options.
|
||||
*/
|
||||
public DefaultServerCache(String name, CurrentTenantProvider tenantProvider, ServerCacheOptions options) {
|
||||
this(name, new ConcurrentHashMap<>(), tenantProvider, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct passing in name, map and base eviction controls as ServerCacheOptions.
|
||||
*/
|
||||
public DefaultServerCache(String name, Map<Object, CacheEntry> map, CurrentTenantProvider tenantProvider, ServerCacheOptions options) {
|
||||
this(name, map, tenantProvider, options.getMaxSize(), options.getMaxIdleSecs(), options.getMaxSecsToLive(), options.getTrimFrequency());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct passing in name, map and base eviction controls.
|
||||
*/
|
||||
public DefaultServerCache(String name, Map<Object, CacheEntry> map, CurrentTenantProvider tenantProvider, int maxSize, int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
|
||||
this.name = name;
|
||||
this.map = map;
|
||||
this.maxSize = maxSize;
|
||||
this.tenantAwareKey = new TenantAwareKey(tenantProvider);
|
||||
this.maxIdleSecs = maxIdleSecs;
|
||||
this.maxSecsToLive = maxSecsToLive;
|
||||
this.trimFrequency = determineTrim(maxIdleSecs, maxSecsToLive, trimFrequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
|
||||
*/
|
||||
int determineTrim(int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
|
||||
if (trimFrequency > 0) {
|
||||
return trimFrequency;
|
||||
}
|
||||
if (maxIdleSecs > 0) {
|
||||
return maxIdleSecs / 2 - 1;
|
||||
}
|
||||
if (maxSecsToLive > 0) {
|
||||
return maxSecsToLive / 2 - 1;
|
||||
}
|
||||
return 0;
|
||||
public DefaultServerCache(DefaultServerCacheConfig config) {
|
||||
this.name = config.getName();
|
||||
this.map = config.getMap();
|
||||
this.maxSize = config.getMaxSize();
|
||||
this.tenantAwareKey = new TenantAwareKey(config.getTenantProvider());
|
||||
this.maxIdleSecs = config.getMaxIdleSecs();
|
||||
this.maxSecsToLive = config.getMaxSecsToLive();
|
||||
this.trimFrequency = config.determineTrimFrequency();
|
||||
}
|
||||
|
||||
public void periodicTrim(BackgroundExecutor executor) {
|
||||
@@ -193,7 +157,7 @@ public class DefaultServerCache implements ServerCache {
|
||||
/**
|
||||
* Return the tenant aware key.
|
||||
*/
|
||||
private Object key(Object id) {
|
||||
protected Object key(Object id) {
|
||||
return tenantAwareKey.key(id);
|
||||
}
|
||||
|
||||
@@ -203,7 +167,7 @@ public class DefaultServerCache implements ServerCache {
|
||||
@Override
|
||||
public Object get(Object id) {
|
||||
|
||||
CacheEntry entry = map.get(key(id));
|
||||
CacheEntry entry = getCacheEntry(id);
|
||||
if (entry == null) {
|
||||
missCount.increment();
|
||||
return null;
|
||||
@@ -212,10 +176,24 @@ public class DefaultServerCache implements ServerCache {
|
||||
// Important that hitCount.increment() MUST be low latency under concurrent
|
||||
// use hence must use LongAdder or better here
|
||||
hitCount.increment();
|
||||
return entry.getValue();
|
||||
return unwrapEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap the cache entry - override for query cache to unwrap to the query result.
|
||||
*/
|
||||
protected Object unwrapEntry(CacheEntry entry) {
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache entry - override for query cache to validate dependent tables.
|
||||
*/
|
||||
protected CacheEntry getCacheEntry(Object id) {
|
||||
return map.get(key(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<Object, Object> keyValues) {
|
||||
keyValues.forEach(this::put);
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.QueryCacheEntryValidate;
|
||||
import io.ebean.cache.ServerCacheConfig;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class DefaultServerCacheConfig {
|
||||
|
||||
private final ServerCacheConfig config;
|
||||
|
||||
private int maxSize;
|
||||
private int maxIdleSecs;
|
||||
private int maxSecsToLive;
|
||||
private int trimFrequency;
|
||||
|
||||
private Map<Object, DefaultServerCache.CacheEntry> map;
|
||||
|
||||
public DefaultServerCacheConfig(ServerCacheConfig config) {
|
||||
this(config, new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
public DefaultServerCacheConfig(ServerCacheConfig config, Map<Object, DefaultServerCache.CacheEntry> map) {
|
||||
this.config = config;
|
||||
this.map = map;
|
||||
|
||||
ServerCacheOptions options = config.getCacheOptions();
|
||||
this.maxIdleSecs = options.getMaxIdleSecs();
|
||||
this.maxSecsToLive = options.getMaxSecsToLive();
|
||||
this.trimFrequency = options.getTrimFrequency();
|
||||
this.maxSize = options.getMaxSize();
|
||||
}
|
||||
|
||||
public CurrentTenantProvider getTenantProvider() {
|
||||
return config.getTenantProvider();
|
||||
}
|
||||
|
||||
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
|
||||
return config.getQueryCacheEntryValidate();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return config.getCacheKey();
|
||||
}
|
||||
|
||||
public Map<Object, DefaultServerCache.CacheEntry> getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
public int getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
public int getMaxIdleSecs() {
|
||||
return maxIdleSecs;
|
||||
}
|
||||
|
||||
public int getMaxSecsToLive() {
|
||||
return maxSecsToLive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
|
||||
*/
|
||||
public int determineTrimFrequency() {
|
||||
if (trimFrequency > 0) {
|
||||
return trimFrequency;
|
||||
}
|
||||
if (maxIdleSecs > 0) {
|
||||
return maxIdleSecs / 2 - 1;
|
||||
}
|
||||
if (maxSecsToLive > 0) {
|
||||
return maxSecsToLive / 2 - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,8 @@ package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheConfig;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
|
||||
/**
|
||||
@@ -30,9 +28,15 @@ class DefaultServerCacheFactory implements ServerCacheFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCache createCache(ServerCacheType type, String cacheKey, CurrentTenantProvider tenantProvider, ServerCacheOptions cacheOptions) {
|
||||
public ServerCache createCache(ServerCacheConfig config) {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache(cacheKey, tenantProvider, cacheOptions);
|
||||
DefaultServerCache cache;
|
||||
if (config.isQueryCache()) {
|
||||
// use a server cache aware of extra validation and QueryCacheEntry
|
||||
cache = new DefaultServerQueryCache(new DefaultServerCacheConfig(config));
|
||||
} else {
|
||||
cache = new DefaultServerCache(new DefaultServerCacheConfig(config));
|
||||
}
|
||||
if (executor != null) {
|
||||
cache.periodicTrim(executor);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.QueryCacheEntry;
|
||||
import io.ebean.cache.QueryCacheEntryValidate;
|
||||
|
||||
/**
|
||||
* Server cache for query caching.
|
||||
* <p>
|
||||
* Entries in this cache contain QueryCacheEntry and we need to additionally
|
||||
* validate the entries when hit for changes to dependent tables.
|
||||
* </p>
|
||||
*/
|
||||
public class DefaultServerQueryCache extends DefaultServerCache {
|
||||
|
||||
private final QueryCacheEntryValidate queryCacheEntryValidate;
|
||||
|
||||
public DefaultServerQueryCache(DefaultServerCacheConfig config) {
|
||||
super(config);
|
||||
this.queryCacheEntryValidate = config.getQueryCacheEntryValidate();
|
||||
}
|
||||
|
||||
protected Object unwrapEntry(CacheEntry entry) {
|
||||
return ((QueryCacheEntry) entry.getValue()).getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheEntry getCacheEntry(Object id) {
|
||||
Object key = key(id);
|
||||
CacheEntry entry = map.get(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
QueryCacheEntry value = (QueryCacheEntry) entry.getValue();
|
||||
if (!queryCacheEntryValidate.isValid(value)) {
|
||||
map.remove(key);
|
||||
removeCount.increment();
|
||||
return null;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user