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 8d1a955cf..85895bd5d 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundValueOperations.java @@ -63,6 +63,7 @@ public interface BoundValueOperations extends BoundKeyOperations { * @param timeout * @param unit must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. + * @since 2.1 * @see Redis Documentation: SET */ @Nullable 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 0d9384807..426decabe 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundValueOperations.java @@ -28,7 +28,7 @@ class DefaultBoundValueOperations extends DefaultBoundKeyOperations imp private final ValueOperations ops; /** - * Constructs a new DefaultBoundValueOperations instance. + * Constructs a new {@link DefaultBoundValueOperations} instance. * * @param key * @param operations 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 aacde0557..9fcc84fac 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveValueOperations.java @@ -86,10 +86,10 @@ 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) + * @see org.springframework.data.redis.core.ReactiveValueOperations#setIfAbsent(java.lang.Object, java.lang.Object, java.time.Duration) */ + @Override public Mono setIfAbsent(K key, V value, Duration timeout) { Assert.notNull(key, "Key must not be null!"); @@ -111,6 +111,19 @@ class DefaultReactiveValueOperations implements ReactiveValueOperations connection.set(rawKey(key), rawValue(value), Expiration.persistent(), SetOption.SET_IF_PRESENT)); } + /* (non-Javadoc) + * @see org.springframework.data.redis.core.ReactiveValueOperations#setIfPresent(java.lang.Object, java.lang.Object, java.time.Duration) + */ + @Override + public Mono setIfPresent(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_PRESENT)); + } + /* (non-Javadoc) * @see org.springframework.data.redis.core.ReactiveValueOperations#multiSet(java.util.Map) */ 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 d33578447..69b66a716 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultValueOperations.java @@ -24,7 +24,7 @@ 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.connection.RedisStringCommands.SetOption; import org.springframework.data.redis.core.types.Expiration; /** @@ -267,12 +267,12 @@ class DefaultValueOperations extends AbstractOperations implements V */ @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); + return execute(connection -> connection.set(rawKey, rawValue, expiration, SetOption.ifAbsent()), true); } /* 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 1e670b726..a33653e42 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveValueOperations.java @@ -65,6 +65,7 @@ public interface ReactiveValueOperations { * @param key must not be {@literal null}. * @param value * @param timeout must not be {@literal null}. + * @since 2.1 * @see Redis Documentation: SET */ Mono setIfAbsent(K key, V value, Duration timeout); @@ -78,6 +79,17 @@ public interface ReactiveValueOperations { */ Mono setIfPresent(K key, V value); + /** + * Set {@code key} to hold the string {@code value} and expiration {@code timeout} if {@code key} is present. + * + * @param key must not be {@literal null}. + * @param value + * @param timeout must not be {@literal null}. + * @since 2.1 + * @see Redis Documentation: SET + */ + Mono setIfPresent(K key, V value, Duration timeout); + /** * Set multiple keys to multiple values using key-value pairs provided in {@code tuple}. * 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 9cddf4971..853639931 100644 --- a/src/main/java/org/springframework/data/redis/core/ValueOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ValueOperations.java @@ -68,9 +68,10 @@ public interface ValueOperations { * * @param key must not be {@literal null}. * @param value - * @param timeout + * @param timeout must not be {@literal null}. * @param unit must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. + * @since 2.1 * @see Redis Documentation: SET */ @Nullable 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 4a22cbbc9..86f04d6e6 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultReactiveValueOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultReactiveValueOperationsIntegrationTests.java @@ -18,7 +18,6 @@ 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; @@ -142,16 +141,18 @@ public class DefaultReactiveValueOperationsIntegrationTests { K key = keyFactory.instance(); V value = valueFactory.instance(); - StepVerifier.create(valueOperations.setIfAbsent(key, value, Duration.ofMillis(500))).expectNext(true) + StepVerifier.create(valueOperations.setIfAbsent(key, value, Duration.ofSeconds(5))).expectNext(true) .expectComplete().verify(); StepVerifier.create(valueOperations.setIfAbsent(key, value)).expectNext(false).verifyComplete(); - StepVerifier.create(valueOperations.setIfAbsent(key, value, Duration.ofMillis(500))).expectNext(false) + StepVerifier.create(valueOperations.setIfAbsent(key, value, Duration.ofSeconds(5))).expectNext(false) .verifyComplete(); - Mono mono = valueOperations.setIfAbsent(key, value, Duration.ofMillis(500)) - .delaySubscription(Duration.ofMillis(500)); - StepVerifier.create(mono).expectNext(true).verifyComplete(); + StepVerifier.create(redisTemplate.getExpire(key)) // + .assertNext(actual -> { + + assertThat(actual).isBetween(Duration.ofMillis(1), Duration.ofSeconds(5)); + }).verifyComplete(); } @Test // DATAREDIS-602, DATAREDIS-779 @@ -170,6 +171,30 @@ public class DefaultReactiveValueOperationsIntegrationTests { StepVerifier.create(valueOperations.get(key)).expectNext(laterValue).verifyComplete(); } + @Test // DATAREDIS-782 + public void setIfPresentWithExpiry() { + + K key = keyFactory.instance(); + V value = valueFactory.instance(); + V laterValue = valueFactory.instance(); + + StepVerifier.create(valueOperations.setIfPresent(key, value, Duration.ofSeconds(5))).expectNext(false) + .verifyComplete(); + + StepVerifier.create(valueOperations.set(key, value, Duration.ofSeconds(5))).expectNext(true).verifyComplete(); + + StepVerifier.create(valueOperations.setIfPresent(key, laterValue, Duration.ofSeconds(5))).expectNext(true) + .verifyComplete(); + + StepVerifier.create(valueOperations.get(key)).expectNext(laterValue).verifyComplete(); + + StepVerifier.create(redisTemplate.getExpire(key)) // + .assertNext(actual -> { + + assertThat(actual).isBetween(Duration.ofMillis(1), Duration.ofSeconds(5)); + }).verifyComplete(); + } + @Test // DATAREDIS-602 public void multiSet() { 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 b51623904..d2d7c1e6f 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultValueOperationsTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultValueOperationsTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.redis.core; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.junit.Assume.*; import static org.springframework.data.redis.SpinBarrier.*; @@ -47,6 +48,7 @@ import org.springframework.data.redis.RedisTestProfileValueSource; * @author Christoph Strobl * @author David Liu * @author Thomas Darimont + * @author Jiahe Cai */ @RunWith(Parameterized.class) public class DefaultValueOperationsTests { @@ -237,13 +239,15 @@ public class DefaultValueOperationsTests { @Test // DATAREDIS-782 public void testSetIfAbsentWithExpiration() { - final K key1 = keyFactory.instance(); + K key = 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); + assertTrue(valueOps.setIfAbsent(key, value1, 5, TimeUnit.SECONDS)); + assertFalse(valueOps.setIfAbsent(key, value2)); + assertFalse(valueOps.setIfAbsent(key, value2, 5, TimeUnit.SECONDS)); + Long expire = redisTemplate.getExpire(key, TimeUnit.MILLISECONDS); + assertThat(expire, is(lessThan(TimeUnit.SECONDS.toMillis(6)))); + assertThat(expire, is(greaterThan(TimeUnit.MILLISECONDS.toMillis(1)))); } @Test