From 32511e67c3bf5956332e546cc7455588559807e5 Mon Sep 17 00:00:00 2001 From: Nikita Koksharov Date: Tue, 2 May 2023 11:11:02 +0300 Subject: [PATCH] Feature - RMapCache.getAllWithTTLOnly() method added. #3907 --- .../java/org/redisson/RedissonMapCache.java | 66 +++++++++++++++++++ .../src/main/java/org/redisson/api/RMap.java | 2 +- .../main/java/org/redisson/api/RMapCache.java | 16 +++++ .../java/org/redisson/api/RMapCacheAsync.java | 15 +++++ .../org/redisson/api/RMapCacheReactive.java | 16 +++++ .../java/org/redisson/api/RMapCacheRx.java | 16 +++++ .../org/redisson/RedissonMapCacheTest.java | 18 +++++ 7 files changed, 148 insertions(+), 1 deletion(-) diff --git a/redisson/src/main/java/org/redisson/RedissonMapCache.java b/redisson/src/main/java/org/redisson/RedissonMapCache.java index dd3b8e0cc..3ab716a4e 100644 --- a/redisson/src/main/java/org/redisson/RedissonMapCache.java +++ b/redisson/src/main/java/org/redisson/RedissonMapCache.java @@ -1294,6 +1294,72 @@ public class RedissonMapCache extends RedissonMap implements RMapCac return new CompletableFutureWrapper<>(f); } + @Override + public Map getAllWithTTLOnly(Set keys) { + return get(getAllWithTTLOnlyAsync(keys)); + } + + @Override + public RFuture> getAllWithTTLOnlyAsync(Set keys) { + if (keys.isEmpty()) { + return new CompletableFutureWrapper<>(Collections.emptyMap()); + } + + RFuture> future = getAllWithTTLOnlyOperationAsync(keys); + if (hasNoLoader()) { + return future; + } + + CompletionStage> f = future.thenCompose(res -> { + if (!res.keySet().containsAll(keys)) { + Set newKeys = new HashSet(keys); + newKeys.removeAll(res.keySet()); + + CompletionStage> ff = loadAllMapAsync(newKeys.spliterator(), false, 1); + return ff.thenApply(map -> { + res.putAll(map); + return res; + }); + } + return CompletableFuture.completedFuture(res); + }); + return new CompletableFutureWrapper<>(f); + } + + protected RFuture> getAllWithTTLOnlyOperationAsync(Set keys) { + List args = new ArrayList<>(keys.size() + 1); + List plainKeys = new ArrayList<>(keys); + + args.add(System.currentTimeMillis()); + encodeMapKeys(args, keys); + + return commandExecutor.evalReadAsync(getRawName(), codec, new RedisCommand>("EVAL", + new MapValueDecoder(new MapGetAllDecoder(plainKeys, 0))), + "local expireHead = redis.call('zrange', KEYS[2], 0, 0, 'withscores'); " + + "local currentTime = tonumber(table.remove(ARGV, 1)); " + // index is the first parameter + "local hasExpire = #expireHead == 2 and tonumber(expireHead[2]) <= currentTime; " + + "local map = {}; " + + "for i = 1, #ARGV, 1 do " + + " local value = redis.call('hget', KEYS[1], ARGV[i]); " + + " map[i] = false;" + + " if value ~= false then " + + " local key = ARGV[i]; " + + " local t, val = struct.unpack('dLc0', value); " + + " map[i] = val; " + + " if hasExpire then " + + " local expireDate = redis.call('zscore', KEYS[2], key); " + + " if expireDate ~= false and tonumber(expireDate) <= currentTime then " + + " map[i] = false; " + + " end; " + + " end; " + + " end; " + + "end; " + + "return map;", + Arrays.asList(getRawName(), getTimeoutSetName()), + args.toArray()); + } + + @Override public long remainTimeToLive(K key) { return get(remainTimeToLiveAsync(key)); diff --git a/redisson/src/main/java/org/redisson/api/RMap.java b/redisson/src/main/java/org/redisson/api/RMap.java index 120de63a4..6dbe19c11 100644 --- a/redisson/src/main/java/org/redisson/api/RMap.java +++ b/redisson/src/main/java/org/redisson/api/RMap.java @@ -303,7 +303,7 @@ public interface RMap extends ConcurrentMap, RExpirable, RMapAsync * The returned map is NOT backed by the original map. * - * @param keys - map keys + * @param keys map keys * @return Map slice */ Map getAll(Set keys); diff --git a/redisson/src/main/java/org/redisson/api/RMapCache.java b/redisson/src/main/java/org/redisson/api/RMapCache.java index 1cad2b976..e242fbda6 100644 --- a/redisson/src/main/java/org/redisson/api/RMapCache.java +++ b/redisson/src/main/java/org/redisson/api/RMapCache.java @@ -19,6 +19,8 @@ import org.redisson.api.map.MapLoader; import org.redisson.api.map.MapWriter; import org.redisson.api.map.event.MapEntryListener; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -303,6 +305,20 @@ public interface RMapCache extends RMap, RMapCacheAsync { */ V getWithTTLOnly(K key); + /** + * Returns map slice contained the mappings with defined keys. + *

