diff --git a/redisson/src/main/java/org/redisson/RedissonMapCache.java b/redisson/src/main/java/org/redisson/RedissonMapCache.java index 546082ce8..353be86c0 100644 --- a/redisson/src/main/java/org/redisson/RedissonMapCache.java +++ b/redisson/src/main/java/org/redisson/RedissonMapCache.java @@ -51,6 +51,10 @@ import org.redisson.eviction.EvictionScheduler; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import java.io.IOException; +import java.math.BigDecimal; +import org.redisson.client.codec.StringCodec; +import org.redisson.client.protocol.convertor.NumberConvertor; /** *

Map-based cache with ability to set TTL for each entry via @@ -364,7 +368,7 @@ public class RedissonMapCache extends RedissonMap implements RMapCac return commandExecutor.evalWriteAsync(getName(key), codec, EVAL_PUT, "local value = struct.pack('dLc0', 0, string.len(ARGV[2]), ARGV[2]); " + "if redis.call('hsetnx', KEYS[1], ARGV[1], value) == 1 then " - + "return nil " + + "return nil;" + "else " + "local v = redis.call('hget', KEYS[1], ARGV[1]); " + "if v == false then " @@ -376,6 +380,56 @@ public class RedissonMapCache extends RedissonMap implements RMapCac Collections.singletonList(getName(key)), key, value); } + @Override + public V addAndGet(K key, Number value) { + return get(addAndGetAsync(key, value)); + } + + @Override + public RFuture addAndGetAsync(K key, Number value) { + byte[] keyState = encodeMapKey(key); + byte[] valueState; + try { + valueState = StringCodec.INSTANCE + .getMapValueEncoder() + .encode(new BigDecimal(value.toString()).toPlainString()); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + return commandExecutor.evalWriteAsync(getName(key), StringCodec.INSTANCE, + new RedisCommand("EVAL", new NumberConvertor(value.getClass())), + "local value = redis.call('hget', KEYS[1], ARGV[2]); " + + "local expireDate = 92233720368547758; " + + "local t = 0; " + + "local val = 0; " + + "if value ~= false then " + + "t, val = struct.unpack('dLc0', value); " + + "local expireDateScore = redis.call('zscore', KEYS[2], ARGV[2]); " + + "if expireDateScore ~= false then " + + "expireDate = tonumber(expireDateScore) " + + "end; " + + "if t ~= 0 then " + + "local expireIdle = redis.call('zscore', KEYS[3], ARGV[2]); " + + "if expireIdle ~= false then " + + "if tonumber(expireIdle) > tonumber(ARGV[1]) then " + + "local value = struct.pack('dLc0', t, string.len(val), val); " + + "redis.call('hset', KEYS[1], ARGV[2], value); " + + "redis.call('zadd', KEYS[3], t + tonumber(ARGV[1]), ARGV[2]); " + + "end; " + + "expireDate = math.min(expireDate, tonumber(expireIdle)) " + + "end; " + + "end; " + + "end; " + + "local newValue = tonumber(ARGV[3]); " + + "if expireDate >= tonumber(ARGV[1]) then " + + "newValue = tonumber(val) + newValue; " + + "end; " + + "local newValuePack = struct.pack('dLc0', t + tonumber(ARGV[1]), string.len(newValue), newValue); " + + "redis.call('hset', KEYS[1], ARGV[2], newValuePack); " + + "return tostring(newValue); ", + Arrays.asList(getName(key), getTimeoutSetNameByKey(key), getIdleSetNameByKey(key)), System.currentTimeMillis(), keyState, valueState); + } + @Override public boolean fastPut(K key, V value, long ttl, TimeUnit ttlUnit) { return get(fastPutAsync(key, value, ttl, ttlUnit)); diff --git a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java index 6a3c095db..ea87bd75d 100644 --- a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java +++ b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java @@ -20,6 +20,8 @@ import org.junit.Test; import org.redisson.api.RFuture; import org.redisson.api.RMap; import org.redisson.api.RMapCache; +import org.redisson.client.codec.LongCodec; +import org.redisson.client.codec.StringCodec; import org.redisson.codec.JsonJacksonCodec; import org.redisson.codec.MsgPackJacksonCodec; @@ -839,6 +841,31 @@ public class RedissonMapCacheTest extends BaseTest { } } + @Test + public void testIssue827() { + RMapCache mapCache = redisson.getMapCache("test_put_if_absent", StringCodec.INSTANCE); + mapCache.putIfAbsent("4", 0L, 10000L, TimeUnit.SECONDS); + mapCache.addAndGet("4", 1L); + mapCache.putIfAbsent("4", 0L); + Assert.assertEquals("1", mapCache.get("4")); + mapCache = redisson.getMapCache("test_put_if_absent_1", StringCodec.INSTANCE); + mapCache.putIfAbsent("4", 0L); + mapCache.addAndGet("4", 1L); + mapCache.putIfAbsent("4", 0L); + Assert.assertEquals("1", mapCache.get("4")); + RMap map = redisson.getMap("test_put_if_absent_2", StringCodec.INSTANCE); + map.putIfAbsent("4", 0L); + map.addAndGet("4", 1L); + map.putIfAbsent("4", 0L); + Assert.assertEquals("1", map.get("4")); + RMapCache mapCache1 = redisson.getMapCache("test_put_if_absent_3"); + Object currValue = mapCache1.putIfAbsent("4", 1.23, 10000L, TimeUnit.SECONDS); + Object updatedValue = mapCache1.addAndGet("4", 1D); + System.out.println("updatedValue: " + updatedValue); + Assert.assertEquals(2.23, mapCache1.get("4")); + } + + @Test public void testFastPutIfAbsentWithTTL() throws Exception { RMapCache map = redisson.getMapCache("simpleTTL");