mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-21 06:41:12 +00:00
* Add tenant-partitioned L2 caches (#2956) When `tenantPartitionedCache` is enabled, each tenant gets its own cache namespace (keys include the tenant id), improving cache-hit ratio by preventing cross-tenant key collisions. Refactor BeanDescriptorCacheHelp to abstract base class with two concrete subclasses: - BeanDescriptorCacheHelpFixed - static cache refs (original behaviour) - BeanDescriptorCacheHelpPartitioned - per-request Supplier<> lookup so the correct tenant-scoped cache is resolved on each access Fix background-thread invalidation: clear(name) in partitioned mode now scans all matching cache entries by key prefix rather than calling tenantProvider.currentId() which is null/wrong on background executor threads. Add SpiCacheManager.clearTenant(tenantId) to allow removal of all cache entries for a deactivated tenant, preventing unbounded memory growth in long-running multi-tenant deployments. Also load cache settings (cacheMaxSize, cacheMaxIdleTime, etc.) from application properties - these were previously missing from loadSettings(). * Fix versions in test-java16 --------- Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
This commit is contained in:
co-authored by
robin.bygrave
parent
3d2cc2d4af
commit
66bcfd107d
@@ -887,6 +887,13 @@ public interface DatabaseBuilder {
|
||||
@Deprecated
|
||||
DatabaseBuilder setBackgroundExecutorWrapper(BackgroundExecutorWrapper backgroundExecutorWrapper);
|
||||
|
||||
/**
|
||||
* Enable tenant-partitioned caches. When enabled each tenant gets its own cache namespace,
|
||||
* improving cache-hit ratio by preventing cross-tenant key collisions.
|
||||
* Use {@link SpiCacheManager#clearTenant(Object)} when a tenant is deactivated.
|
||||
*/
|
||||
DatabaseBuilder tenantPartitionedCache(boolean tenantPartitionedCache);
|
||||
|
||||
/**
|
||||
* Set the L2 cache default max size.
|
||||
*/
|
||||
@@ -2567,6 +2574,11 @@ public interface DatabaseBuilder {
|
||||
*/
|
||||
boolean isAutoPersistUpdates();
|
||||
|
||||
/**
|
||||
* Return true if caches are partitioned by tenant.
|
||||
*/
|
||||
boolean isTenantPartitionedCache();
|
||||
|
||||
/**
|
||||
* Return the L2 cache default max size.
|
||||
*/
|
||||
|
||||
@@ -432,6 +432,8 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
private int backgroundExecutorShutdownSecs = 30;
|
||||
private BackgroundExecutorWrapper backgroundExecutorWrapper = new MdcBackgroundExecutorWrapper();
|
||||
|
||||
private boolean tenantPartitionedCache;
|
||||
|
||||
// defaults for the L2 bean caching
|
||||
|
||||
private int cacheMaxSize = 10000;
|
||||
@@ -1175,6 +1177,17 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
return cacheMaxSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTenantPartitionedCache() {
|
||||
return tenantPartitionedCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseConfig tenantPartitionedCache(boolean tenantPartitionedCache) {
|
||||
this.tenantPartitionedCache = tenantPartitionedCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseConfig setCacheMaxSize(int cacheMaxSize) {
|
||||
this.cacheMaxSize = cacheMaxSize;
|
||||
@@ -2228,6 +2241,15 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
ddlPlaceholders = p.get("ddl.placeholders", ddlPlaceholders);
|
||||
ddlHeader = p.get("ddl.header", ddlHeader);
|
||||
|
||||
tenantPartitionedCache = p.getBoolean("tenantPartitionedCache", tenantPartitionedCache);
|
||||
|
||||
cacheMaxSize = p.getInt("cacheMaxSize", cacheMaxSize);
|
||||
cacheMaxIdleTime = p.getInt("cacheMaxIdleTime", cacheMaxIdleTime);
|
||||
cacheMaxTimeToLive = p.getInt("cacheMaxTimeToLive", cacheMaxTimeToLive);
|
||||
queryCacheMaxSize = p.getInt("queryCacheMaxSize", queryCacheMaxSize);
|
||||
queryCacheMaxIdleTime = p.getInt("queryCacheMaxIdleTime", queryCacheMaxIdleTime);
|
||||
queryCacheMaxTimeToLive = p.getInt("queryCacheMaxTimeToLive", queryCacheMaxTimeToLive);
|
||||
|
||||
// read tenant-configuration from config:
|
||||
// tenant.mode = NONE | DB | SCHEMA | CATALOG | PARTITION
|
||||
String mode = p.get("tenant.mode");
|
||||
|
||||
+11
-4
@@ -13,8 +13,9 @@ import io.ebeaninternal.server.cluster.ClusterManager;
|
||||
public final class CacheManagerOptions {
|
||||
|
||||
private final ClusterManager clusterManager;
|
||||
private final DatabaseBuilder.Settings databaseBuilder;
|
||||
private final String serverName;
|
||||
private final boolean localL2Caching;
|
||||
private final boolean tenantPartitionedCache;
|
||||
private CurrentTenantProvider currentTenantProvider;
|
||||
private QueryCacheEntryValidate queryCacheEntryValidate;
|
||||
private ServerCacheFactory cacheFactory = new DefaultServerCacheFactory();
|
||||
@@ -24,7 +25,8 @@ public final class CacheManagerOptions {
|
||||
CacheManagerOptions() {
|
||||
this.localL2Caching = true;
|
||||
this.clusterManager = null;
|
||||
this.databaseBuilder = null;
|
||||
this.serverName = "db";
|
||||
this.tenantPartitionedCache = false;
|
||||
this.cacheFactory = new DefaultServerCacheFactory();
|
||||
this.beanDefault = new ServerCacheOptions();
|
||||
this.queryDefault = new ServerCacheOptions();
|
||||
@@ -32,9 +34,10 @@ public final class CacheManagerOptions {
|
||||
|
||||
public CacheManagerOptions(ClusterManager clusterManager, DatabaseBuilder.Settings config, boolean localL2Caching) {
|
||||
this.clusterManager = clusterManager;
|
||||
this.databaseBuilder = config;
|
||||
this.serverName = config.getName();
|
||||
this.localL2Caching = localL2Caching;
|
||||
this.currentTenantProvider = config.getCurrentTenantProvider();
|
||||
this.tenantPartitionedCache = config.isTenantPartitionedCache();
|
||||
}
|
||||
|
||||
public CacheManagerOptions with(ServerCacheOptions beanDefault, ServerCacheOptions queryDefault) {
|
||||
@@ -55,7 +58,7 @@ public final class CacheManagerOptions {
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return (databaseBuilder == null) ? "db" : databaseBuilder.getName();
|
||||
return serverName;
|
||||
}
|
||||
|
||||
public boolean isLocalL2Caching() {
|
||||
@@ -85,4 +88,8 @@ public final class CacheManagerOptions {
|
||||
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
|
||||
return queryCacheEntryValidate;
|
||||
}
|
||||
|
||||
public boolean isTenantPartitionedCache() {
|
||||
return tenantPartitionedCache;
|
||||
}
|
||||
}
|
||||
|
||||
+92
-13
@@ -9,6 +9,7 @@ import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.util.AnnotationUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
@@ -32,6 +33,7 @@ final class DefaultCacheHolder {
|
||||
private final ServerCacheOptions queryDefault;
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
private final QueryCacheEntryValidate queryCacheEntryValidate;
|
||||
private final boolean tenantPartitionedCache;
|
||||
|
||||
DefaultCacheHolder(CacheManagerOptions builder) {
|
||||
this.cacheFactory = builder.getCacheFactory();
|
||||
@@ -39,6 +41,7 @@ final class DefaultCacheHolder {
|
||||
this.queryDefault = builder.getQueryDefault();
|
||||
this.tenantProvider = builder.getCurrentTenantProvider();
|
||||
this.queryCacheEntryValidate = builder.getQueryCacheEntryValidate();
|
||||
this.tenantPartitionedCache = builder.isTenantPartitionedCache();
|
||||
}
|
||||
|
||||
void visitMetrics(MetricVisitor visitor) {
|
||||
@@ -56,16 +59,31 @@ final class DefaultCacheHolder {
|
||||
return getCacheInternal(beanType, ServerCacheType.COLLECTION_IDS, collectionProperty);
|
||||
}
|
||||
|
||||
private String tenantKey(String beanName) {
|
||||
if (tenantPartitionedCache) {
|
||||
return beanName + '.' + tenantProvider.currentId();
|
||||
}
|
||||
return beanName;
|
||||
}
|
||||
|
||||
private String key(String beanName, ServerCacheType type) {
|
||||
if (tenantPartitionedCache) {
|
||||
return beanName + '.' + tenantProvider.currentId() + type.code();
|
||||
}
|
||||
return beanName + type.code();
|
||||
}
|
||||
|
||||
private String key(String beanName, String collectionProperty, ServerCacheType type) {
|
||||
if (collectionProperty != null) {
|
||||
return beanName + "." + collectionProperty + type.code();
|
||||
} else {
|
||||
return beanName + type.code();
|
||||
StringBuilder sb = new StringBuilder(beanName.length() + 64);
|
||||
sb.append(beanName);
|
||||
if (tenantPartitionedCache) {
|
||||
sb.append('.').append(tenantProvider.currentId());
|
||||
}
|
||||
if (collectionProperty != null) {
|
||||
sb.append('.').append(collectionProperty);
|
||||
}
|
||||
sb.append(type.code());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,12 +100,15 @@ final class DefaultCacheHolder {
|
||||
if (type == ServerCacheType.COLLECTION_IDS) {
|
||||
lock.lock();
|
||||
try {
|
||||
collectIdCaches.computeIfAbsent(beanType.getName(), s -> new ConcurrentSkipListSet<>()).add(key);
|
||||
collectIdCaches.computeIfAbsent(tenantKey(beanType.getName()), s -> new ConcurrentSkipListSet<>()).add(key);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
return cacheFactory.createCache(new ServerCacheConfig(type, key, shortName, options, tenantProvider, queryCacheEntryValidate));
|
||||
// in partitioned mode each ServerCache instance is already tenant-scoped via its key,
|
||||
// so the tenantProvider is not needed inside the cache itself
|
||||
CurrentTenantProvider cacheProvider = tenantPartitionedCache ? null : tenantProvider;
|
||||
return cacheFactory.createCache(new ServerCacheConfig(type, key, shortName, options, cacheProvider, queryCacheEntryValidate));
|
||||
}
|
||||
|
||||
void clearAll() {
|
||||
@@ -100,17 +121,75 @@ final class DefaultCacheHolder {
|
||||
|
||||
public void clear(String name) {
|
||||
log.log(DEBUG, "clear {0}", name);
|
||||
clearIfExists(key(name, ServerCacheType.QUERY));
|
||||
clearIfExists(key(name, ServerCacheType.BEAN));
|
||||
clearIfExists(key(name, ServerCacheType.NATURAL_KEY));
|
||||
Set<String> keys = collectIdCaches.get(name);
|
||||
if (keys != null) {
|
||||
for (String collectionIdKey : keys) {
|
||||
clearIfExists(collectionIdKey);
|
||||
if (tenantPartitionedCache) {
|
||||
// In partitioned mode, tenantProvider.currentId() may be null/wrong on a background
|
||||
// invalidation thread. Scan all cache entries for this entity type across all tenants.
|
||||
clearAllTenantsFor(name);
|
||||
} else {
|
||||
clearIfExists(key(name, ServerCacheType.QUERY));
|
||||
clearIfExists(key(name, ServerCacheType.BEAN));
|
||||
clearIfExists(key(name, ServerCacheType.NATURAL_KEY));
|
||||
Set<String> keys = collectIdCaches.get(name);
|
||||
if (keys != null) {
|
||||
for (String collectionIdKey : keys) {
|
||||
clearIfExists(collectionIdKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearAllTenantsFor(String name) {
|
||||
// Keys in partitioned mode: beanName.tenantId_X or beanName.tenantId.prop_X
|
||||
// collectIdCaches keys: beanName.tenantId
|
||||
// Both start with beanName + '.' so we can use prefix scan.
|
||||
String prefix = name + '.';
|
||||
for (Map.Entry<String, ServerCache> entry : allCaches.entrySet()) {
|
||||
if (entry.getKey().startsWith(prefix)) {
|
||||
log.log(TRACE, "clear cache {0}", entry.getKey());
|
||||
entry.getValue().clear();
|
||||
}
|
||||
}
|
||||
lock.lock();
|
||||
try {
|
||||
for (Map.Entry<String, Set<String>> entry : collectIdCaches.entrySet()) {
|
||||
if (entry.getKey().startsWith(prefix)) {
|
||||
for (String collectionIdKey : entry.getValue()) {
|
||||
clearIfExists(collectionIdKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all cache entries belonging to the given tenant.
|
||||
* Call this when a tenant is deactivated to prevent unbounded memory growth.
|
||||
*/
|
||||
void clearTenant(Object tenantId) {
|
||||
String tenantIdStr = String.valueOf(tenantId);
|
||||
// Keys look like: beanName.tenantId_X or beanName.tenantId.prop_X
|
||||
String segmentBeforeTypeCode = '.' + tenantIdStr + '_';
|
||||
String segmentBeforeProperty = '.' + tenantIdStr + '.';
|
||||
allCaches.entrySet().removeIf(e -> {
|
||||
String k = e.getKey();
|
||||
return k.contains(segmentBeforeTypeCode) || k.contains(segmentBeforeProperty);
|
||||
});
|
||||
// collectIdCaches keys look like: beanName.tenantId
|
||||
String collectKeySuffix = '.' + tenantIdStr;
|
||||
lock.lock();
|
||||
try {
|
||||
collectIdCaches.entrySet().removeIf(e -> e.getKey().endsWith(collectKeySuffix));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
boolean isTenantPartitionedCache() {
|
||||
return tenantPartitionedCache;
|
||||
}
|
||||
|
||||
private void clearIfExists(String fullKey) {
|
||||
ServerCache cache = allCaches.get(fullKey);
|
||||
if (cache != null) {
|
||||
|
||||
+10
@@ -154,4 +154,14 @@ public final class DefaultServerCacheManager implements SpiCacheManager {
|
||||
return cacheHolder.getCache(beanType, ServerCacheType.BEAN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTenantPartitionedCache() {
|
||||
return cacheHolder.isTenantPartitionedCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTenant(Object tenantId) {
|
||||
cacheHolder.clearTenant(tenantId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -88,4 +88,17 @@ public interface SpiCacheManager {
|
||||
*/
|
||||
void clearLocal(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Returns true if this cache manager runs in tenant-partitioned mode.
|
||||
* In this mode caches are namespaced per tenant to improve cache-hit ratio.
|
||||
*/
|
||||
boolean isTenantPartitionedCache();
|
||||
|
||||
/**
|
||||
* Remove all cache entries belonging to the given tenant.
|
||||
* Call this when a tenant is deactivated to prevent unbounded memory growth
|
||||
* in tenant-partitioned mode.
|
||||
*/
|
||||
void clearTenant(Object tenantId);
|
||||
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
|
||||
this.idOnlyReference = isIdOnlyReference(propertiesBaseScalar);
|
||||
boolean noRelationships = propertiesOne.length + propertiesMany.length == 0;
|
||||
this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly();
|
||||
this.cacheHelp = new BeanDescriptorCacheHelp<>(this, owner.cacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
|
||||
this.cacheHelp = BeanDescriptorCacheHelp.create(this, owner.cacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
|
||||
this.jsonHelp = initJsonHelp();
|
||||
this.draftHelp = new BeanDescriptorDraftHelp<>(this);
|
||||
this.docStoreAdapter = owner.createDocStoreBeanAdapter(this, deploy);
|
||||
|
||||
+58
-66
@@ -27,7 +27,7 @@ import static java.lang.System.Logger.Level.*;
|
||||
*
|
||||
* @param <T> The entity bean type
|
||||
*/
|
||||
final class BeanDescriptorCacheHelp<T> {
|
||||
abstract class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
private static final System.Logger log = CoreLog.internal;
|
||||
|
||||
@@ -36,7 +36,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
private static final System.Logger manyLog = AppLog.getLogger("io.ebean.cache.COLL");
|
||||
private static final System.Logger natLog = AppLog.getLogger("io.ebean.cache.NATKEY");
|
||||
|
||||
private final BeanDescriptor<T> desc;
|
||||
final BeanDescriptor<T> desc;
|
||||
private final SpiCacheManager cacheManager;
|
||||
private final CacheOptions cacheOptions;
|
||||
/**
|
||||
@@ -44,13 +44,10 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
*/
|
||||
private final boolean cacheSharableBeans;
|
||||
private final boolean invalidateQueryCache;
|
||||
private final Class<?> beanType;
|
||||
final Class<?> beanType;
|
||||
private final String cacheName;
|
||||
private final BeanPropertyAssocOne<?>[] propertiesOneImported;
|
||||
private final String[] naturalKey;
|
||||
private final ServerCache beanCache;
|
||||
private final ServerCache naturalKeyCache;
|
||||
private final ServerCache queryCache;
|
||||
private final boolean noCaching;
|
||||
private final SpiCacheControl cacheControl;
|
||||
private final SpiCacheRegion cacheRegion;
|
||||
@@ -64,6 +61,15 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
*/
|
||||
private boolean cacheNotifyOnDelete;
|
||||
|
||||
static <T> BeanDescriptorCacheHelp<T> create(BeanDescriptor<T> desc, SpiCacheManager cacheManager, CacheOptions cacheOptions,
|
||||
boolean cacheSharableBeans, BeanPropertyAssocOne<?>[] propertiesOneImported) {
|
||||
if ((cacheOptions.isEnableQueryCache() || cacheOptions.isEnableBeanCache()) && cacheManager.isTenantPartitionedCache()) {
|
||||
return new BeanDescriptorCacheHelpPartitioned<>(desc, cacheManager, cacheOptions, cacheSharableBeans, propertiesOneImported);
|
||||
} else {
|
||||
return new BeanDescriptorCacheHelpFixed<>(desc, cacheManager, cacheOptions, cacheSharableBeans, propertiesOneImported);
|
||||
}
|
||||
}
|
||||
|
||||
BeanDescriptorCacheHelp(BeanDescriptor<T> desc, SpiCacheManager cacheManager, CacheOptions cacheOptions,
|
||||
boolean cacheSharableBeans, BeanPropertyAssocOne<?>[] propertiesOneImported) {
|
||||
this.desc = desc;
|
||||
@@ -75,38 +81,34 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
this.cacheSharableBeans = cacheSharableBeans;
|
||||
this.propertiesOneImported = propertiesOneImported;
|
||||
this.naturalKey = cacheOptions.getNaturalKey();
|
||||
if (!cacheOptions.isEnableQueryCache()) {
|
||||
this.queryCache = null;
|
||||
} else {
|
||||
this.queryCache = cacheManager.getQueryCache(beanType);
|
||||
}
|
||||
|
||||
if (cacheOptions.isEnableBeanCache()) {
|
||||
this.beanCache = cacheManager.getBeanCache(beanType);
|
||||
if (cacheOptions.getNaturalKey() != null) {
|
||||
this.naturalKeyCache = cacheManager.getNaturalKeyCache(beanType);
|
||||
} else {
|
||||
this.naturalKeyCache = null;
|
||||
}
|
||||
} else {
|
||||
this.beanCache = null;
|
||||
this.naturalKeyCache = null;
|
||||
}
|
||||
this.noCaching = (beanCache == null && queryCache == null);
|
||||
this.noCaching = !cacheOptions.isEnableQueryCache() && !cacheOptions.isEnableBeanCache();
|
||||
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));
|
||||
this.cacheControl = new DCacheControl(cacheRegion,
|
||||
cacheOptions.isEnableBeanCache(),
|
||||
cacheOptions.isEnableBeanCache() && cacheOptions.getNaturalKey() != null,
|
||||
cacheOptions.isEnableQueryCache());
|
||||
}
|
||||
}
|
||||
|
||||
abstract boolean hasBeanCache();
|
||||
|
||||
abstract boolean hasQueryCache();
|
||||
|
||||
abstract ServerCache queryCache();
|
||||
|
||||
abstract ServerCache naturalKeyCache();
|
||||
|
||||
abstract ServerCache beanCache();
|
||||
|
||||
/**
|
||||
* Derive the cache notify flags.
|
||||
*/
|
||||
void deriveNotifyFlags() {
|
||||
cacheNotifyOnAll = (invalidateQueryCache || beanCache != null || queryCache != null);
|
||||
cacheNotifyOnAll = (invalidateQueryCache || hasBeanCache() || hasQueryCache());
|
||||
cacheNotifyOnDelete = !cacheNotifyOnAll && isNotifyOnDeletes();
|
||||
if (log.isLoggable(DEBUG)) {
|
||||
if (cacheNotifyOnAll || cacheNotifyOnDelete) {
|
||||
@@ -184,11 +186,11 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Clear the query cache.
|
||||
*/
|
||||
void queryCacheClear() {
|
||||
if (queryCache != null) {
|
||||
if (hasQueryCache()) {
|
||||
if (queryLog.isLoggable(DEBUG)) {
|
||||
queryLog.log(DEBUG, " CLEAR {0}", cacheName);
|
||||
}
|
||||
queryCache.clear();
|
||||
queryCache().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +198,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Add query cache clear to the changeSet.
|
||||
*/
|
||||
private void queryCacheClear(CacheChangeSet changeSet) {
|
||||
if (queryCache != null) {
|
||||
if (hasQueryCache()) {
|
||||
changeSet.addClearQuery(desc);
|
||||
}
|
||||
}
|
||||
@@ -205,10 +207,10 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Get a query result from the query cache.
|
||||
*/
|
||||
Object queryCacheGet(Object id) {
|
||||
if (queryCache == null) {
|
||||
if (!hasQueryCache()) {
|
||||
throw new IllegalStateException("No query cache enabled on " + desc + ". Need explicit @Cache(enableQueryCache=true)");
|
||||
}
|
||||
Object queryResult = queryCache.get(id);
|
||||
Object queryResult = queryCache().get(id);
|
||||
if (queryLog.isLoggable(DEBUG)) {
|
||||
if (queryResult == null) {
|
||||
queryLog.log(DEBUG, " GET {0}({1}) - cache miss", cacheName, id);
|
||||
@@ -223,13 +225,13 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Put a query result into the query cache.
|
||||
*/
|
||||
void queryCachePut(Object id, QueryCacheEntry entry) {
|
||||
if (queryCache == null) {
|
||||
if (!hasQueryCache()) {
|
||||
throw new IllegalStateException("No query cache enabled on " + desc + ". Need explicit @Cache(enableQueryCache=true)");
|
||||
}
|
||||
if (queryLog.isLoggable(DEBUG)) {
|
||||
queryLog.log(DEBUG, " PUT {0}({1})", cacheName, id);
|
||||
}
|
||||
queryCache.put(id, entry);
|
||||
queryCache().put(id, entry);
|
||||
}
|
||||
|
||||
void manyPropRemove(String propertyName, String parentKey) {
|
||||
@@ -300,7 +302,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
*/
|
||||
void manyPropPut(BeanPropertyAssocMany<?> many, Object details, String parentKey) {
|
||||
if (many.isElementCollection()) {
|
||||
CachedBeanData data = (CachedBeanData) beanCache.get(parentKey);
|
||||
CachedBeanData data = (CachedBeanData) beanCache().get(parentKey);
|
||||
if (data != null) {
|
||||
try {
|
||||
// add as JSON to bean cache
|
||||
@@ -312,7 +314,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
if (beanLog.isLoggable(DEBUG)) {
|
||||
beanLog.log(DEBUG, " UPDATE {0}({1}) changes:{2}", cacheName, parentKey, changes);
|
||||
}
|
||||
beanCache.put(parentKey, newData);
|
||||
beanCache().put(parentKey, newData);
|
||||
} catch (IOException e) {
|
||||
log.log(ERROR, "Error updating L2 cache", e);
|
||||
}
|
||||
@@ -358,7 +360,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
if (ids.isEmpty()) {
|
||||
return new BeanCacheResult<>();
|
||||
}
|
||||
Map<Object, Object> beanDataMap = beanCache.getAll(keys);
|
||||
Map<Object, Object> beanDataMap = beanCache().getAll(keys);
|
||||
if (beanLog.isLoggable(TRACE)) {
|
||||
beanLog.log(TRACE, " MGET {0}({1}) - hits:{2}", cacheName, ids, beanDataMap.keySet());
|
||||
}
|
||||
@@ -380,7 +382,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
// naturalKey -> Id map
|
||||
Map<Object, Object> naturalKeyMap = naturalKeyCache.getAll(keys);
|
||||
Map<Object, Object> naturalKeyMap = naturalKeyCache().getAll(keys);
|
||||
if (natLog.isLoggable(TRACE)) {
|
||||
natLog.log(TRACE, " MLOOKUP {0}({1}) - hits:{2}", cacheName, keys, naturalKeyMap);
|
||||
}
|
||||
@@ -397,7 +399,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
Set<Object> ids = new HashSet<>(naturalKeyMap.values());
|
||||
Map<Object, Object> beanDataMap = beanCache.getAll(ids);
|
||||
Map<Object, Object> beanDataMap = beanCache().getAll(ids);
|
||||
if (beanLog.isLoggable(TRACE)) {
|
||||
beanLog.log(TRACE, " MGET {0}({1}) - hits:{2}", cacheName, ids, beanDataMap.keySet());
|
||||
}
|
||||
@@ -428,25 +430,15 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
desc.contextPut(context, id, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the beanCache creating it if necessary.
|
||||
*/
|
||||
private ServerCache getBeanCache() {
|
||||
if (beanCache == null) {
|
||||
throw new IllegalStateException("No bean cache enabled for " + desc + ". Add the @Cache annotation.");
|
||||
}
|
||||
return beanCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the bean cache.
|
||||
*/
|
||||
void beanCacheClear() {
|
||||
if (beanCache != null) {
|
||||
if (hasBeanCache()) {
|
||||
if (beanLog.isLoggable(DEBUG)) {
|
||||
beanLog.log(DEBUG, " CLEAR {0}", cacheName);
|
||||
}
|
||||
beanCache.clear();
|
||||
beanCache().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,13 +508,13 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
if (beanLog.isLoggable(DEBUG)) {
|
||||
beanLog.log(DEBUG, " MPUT {0}({1})", cacheName, map.keySet());
|
||||
}
|
||||
getBeanCache().putAll(map);
|
||||
beanCache().putAll(map);
|
||||
|
||||
if (natKeys != null && !natKeys.isEmpty()) {
|
||||
if (natLog.isLoggable(DEBUG)) {
|
||||
natLog.log(DEBUG, " MPUT {0}({1}, {2})", cacheName, Arrays.toString(naturalKey), natKeys.keySet());
|
||||
}
|
||||
naturalKeyCache.putAll(natKeys);
|
||||
naturalKeyCache().putAll(natKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,14 +527,14 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
if (beanLog.isLoggable(DEBUG)) {
|
||||
beanLog.log(DEBUG, " PUT {0}({1}) data:{2}", cacheName, key, beanData);
|
||||
}
|
||||
getBeanCache().put(key, beanData);
|
||||
beanCache().put(key, beanData);
|
||||
if (naturalKey != null) {
|
||||
String naturalKey = calculateNaturalKey(beanData);
|
||||
if (naturalKey != null) {
|
||||
if (natLog.isLoggable(DEBUG)) {
|
||||
natLog.log(DEBUG, " PUT {0}({1}, {2})", cacheName, naturalKey, key);
|
||||
}
|
||||
naturalKeyCache.put(naturalKey, key);
|
||||
naturalKeyCache().put(naturalKey, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -564,7 +556,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
CachedBeanData beanCacheGetData(String key) {
|
||||
return (CachedBeanData) getBeanCache().get(key);
|
||||
return (CachedBeanData) beanCache().get(key);
|
||||
}
|
||||
|
||||
T beanCacheGet(String key, boolean unmodifiable, PersistenceContext context) {
|
||||
@@ -579,7 +571,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Return a bean from the bean cache.
|
||||
*/
|
||||
private T beanCacheGetInternal(String key, boolean unmodifiable, PersistenceContext context) {
|
||||
CachedBeanData data = (CachedBeanData) getBeanCache().get(key);
|
||||
CachedBeanData data = (CachedBeanData) beanCache().get(key);
|
||||
if (data == null) {
|
||||
if (beanLog.isLoggable(TRACE)) {
|
||||
beanLog.log(TRACE, " GET {0}({1}) - cache miss", cacheName, key);
|
||||
@@ -684,11 +676,11 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Remove a bean from the cache given its Id.
|
||||
*/
|
||||
void beanCacheApplyInvalidate(Collection<String> keys) {
|
||||
if (beanCache != null) {
|
||||
if (hasBeanCache()) {
|
||||
if (beanLog.isLoggable(DEBUG)) {
|
||||
beanLog.log(DEBUG, " MREMOVE {0}({1})", cacheName, keys);
|
||||
}
|
||||
beanCache.removeAll(new HashSet<>(keys));
|
||||
beanCache().removeAll(new HashSet<>(keys));
|
||||
}
|
||||
for (BeanPropertyAssocOne<?> imported : propertiesOneImported) {
|
||||
imported.cacheClear();
|
||||
@@ -704,7 +696,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
ebis.put(desc.cacheKeyForBean(ebi.owner()), ebi);
|
||||
}
|
||||
|
||||
Map<Object, Object> hits = getBeanCache().getAll(ebis.keySet());
|
||||
Map<Object, Object> hits = beanCache().getAll(ebis.keySet());
|
||||
if (beanLog.isLoggable(TRACE)) {
|
||||
beanLog.log(TRACE, " MLOAD {0}({1}) - got hits ({2})", cacheName, ebis.keySet(), hits.size());
|
||||
}
|
||||
@@ -737,7 +729,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Returns true if it managed to populate/load the single bean from the cache.
|
||||
*/
|
||||
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, String key, PersistenceContext context) {
|
||||
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(key);
|
||||
CachedBeanData cacheData = (CachedBeanData) beanCache().get(key);
|
||||
if (cacheData == null) {
|
||||
if (beanLog.isLoggable(TRACE)) {
|
||||
beanLog.log(TRACE, " LOAD {0}({1}) - cache miss", cacheName, key);
|
||||
@@ -775,7 +767,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
changeSet.addInvalidate(desc);
|
||||
} else {
|
||||
queryCacheClear(changeSet);
|
||||
if (beanCache != null) {
|
||||
if (hasBeanCache()) {
|
||||
changeSet.addBeanRemoveMany(desc, ids);
|
||||
}
|
||||
cacheDeleteImported(true, null, changeSet);
|
||||
@@ -793,7 +785,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
changeSet.addInvalidate(desc);
|
||||
} else {
|
||||
queryCacheClear(changeSet);
|
||||
if (beanCache != null) {
|
||||
if (hasBeanCache()) {
|
||||
changeSet.addBeanRemove(desc, id);
|
||||
}
|
||||
cacheDeleteImported(true, deleteRequest.entityBean(), changeSet);
|
||||
@@ -831,7 +823,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
} else {
|
||||
queryCacheClear(changeSet);
|
||||
if (beanCache == null) {
|
||||
if (!hasBeanCache()) {
|
||||
// query caching only
|
||||
return;
|
||||
}
|
||||
@@ -877,7 +869,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
void cacheNaturalKeyPut(String key, String newKey) {
|
||||
if (newKey != null) {
|
||||
naturalKeyCache.put(newKey, key);
|
||||
naturalKeyCache().put(newKey, key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -885,7 +877,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Apply changes to the bean cache entry.
|
||||
*/
|
||||
void cacheBeanUpdate(String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
ServerCache cache = getBeanCache();
|
||||
ServerCache cache = beanCache();
|
||||
CachedBeanData existingData = (CachedBeanData) cache.get(key);
|
||||
if (existingData != null) {
|
||||
long currentVersion = existingData.getVersion();
|
||||
@@ -910,7 +902,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
if (natLog.isLoggable(DEBUG)) {
|
||||
natLog.log(DEBUG, ".. update {0} REMOVE({1}) - old key for ({2})", cacheName, oldKey, key);
|
||||
}
|
||||
naturalKeyCache.remove(oldKey);
|
||||
naturalKeyCache().remove(oldKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebeaninternal.server.cache.SpiCacheManager;
|
||||
import io.ebeaninternal.server.core.CacheOptions;
|
||||
|
||||
/**
|
||||
* BeanDescriptorCacheHelp implementation for non-tenant-partitioned (fixed) caches.
|
||||
* Cache instances are obtained once at construction time and reused for all requests.
|
||||
*
|
||||
* @param <T> The entity bean type
|
||||
*/
|
||||
final class BeanDescriptorCacheHelpFixed<T> extends BeanDescriptorCacheHelp<T> {
|
||||
|
||||
private final ServerCache beanCache;
|
||||
private final ServerCache naturalKeyCache;
|
||||
private final ServerCache queryCache;
|
||||
|
||||
BeanDescriptorCacheHelpFixed(BeanDescriptor<T> desc, SpiCacheManager cacheManager, CacheOptions cacheOptions,
|
||||
boolean cacheSharableBeans, BeanPropertyAssocOne<?>[] propertiesOneImported) {
|
||||
super(desc, cacheManager, cacheOptions, cacheSharableBeans, propertiesOneImported);
|
||||
if (!cacheOptions.isEnableQueryCache()) {
|
||||
this.queryCache = null;
|
||||
} else {
|
||||
this.queryCache = cacheManager.getQueryCache(beanType);
|
||||
}
|
||||
if (cacheOptions.isEnableBeanCache()) {
|
||||
this.beanCache = cacheManager.getBeanCache(beanType);
|
||||
this.naturalKeyCache = (cacheOptions.getNaturalKey() != null) ? cacheManager.getNaturalKeyCache(beanType) : null;
|
||||
} else {
|
||||
this.beanCache = null;
|
||||
this.naturalKeyCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean hasBeanCache() {
|
||||
return beanCache != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean hasQueryCache() {
|
||||
return queryCache != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
ServerCache queryCache() {
|
||||
return queryCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
ServerCache naturalKeyCache() {
|
||||
return naturalKeyCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
ServerCache beanCache() {
|
||||
if (beanCache == null) {
|
||||
throw new IllegalStateException("No bean cache enabled for " + desc + ". Add the @Cache annotation.");
|
||||
}
|
||||
return beanCache;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebeaninternal.server.cache.SpiCacheManager;
|
||||
import io.ebeaninternal.server.core.CacheOptions;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* BeanDescriptorCacheHelp implementation for tenant-partitioned caches.
|
||||
* Cache instances are looked up on every access via the cacheManager (which resolves the
|
||||
* correct tenant-namespaced cache using the current tenant id from the tenant provider).
|
||||
*
|
||||
* @param <T> The entity bean type
|
||||
*/
|
||||
final class BeanDescriptorCacheHelpPartitioned<T> extends BeanDescriptorCacheHelp<T> {
|
||||
|
||||
private final Supplier<ServerCache> beanCacheSupplier;
|
||||
private final Supplier<ServerCache> naturalKeyCacheSupplier;
|
||||
private final Supplier<ServerCache> queryCacheSupplier;
|
||||
|
||||
BeanDescriptorCacheHelpPartitioned(BeanDescriptor<T> desc, SpiCacheManager cacheManager, CacheOptions cacheOptions,
|
||||
boolean cacheSharableBeans, BeanPropertyAssocOne<?>[] propertiesOneImported) {
|
||||
super(desc, cacheManager, cacheOptions, cacheSharableBeans, propertiesOneImported);
|
||||
this.queryCacheSupplier = cacheOptions.isEnableQueryCache() ? () -> cacheManager.getQueryCache(beanType) : null;
|
||||
if (cacheOptions.isEnableBeanCache()) {
|
||||
this.beanCacheSupplier = () -> cacheManager.getBeanCache(beanType);
|
||||
this.naturalKeyCacheSupplier = (cacheOptions.getNaturalKey() != null) ? () -> cacheManager.getNaturalKeyCache(beanType) : null;
|
||||
} else {
|
||||
this.beanCacheSupplier = null;
|
||||
this.naturalKeyCacheSupplier = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean hasBeanCache() {
|
||||
return beanCacheSupplier != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean hasQueryCache() {
|
||||
return queryCacheSupplier != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
ServerCache queryCache() {
|
||||
return queryCacheSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
ServerCache naturalKeyCache() {
|
||||
return naturalKeyCacheSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
ServerCache beanCache() {
|
||||
if (beanCacheSupplier == null) {
|
||||
throw new IllegalStateException("No bean cache enabled for " + desc + ". Add the @Cache annotation.");
|
||||
}
|
||||
return beanCacheSupplier.get();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>tests</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>test-java16</artifactId>
|
||||
@@ -18,7 +18,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-h2</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -30,14 +30,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user