+ * If map doesn't contain value/values for specified key/keys and {@link MapLoader} is defined + * then value/values will be loaded in read-through mode. + *

+ * NOTE: Idle time of entry is not taken into account. + * Entry last access time isn't modified if map limited by size. + * + * @param keys map keys + * @return Map slice + */ + Map getAllWithTTLOnly(Set keys); + /** * Returns the number of entries in cache. * This number can reflects expired entries too diff --git a/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java b/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java index ef5eb4563..386b5cd25 100644 --- a/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java +++ b/redisson/src/main/java/org/redisson/api/RMapCacheAsync.java @@ -20,6 +20,7 @@ import org.redisson.api.map.MapWriter; import org.redisson.api.map.event.MapEntryListener; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -288,6 +289,20 @@ public interface RMapCacheAsync extends RMapAsync { */ RFuture getWithTTLOnlyAsync(K key); + /** + * Returns map slice contained the mappings with defined keys. + *

+ * If map doesn't contain value/values for specified key/keys and {@link MapLoader} is defined + * then value/values will be loaded in read-through mode. + *

+ * NOTE: Idle time of entry is not taken into account. + * Entry last access time isn't modified if map limited by size. + * + * @param keys map keys + * @return Map slice + */ + RFuture> getAllWithTTLOnlyAsync(Set keys); + /** * Returns the number of entries in cache. * This number can reflects expired entries too diff --git a/redisson/src/main/java/org/redisson/api/RMapCacheReactive.java b/redisson/src/main/java/org/redisson/api/RMapCacheReactive.java index ff7546bb7..eda21df6e 100644 --- a/redisson/src/main/java/org/redisson/api/RMapCacheReactive.java +++ b/redisson/src/main/java/org/redisson/api/RMapCacheReactive.java @@ -19,6 +19,8 @@ import org.redisson.api.map.MapLoader; import org.redisson.api.map.event.MapEntryListener; import reactor.core.publisher.Mono; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -273,6 +275,20 @@ public interface RMapCacheReactive extends RMapReactive, RDestroyabl */ Mono getWithTTLOnly(K key); + /** + * Returns map slice contained the mappings with defined keys. + *

+ * If map doesn't contain value/values for specified key/keys and {@link MapLoader} is defined + * then value/values will be loaded in read-through mode. + *

+ * NOTE: Idle time of entry is not taken into account. + * Entry last access time isn't modified if map limited by size. + * + * @param keys map keys + * @return Map slice + */ + Mono> getAllWithTTLOnly(Set keys); + /** * Returns the number of entries in cache. * This number can reflects expired entries too diff --git a/redisson/src/main/java/org/redisson/api/RMapCacheRx.java b/redisson/src/main/java/org/redisson/api/RMapCacheRx.java index b67c70a8b..41002f3de 100644 --- a/redisson/src/main/java/org/redisson/api/RMapCacheRx.java +++ b/redisson/src/main/java/org/redisson/api/RMapCacheRx.java @@ -21,6 +21,8 @@ import io.reactivex.rxjava3.core.Single; import org.redisson.api.map.MapLoader; import org.redisson.api.map.event.MapEntryListener; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; /** @@ -275,6 +277,20 @@ public interface RMapCacheRx extends RMapRx, RDestroyable { */ Maybe getWithTTLOnly(K key); + /** + * Returns map slice contained the mappings with defined keys. + *

+ * If map doesn't contain value/values for specified key/keys and {@link MapLoader} is defined + * then value/values will be loaded in read-through mode. + *

+ * NOTE: Idle time of entry is not taken into account. + * Entry last access time isn't modified if map limited by size. + * + * @param keys map keys + * @return Map slice + */ + Single> getAllWithTTLOnly(Set keys); + /** * Returns the number of entries in cache. * This number can reflects expired entries too diff --git a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java index 3bd65c3ac..7a55ca11f 100644 --- a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java +++ b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java @@ -49,6 +49,24 @@ public class RedissonMapCacheTest extends BaseMapTest { assertThat(mapCache.get("k1")).isEqualTo("v2"); } + @Test + public void testGetAllWithTTLOnly() throws InterruptedException { + RMapCache cache = redisson.getMapCache("testGetAllWithTTLOnly"); + cache.put(1, 2, 3, TimeUnit.SECONDS); + cache.put(3, 4, 1, TimeUnit.SECONDS); + cache.put(5, 6, 1, TimeUnit.SECONDS); + + Map map = cache.getAllWithTTLOnly(new HashSet<>(Arrays.asList(1, 3, 5))); + assertThat(map).containsOnlyKeys(1, 3, 5); + assertThat(map).containsValues(2, 4, 6); + + Thread.sleep(1500); + + map = cache.getAllWithTTLOnly(new HashSet<>(Arrays.asList(1, 3, 5))); + assertThat(map).containsOnlyKeys(1); + assertThat(map).containsValues(2); + } + @Test public void testGetWithTTLOnly() throws InterruptedException { RMapCache cache = redisson.getMapCache("testUpdateEntryExpiration");