From 17435807db09e5cdb9250f026b220544c0a36065 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Tue, 17 Jan 2023 14:26:14 +0100 Subject: [PATCH] NEW: Hazelcast supports near cache --- .../io/ebean/hazelcast/HzCacheFactory.java | 61 +++++++++++++++---- .../ebean/hazelcast/HzCacheFactoryTest.java | 19 +++++- src/test/java/org/example/domain/EConfig.java | 19 ++++++ 3 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 src/test/java/org/example/domain/EConfig.java diff --git a/src/main/java/io/ebean/hazelcast/HzCacheFactory.java b/src/main/java/io/ebean/hazelcast/HzCacheFactory.java index 3d60ce8..03170ec 100644 --- a/src/main/java/io/ebean/hazelcast/HzCacheFactory.java +++ b/src/main/java/io/ebean/hazelcast/HzCacheFactory.java @@ -7,7 +7,6 @@ import com.hazelcast.core.HazelcastInstance; import com.hazelcast.map.IMap; import com.hazelcast.topic.ITopic; - import io.ebean.BackgroundExecutor; import io.ebean.cache.ServerCache; import io.ebean.cache.ServerCacheConfig; @@ -15,6 +14,7 @@ import io.ebean.cache.ServerCacheNotification; import io.ebean.cache.ServerCacheNotify; import io.ebean.config.DatabaseConfig; +import io.ebeaninternal.server.cache.DefaultServerCache; import io.ebeaninternal.server.cache.DefaultServerCacheConfig; import io.ebeaninternal.server.cache.DefaultServerQueryCache; import org.slf4j.Logger; @@ -36,7 +36,7 @@ public final class HzCacheFactory implements ServerCacheFactory { */ private static final Logger logger = LoggerFactory.getLogger("io.ebean.cache.HzCacheFactory"); - private final ConcurrentHashMap queryCaches; + private final ConcurrentHashMap localCaches; private final HazelcastInstance instance; /** * Topic used to broadcast query cache invalidation. @@ -51,7 +51,7 @@ public final class HzCacheFactory implements ServerCacheFactory { public HzCacheFactory(DatabaseConfig config, BackgroundExecutor executor) { this.executor = executor; - this.queryCaches = new ConcurrentHashMap<>(); + this.localCaches = new ConcurrentHashMap<>(); if (System.getProperty("hazelcast.logging.type") == null) { System.setProperty("hazelcast.logging.type", "slf4j"); } @@ -107,7 +107,9 @@ public ServerCacheNotify createCacheNotify(ServerCacheNotify listener) { @Override public ServerCache createCache(ServerCacheConfig config) { if (config.isQueryCache()) { - return createQueryCache(config); + return createLocalCache(config, true); + } else if (config.getCacheOptions().isNearCache()) { + return createLocalCache(config, false); } else { return createNormalCache(config); } @@ -120,23 +122,38 @@ private ServerCache createNormalCache(ServerCacheConfig config) { return config.tenantAware(new HzCache(map)); } - private ServerCache createQueryCache(ServerCacheConfig config) { + private ServerCache createLocalCache(ServerCacheConfig config, boolean queryCache) { synchronized (this) { - HzQueryCache cache = queryCaches.get(config.getCacheKey()); + HzLocalCache cache = localCaches.get(config.getCacheKey()); if (cache == null) { logger.debug("create query cache [{}]", config.getCacheKey()); - cache = new HzQueryCache(new DefaultServerCacheConfig(config)); + if (queryCache) { + cache = new HzQueryCache(new DefaultServerCacheConfig(config)); + } else { + cache = new HzNearCache(new DefaultServerCacheConfig(config)); + } cache.periodicTrim(executor); - queryCaches.put(config.getCacheKey(), cache); + localCaches.put(config.getCacheKey(), cache); } + assert cache instanceof HzQueryCache == queryCache : "Got wrong cache type: " + cache.getClass().getName() + ", queyCache: " + queryCache; return config.tenantAware(cache); } } + /** + * Either a local QueryCache or a near beanCache. + */ + interface HzLocalCache extends ServerCache { + + void periodicTrim(BackgroundExecutor executor); + + void invalidate(); + } + /** * Extends normal default implementation with notification of clear() to cluster. */ - private class HzQueryCache extends DefaultServerQueryCache { + class HzQueryCache extends DefaultServerQueryCache implements HzLocalCache { HzQueryCache(DefaultServerCacheConfig cacheConfig) { super(cacheConfig); @@ -151,7 +168,29 @@ public void clear() { /** * Process the invalidation message coming from the cluster. */ - private void invalidate() { + @Override + public void invalidate() { + super.clear(); + } + } + + /** + * Extends normal default implementation with notification of clear() to cluster. + */ + class HzNearCache extends DefaultServerCache implements HzLocalCache { + + public HzNearCache(DefaultServerCacheConfig config) { + super(config); + } + + @Override + public void clear() { + super.clear(); + sendInvalidation(name); + } + + @Override + public void invalidate() { super.clear(); } } @@ -167,7 +206,7 @@ private void sendInvalidation(String key) { * Process a remote query cache invalidation. */ private void processInvalidation(String cacheName) { - HzQueryCache cache = queryCaches.get(cacheName); + HzLocalCache cache = localCaches.get(cacheName); if (cache != null) { cache.invalidate(); } diff --git a/src/test/java/io/ebean/hazelcast/HzCacheFactoryTest.java b/src/test/java/io/ebean/hazelcast/HzCacheFactoryTest.java index 1083956..7bcf4a6 100644 --- a/src/test/java/io/ebean/hazelcast/HzCacheFactoryTest.java +++ b/src/test/java/io/ebean/hazelcast/HzCacheFactoryTest.java @@ -3,12 +3,12 @@ import io.ebean.DB; import io.ebean.Database; import io.ebean.Ebean; -import io.ebean.EbeanServer; import io.ebean.cache.ServerCache; import io.ebean.cache.ServerCacheManager; import io.ebean.cache.TenantAwareCache; -import io.ebean.cache.TenantAwareKey; +import io.ebeaninternal.server.cache.DefaultServerQueryCache; import org.example.domain.EAddress; +import org.example.domain.EConfig; import org.example.domain.ECustomer; import org.example.domain.EOrder; import org.junit.Test; @@ -94,7 +94,20 @@ public void integration() throws InterruptedException { ServerCache beanCache = cacheManager.beanCache(ECustomer.class); assertThat(beanCache).isInstanceOf(TenantAwareCache.class); - assertThat(beanCache.unwrap(HzCache.class)).isInstanceOf(HzCache.class); + assertThat(beanCache.unwrap(ServerCache.class)).isInstanceOf(HzCache.class); + + ServerCache queryCache = cacheManager.queryCache(ECustomer.class); + assertThat(queryCache).isInstanceOf(TenantAwareCache.class); + assertThat(queryCache.unwrap(ServerCache.class)).isInstanceOf(HzCacheFactory.HzQueryCache.class); + + beanCache = cacheManager.beanCache(EConfig.class); + + assertThat(beanCache).isInstanceOf(TenantAwareCache.class); + assertThat(beanCache.unwrap(ServerCache.class)).isInstanceOf(HzCacheFactory.HzNearCache.class); + + queryCache = cacheManager.queryCache(EConfig.class); + assertThat(queryCache).isInstanceOf(TenantAwareCache.class); + assertThat(queryCache.unwrap(ServerCache.class)).isInstanceOf(HzCacheFactory.HzQueryCache.class); // Ebean.update(ECustomer.class) // .setRaw("name = 'x'") diff --git a/src/test/java/org/example/domain/EConfig.java b/src/test/java/org/example/domain/EConfig.java new file mode 100644 index 0000000..1428a58 --- /dev/null +++ b/src/test/java/org/example/domain/EConfig.java @@ -0,0 +1,19 @@ +package org.example.domain; + +import io.ebean.annotation.Cache; + +import javax.persistence.Entity; + +@Cache(enableQueryCache = true, nearCache = true) +@Entity +public class EConfig extends EBase { + private boolean option1; + + public boolean isOption1() { + return option1; + } + + public void setOption1(boolean option1) { + this.option1 = option1; + } +}