diff --git a/crane4j-extension/crane4j-extension-redisson/pom.xml b/crane4j-extension/crane4j-extension-redisson/pom.xml new file mode 100644 index 00000000..1025476f --- /dev/null +++ b/crane4j-extension/crane4j-extension-redisson/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + cn.crane4j + crane4j + 2.8.0 + ../../pom.xml + + + crane4j-extension-redisson + + + 8 + 8 + UTF-8 + + + + cn.crane4j + crane4j-core + 2.8.0 + compile + + + org.redisson + redisson-spring-boot-starter + 3.15.6 + + + + \ No newline at end of file diff --git a/crane4j-extension/crane4j-extension-redisson/src/main/java/cn/crane4j/extension/redisson/StringKeyRedissonCacheManger.java b/crane4j-extension/crane4j-extension-redisson/src/main/java/cn/crane4j/extension/redisson/StringKeyRedissonCacheManger.java new file mode 100644 index 00000000..f168e05a --- /dev/null +++ b/crane4j-extension/crane4j-extension-redisson/src/main/java/cn/crane4j/extension/redisson/StringKeyRedissonCacheManger.java @@ -0,0 +1,243 @@ +package cn.crane4j.extension.redisson; +import cn.crane4j.core.cache.AbstractCacheManager; +import lombok.NonNull; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.redisson.api.RBatch; +import org.redisson.api.RBucket; +import org.redisson.api.RedissonClient; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +@Setter +@Slf4j +public class StringKeyRedissonCacheManger extends AbstractCacheManager { + /** + * Global prefix for all cache keys + */ + @NonNull + private String globalPrefix = "crane4j:cache:"; + @NonNull + private RedissonClient redissonClient; + private boolean enableClearCache = false; + public StringKeyRedissonCacheManger(@NonNull RedissonClient redisson) { + this.redissonClient = redisson; + } + + /** + * Create cache instance. + */ + @NonNull + @Override + protected RedissonCacheObject doCreateCache(String name,Long expireTime, TimeUnit timeUnit){ + return new RedissonCacheObject(name,expireTime,timeUnit); + } + + /** + * Get the cache key which is used to store value in redisson. + */ + protected String resolveCacheKey(String cacheName,String key){ + return globalPrefix + ":" + cacheName + ":" + key; + } + + /** + * Resolve cache value. + * @param value cache value + * @return cache value + */ + protected Object resolveCacheValue(Object value){return value;} + + /** + * Clean all cache value for a specified cache object. + */ + protected void cleanCache(String cacheName) { + if (enableClearCache) { + String prefix = globalPrefix + ":" + cacheName + ":*"; + + long deletedCount = 0; + for (String key : redissonClient.getKeys().getKeysByPattern(prefix)) { + RBucket rBucket = redissonClient.getBucket(key); + rBucket.delete(); + deletedCount++; + } + + log.warn("Cleared [{}] keys from cache [{}] by prefix [{}]", deletedCount, cacheName, prefix); + } else { + log.warn("Clearing all cache values is not supported in redis cache [{}]", cacheName); + } + } + + + + /** + * Redis cache object. + */ + protected class RedissonCacheObject extends AbstractCacheObject{ + private final long expireTime; + private final TimeUnit timeUnit; + private volatile boolean invalid = false; + + protected RedissonCacheObject(String name,Long expireTime,TimeUnit timeUnit){ + super(name); + this.expireTime = expireTime; + this.timeUnit = timeUnit; + } + + /** + * Clear all cache value. + */ + public void clear(){cleanCache(getName());} + + /** + * Add all cache value. + * @param caches value + */ + public void putAll(Map caches){ + RBatch rBatch = redissonClient.createBatch(); + + for(Map.Entry entry : caches.entrySet()){ + String key = entry.getKey(); + Object value = entry.getValue(); + String cacheKey = globalPrefix + ":" + getName() + ":" + key; + RBucket bucket = redissonClient.getBucket(cacheKey); + bucket.setAsync(value); + rBatch.getBucket(cacheKey).setAsync(value); + } + rBatch.execute(); + } + + + + + /** + * Get all caches according to the key values. + * @param keys keys + * @return map containing keys and their corresponding values + */ + public Map getAll(Iterable keys) { + Set keySet = StreamSupport.stream(keys.spliterator(), false) + .map(key -> resolveCacheKey(getName(), key)) + .collect(Collectors.toCollection(LinkedHashSet::new)); + + Map results = new LinkedHashMap<>(); + + RBatch rBatch = redissonClient.createBatch(); + for (String key : keySet) { + rBatch.getBucket(key).getAsync(); + } + + List resultValues = rBatch.execute().getResponses(); + int index = 0; + for (String key : keys) { + Object value = resultValues.get(index++); + if (value != null) { + results.put(key, value); + } + } + return results; + } + + + + /** + * Clear all cache value for a specified cache object. + * @param cacheName + */ + protected void cleanCache(String cacheName) { + if (enableClearCache) { + String prefix = globalPrefix + ":" + cacheName + ":*"; + + Set keys = new HashSet<>(); + for (String key : redissonClient.getKeys().getKeysByPattern(prefix)) { + keys.add(key); + } + + RBatch rBatch = redissonClient.createBatch(); + for (String key : keys) { + rBatch.getBucket(key).deleteAsync(); + } + rBatch.execute(); + + log.warn("Cleared [{}] keys from cache [{}] by prefix [{}]", keys.size(), cacheName, prefix); + } else { + log.warn("Clearing all cache values is not supported in redis cache [{}]", cacheName); + } + } + + + /** + * Get the cache according to the key value. + * @param key key + * @return cache value + */ + @Nullable + public Object get(String key){ + String cacheKey = resolveCacheKey(getName(),key); + RBucket rBucket = redissonClient.getBucket(cacheKey); + return rBucket.get(); + } + + /** + * Add cache value. + * @param key key + * @param value value + */ + public void put(String key, Object value){ + String cacheKey = resolveCacheKey(getName(),key); + Object cacheValue = resolveCacheValue(value); + RBucket bucket = redissonClient.getBucket(cacheKey); + bucket.set(cacheValue,expireTime,timeUnit); + } + + /** + * Add cache value if it does not exist + * @param key key + * @param value value + */ + public void putIfAbsent(String key, Object value) { + String cacheKey = resolveCacheKey(getName(), key); + RBucket bucket = redissonClient.getBucket(cacheKey); + + if (bucket.isExists()) return; + Object cacheValue = resolveCacheValue(value); + bucket.set(cacheValue, expireTime, timeUnit); + } + + /** + * Remove cache value + * @param key key + */ + public void remove(String key){ + String cacheKey = resolveCacheKey(getName(),key); + RBucket rBucket = redissonClient.getBucket(cacheKey); + rBucket.delete(); + } + + /** + * Remove all cache value + * @param keys keys + */ + public void removeAll(Iterable keys) { + Set keySet = StreamSupport.stream(keys.spliterator(), false) + .map(key -> resolveCacheKey(getName(), key)) + .collect(Collectors.toSet()); + + RBatch rBatch = redissonClient.createBatch(); + + for (String key : keySet) { + RBucket bucket = redissonClient.getBucket(key); + rBatch.getBucket(bucket.getName()).deleteAsync(); + } + + rBatch.execute(); + } + } +} \ No newline at end of file diff --git a/crane4j-extension/crane4j-extension-redisson/src/test/java/cn/crane4j/extension/redisson/StringKeyRedissonChangeManagerTest.java b/crane4j-extension/crane4j-extension-redisson/src/test/java/cn/crane4j/extension/redisson/StringKeyRedissonChangeManagerTest.java new file mode 100644 index 00000000..d98e820d --- /dev/null +++ b/crane4j-extension/crane4j-extension-redisson/src/test/java/cn/crane4j/extension/redisson/StringKeyRedissonChangeManagerTest.java @@ -0,0 +1,118 @@ +package cn.crane4j.extension.redisson; + +import cn.crane4j.core.cache.CacheObject; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + + +public class StringKeyRedissonChangeManagerTest { + private static final String PREFIX = "prefix"; + private static final String CACHE_NAME = "test"; + private static final long EXPIRE_TIME = 3000L; + private static final TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS; + private StringKeyRedissonCacheManger cacheManager; + private RedissonClient redissonClient; + private CacheObject cache; + + @Before + public void init() { + Config config = new Config(); + config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(1); + redissonClient = Redisson.create(config); + cacheManager = new StringKeyRedissonCacheManger(redissonClient); + cacheManager.setGlobalPrefix(PREFIX); + cache = cacheManager.createCache("test", EXPIRE_TIME, TIME_UNIT); + } + + @Test + public void testPut() { + cache.put("key", "value"); + Assert.assertEquals("value", cache.get("key")); + Assert.assertEquals("value", redissonClient.getBucket("prefix:test:key").get()); + + redissonClient.getBucket("prefix:test:key").delete(); + + + + } + + @Test + public void testPutAll() { + Map values = new LinkedHashMap<>(2); + values.put("key1", "value1"); + values.put("key2", "value2"); + + cache.putAll(values); + + Assert.assertEquals("value1", redissonClient.getBucket("prefix:test:key1").get()); + redissonClient.getBucket("prefix:test:key1").delete(); + Assert.assertEquals("value2",redissonClient.getBucket("prefix:test:key2").get()); + redissonClient.getBucket("prefix:test:key2").delete(); + } + + @Test + public void testGet() { + cache.put("key", "value"); + Assert.assertEquals("value", cache.get("key")); + redissonClient.getBucket("prefix:test:key").delete(); + } + + @Test + public void testGetAll() { + cache.put("key1", "value1"); + cache.put("key2", "value2"); + Map map = cache.getAll(Arrays.asList("key1", "none", "key2")); + Assert.assertEquals("value1", map.get("key1")); + Assert.assertEquals("value2", map.get("key2")); + Assert.assertNull(map.get("none")); + redissonClient.getBucket("prefix:test:key1").delete(); + redissonClient.getBucket("prefix:test:key2").delete(); + } + + @Test + public void putIfAbsent() { + cache.put("key1", "value1"); + cache.putIfAbsent("key1", "value1"); + Assert.assertEquals("value1",redissonClient.getBucket("prefix:test:key1").get()); + redissonClient.getBucket("prefix:test:key1").delete(); + + cache.putIfAbsent("key2", "value2"); + Assert.assertEquals("value2", redissonClient.getBucket("prefix:test:key2").get()); + redissonClient.getBucket("prefix:test:key2").delete(); + } + + @Test + public void remove() { + cache.put("key", "value"); + cache.remove("key"); + redissonClient.getBucket("prefix:test:key").delete(); + } + + @Test + public void removeAll() { + cache.put("key1", "value1"); + cache.put("key2", "value2"); + cache.removeAll(Arrays.asList("key1", "key2")); + Assert.assertNull(redissonClient.getBucket("prefix:test:key1").get()); + Assert.assertNull(redissonClient.getBucket("prefix:test:key2").get()); + } + + @Test + public void clear() { + cache.put("key", "value"); + cache.clear(); + Assert.assertEquals("value", redissonClient.getBucket("prefix:test:key").get()); + cacheManager.setEnableClearCache(true); + cache.clear(); + Assert.assertNull(redissonClient.getBucket("prefix:test:key").get()); + } +} diff --git a/pom.xml b/pom.xml index 8d3f6b68..394c0c2b 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ crane4j-annotation crane4j-example crane4j-extension + crane4j-extension/crane4j-extension-redisson