From a2ed8e1d9bae0be78d8f0cd6a1f9f74dab842389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiahe=20Cai=20=28=E4=BD=B3=E4=BD=95=20=E8=94=A1=29?= <739416782@qq.com> Date: Fri, 9 Mar 2018 17:54:53 +0800 Subject: [PATCH] DATAREDIS-782 - Add support for SET key value NX EX max-lock-time. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now expose ValueOperations.setIfAbsent(…) to set values when absent along their expiry. Original pull request: #320. --- .../data/redis/core/BoundValueOperations.java | 13 +++++++++++++ .../core/DefaultBoundValueOperations.java | 10 ++++++++++ .../core/DefaultReactiveValueOperations.java | 14 ++++++++++++++ .../redis/core/DefaultValueOperations.java | 17 +++++++++++++++++ .../redis/core/ReactiveValueOperations.java | 11 +++++++++++ .../data/redis/core/ValueOperations.java | 14 ++++++++++++++ ...activeValueOperationsIntegrationTests.java | 19 +++++++++++++++++++ .../core/DefaultValueOperationsTests.java | 11 +++++++++++ 8 files changed, 109 insertions(+) diff --git a/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java b/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java index f724402d5..8d1a955cf 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java @@ -24,6 +24,7 @@ import org.springframework.lang.Nullable; * * @author Costin Leau * @author Mark Paluch + * @author Jiahe Cai */ public interface BoundValueOperations extends BoundKeyOperations { @@ -55,6 +56,18 @@ public interface BoundValueOperations extends BoundKeyOperations { @Nullable Boolean setIfAbsent(V value); + /** + * Set the bound key to hold the string {@code value} and expiration {@code timeout} if the bound key is absent. + * + * @param value must not be {@literal null}. + * @param timeout + * @param unit must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SET + */ + @Nullable + Boolean setIfAbsent(V value, long timeout, TimeUnit unit); + /** * Get the value of the bound key. * diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundValueOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundValueOperations.java index 742f462de..0d9384807 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundValueOperations.java @@ -21,6 +21,7 @@ import org.springframework.data.redis.connection.DataType; /** * @author Costin Leau + * @author Jiahe Cai */ class DefaultBoundValueOperations extends DefaultBoundKeyOperations implements BoundValueOperations { @@ -119,6 +120,15 @@ class DefaultBoundValueOperations extends DefaultBoundKeyOperations imp return ops.setIfAbsent(getKey(), value); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.BoundValueOperations#setIfAbsent(java.lang.Object, long, java.util.concurrent.TimeUnit) + */ + @Override + public Boolean setIfAbsent(V value, long timeout, TimeUnit unit) { + return ops.setIfAbsent(getKey(), value, timeout, unit); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.BoundValueOperations#set(java.lang.Object, long) diff --git a/src/main/java/org/springframework/data/redis/core/DefaultReactiveValueOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultReactiveValueOperations.java index c38ad36af..aacde0557 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveValueOperations.java @@ -41,6 +41,7 @@ import org.springframework.util.Assert; * * @author Mark Paluch * @author Christoph Strobl + * @author Jiahe Cai * @since 2.0 */ @RequiredArgsConstructor @@ -85,6 +86,19 @@ class DefaultReactiveValueOperations implements ReactiveValueOperations connection.set(rawKey(key), rawValue(value), Expiration.persistent(), SetOption.SET_IF_ABSENT)); } + @Override + /* (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveValueOperations#setIfAbsent(java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit) + */ + public Mono setIfAbsent(K key, V value, Duration timeout) { + + Assert.notNull(key, "Key must not be null!"); + Assert.notNull(timeout, "Duration must not be null!"); + + return createMono( + connection -> connection.set(rawKey(key), rawValue(value), Expiration.from(timeout), SetOption.SET_IF_ABSENT)); + } + /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#setIfPresent(java.lang.Object, java.lang.Object) */ diff --git a/src/main/java/org/springframework/data/redis/core/DefaultValueOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultValueOperations.java index 8dadef469..d33578447 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultValueOperations.java @@ -24,6 +24,8 @@ import java.util.concurrent.TimeUnit; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisStringCommands; +import org.springframework.data.redis.core.types.Expiration; /** * Default implementation of {@link ValueOperations}. @@ -31,6 +33,7 @@ import org.springframework.data.redis.connection.RedisConnection; * @author Costin Leau * @author Jennifer Hickey * @author Christoph Strobl + * @author Jiahe Cai */ class DefaultValueOperations extends AbstractOperations implements ValueOperations { @@ -258,6 +261,20 @@ class DefaultValueOperations extends AbstractOperations implements V return execute(connection -> connection.setNX(rawKey, rawValue), true); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.core.ValueOperations#setIfAbsent(java.lang.Object, java.lang.Object, long, java.util.concurrent.TimeUnit) + */ + @Override + public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) { + byte[] rawKey = rawKey(key); + byte[] rawValue = rawValue(value); + + Expiration expiration = Expiration.from(timeout, unit); + RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent(); + return execute(connection -> connection.set(rawKey, rawValue, expiration, setOption), true); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.ValueOperations#set(java.lang.Object, java.lang.Object, long) diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveValueOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveValueOperations.java index aca7d9b8b..1e670b726 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveValueOperations.java @@ -26,6 +26,7 @@ import java.util.Map; * Reactive Redis operations for simple (or in Redis terminology 'string') values. * * @author Mark Paluch + * @author Jiahe Cai * @since 2.0 */ public interface ReactiveValueOperations { @@ -58,6 +59,16 @@ public interface ReactiveValueOperations { */ Mono setIfAbsent(K key, V value); + /** + * Set {@code key} to hold the string {@code value} and expiration {@code timeout} if {@code key} is absent. + * + * @param key must not be {@literal null}. + * @param value + * @param timeout must not be {@literal null}. + * @see Redis Documentation: SET + */ + Mono setIfAbsent(K key, V value, Duration timeout); + /** * Set {@code key} to hold the string {@code value} if {@code key} is present. * diff --git a/src/main/java/org/springframework/data/redis/core/ValueOperations.java b/src/main/java/org/springframework/data/redis/core/ValueOperations.java index e54377732..9cddf4971 100644 --- a/src/main/java/org/springframework/data/redis/core/ValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ValueOperations.java @@ -28,6 +28,7 @@ import org.springframework.lang.Nullable; * @author Costin Leau * @author Christoph Strobl * @author Mark Paluch + * @author Jiahe Cai */ public interface ValueOperations { @@ -62,6 +63,19 @@ public interface ValueOperations { @Nullable Boolean setIfAbsent(K key, V value); + /** + * Set {@code key} to hold the string {@code value} and expiration {@code timeout} if {@code key} is absent. + * + * @param key must not be {@literal null}. + * @param value + * @param timeout + * @param unit must not be {@literal null}. + * @return {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: SET + */ + @Nullable + Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit); + /** * Set multiple keys to multiple values using key-value pairs provided in {@code tuple}. * diff --git a/src/test/java/org/springframework/data/redis/core/DefaultReactiveValueOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultReactiveValueOperationsIntegrationTests.java index c0200d88a..4a22cbbc9 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultReactiveValueOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultReactiveValueOperationsIntegrationTests.java @@ -18,6 +18,7 @@ package org.springframework.data.redis.core; import static org.assertj.core.api.Assertions.*; import static org.junit.Assume.*; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.nio.ByteBuffer; @@ -135,6 +136,24 @@ public class DefaultReactiveValueOperationsIntegrationTests { StepVerifier.create(valueOperations.setIfAbsent(key, value)).expectNext(false).verifyComplete(); } + @Test // DATAREDIS-782 + public void setIfAbsentWithExpiry() { + + K key = keyFactory.instance(); + V value = valueFactory.instance(); + + StepVerifier.create(valueOperations.setIfAbsent(key, value, Duration.ofMillis(500))).expectNext(true) + .expectComplete().verify(); + + StepVerifier.create(valueOperations.setIfAbsent(key, value)).expectNext(false).verifyComplete(); + StepVerifier.create(valueOperations.setIfAbsent(key, value, Duration.ofMillis(500))).expectNext(false) + .verifyComplete(); + + Mono mono = valueOperations.setIfAbsent(key, value, Duration.ofMillis(500)) + .delaySubscription(Duration.ofMillis(500)); + StepVerifier.create(mono).expectNext(true).verifyComplete(); + } + @Test // DATAREDIS-602, DATAREDIS-779 public void setIfPresent() { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultValueOperationsTests.java b/src/test/java/org/springframework/data/redis/core/DefaultValueOperationsTests.java index 1a9db55ac..b51623904 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultValueOperationsTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultValueOperationsTests.java @@ -235,6 +235,17 @@ public class DefaultValueOperationsTests { assertFalse(valueOps.setIfAbsent(key1, value2)); } + @Test // DATAREDIS-782 + public void testSetIfAbsentWithExpiration() { + final K key1 = keyFactory.instance(); + V value1 = valueFactory.instance(); + V value2 = valueFactory.instance(); + assertTrue(valueOps.setIfAbsent(key1, value1, 500, TimeUnit.MILLISECONDS)); + assertFalse(valueOps.setIfAbsent(key1, value2)); + assertFalse(valueOps.setIfAbsent(key1, value2, 500, TimeUnit.MILLISECONDS)); + waitFor(() -> (valueOps.setIfAbsent(key1, value1, 500, TimeUnit.MILLISECONDS)), 500); + } + @Test public void testSize() { K key1 = keyFactory.instance();