diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index 5bd90eb9c..57b86e8af 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -69,6 +69,7 @@ import org.springframework.util.ObjectUtils; * @author Ninad Divadkar * @author Tugdual Grall * @author Andrey Shlykov + * @author dengliming */ public class DefaultStringRedisConnection implements StringRedisConnection, DecoratedRedisConnection { @@ -992,6 +993,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.set(key, value, expiration, option), identityConverter); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOptions, boolean) + */ + @Override + public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option, boolean keepTtl) { + return convertAndReturn(delegate.set(key, value, expiration, option, keepTtl), identityConverter); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisStringCommands#setBit(byte[], long, boolean) diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java index 4b66f1fdf..0cf9bbbf0 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -294,6 +294,13 @@ public interface DefaultedRedisConnection extends RedisConnection { return stringCommands().set(key, value, expiration, option); } + /** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */ + @Override + @Deprecated + default Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option, boolean keepTtl) { + return stringCommands().set(key, value, expiration, option, keepTtl); + } + /** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */ @Override @Deprecated diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java index fab800afd..8e888c343 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java @@ -92,6 +92,22 @@ public interface RedisStringCommands { @Nullable Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option); + /** + * Set {@code value} for {@code key} applying timeouts from {@code expiration} if set and inserting/updating values + * depending on {@code option}. + * + * @param key must not be {@literal null}. + * @param value must not be {@literal null}. + * @param expiration must not be {@literal null}. Use {@link Expiration#persistent()} to not set any ttl. + * @param option must not be {@literal null}. Use {@link SetOption#upsert()} to add non existing. + * @param keepTtl set the value and retain the existing TTL. + * @return {@literal null} when used in pipeline / transaction. + * @since 2.4 + * @see Redis Documentation: SET + */ + @Nullable + Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option, boolean keepTtl); + /** * Set {@code value} for {@code key}, only if {@code key} does not exist. * diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterStringCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterStringCommands.java index cc39ec59f..99d67bc48 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterStringCommands.java @@ -41,6 +41,7 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Mark Paluch * @author Xiaohu Zhang + * @author dengliming * @since 2.0 */ class JedisClusterStringCommands implements RedisStringCommands { @@ -126,11 +127,20 @@ class JedisClusterStringCommands implements RedisStringCommands { */ @Override public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option) { + return set(key, value, expiration, option, false); + } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOptions, boolean) + */ + @Override + public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option, boolean keepTtl) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(value, "Value must not be null!"); Assert.notNull(expiration, "Expiration must not be null!"); Assert.notNull(option, "Option must not be null!"); + Assert.isTrue(keepTtl, "KEEPTTL is currently not supported in jedis!"); SetParams setParams = JedisConverters.toSetCommandExPxArgument(expiration, JedisConverters.toSetCommandNxXxArgument(option)); diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisStringCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisStringCommands.java index 2b47ada13..44c654ec6 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisStringCommands.java @@ -34,6 +34,7 @@ import org.springframework.util.Assert; /** * @author Christoph Strobl * @author Mark Paluch + * @author dengliming * @since 2.0 */ class JedisStringCommands implements RedisStringCommands { @@ -152,11 +153,20 @@ class JedisStringCommands implements RedisStringCommands { */ @Override public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option) { + return set(key, value, expiration, option, false); + } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOption, boolean) + */ + @Override + public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option, boolean keepTtl) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(value, "Value must not be null!"); Assert.notNull(expiration, "Expiration must not be null!"); Assert.notNull(option, "Option must not be null!"); + Assert.isTrue(keepTtl, "KEEPTTL is currently not supported in jedis!"); SetParams params = JedisConverters.toSetCommandExPxArgument(expiration, JedisConverters.toSetCommandNxXxArgument(option)); diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java index acd8442bf..aa22c4284 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java @@ -82,6 +82,7 @@ import org.springframework.util.StringUtils; * @author Thomas Darimont * @author Mark Paluch * @author Ninad Divadkar + * @author dengliming */ abstract public class LettuceConverters extends Converters { @@ -767,6 +768,18 @@ abstract public class LettuceConverters extends Converters { * @since 1.7 */ public static SetArgs toSetArgs(@Nullable Expiration expiration, @Nullable SetOption option) { + return toSetArgs(expiration, option, false); + } + + /** + * Converts a given {@link Expiration} and {@link SetOption} to the according {@link SetArgs}.
+ * + * @param expiration can be {@literal null}. + * @param option can be {@literal null}. + * @param keepTtl set the value and retain the existing TTL. + * @since 2.4 + */ + public static SetArgs toSetArgs(@Nullable Expiration expiration, @Nullable SetOption option, boolean keepTtl) { SetArgs args = new SetArgs(); if (expiration != null && !expiration.isPersistent()) { @@ -781,6 +794,10 @@ abstract public class LettuceConverters extends Converters { } } + if (keepTtl) { + args.keepttl(); + } + if (option != null) { switch (option) { diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java index 0de36a677..f60374898 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java @@ -36,6 +36,7 @@ import org.springframework.util.Assert; /** * @author Christoph Strobl * @author Mark Paluch + * @author dengliming * @since 2.0 */ class LettuceStringCommands implements RedisStringCommands { @@ -156,7 +157,15 @@ class LettuceStringCommands implements RedisStringCommands { */ @Override public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option) { + return set(key, value, expiration, option, false); + } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisStringCommands#set(byte[], byte[], org.springframework.data.redis.core.types.Expiration, org.springframework.data.redis.connection.RedisStringCommands.SetOption, boolean) + */ + @Override + public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption option, boolean keepTtl) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(value, "Value must not be null!"); Assert.notNull(expiration, "Expiration must not be null!"); @@ -165,18 +174,18 @@ class LettuceStringCommands implements RedisStringCommands { try { if (isPipelined()) { pipeline(connection.newLettuceResult( - getAsyncConnection().set(key, value, LettuceConverters.toSetArgs(expiration, option)), + getAsyncConnection().set(key, value, LettuceConverters.toSetArgs(expiration, option, keepTtl)), Converters.stringToBooleanConverter(), () -> false)); return null; } if (isQueueing()) { transaction(connection.newLettuceResult( - getAsyncConnection().set(key, value, LettuceConverters.toSetArgs(expiration, option)), + getAsyncConnection().set(key, value, LettuceConverters.toSetArgs(expiration, option, keepTtl)), Converters.stringToBooleanConverter(), () -> false)); return null; } return Converters - .stringToBoolean(getConnection().set(key, value, LettuceConverters.toSetArgs(expiration, option))); + .stringToBoolean(getConnection().set(key, value, LettuceConverters.toSetArgs(expiration, option, keepTtl))); } catch (Exception ex) { throw convertLettuceAccessException(ex); } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java index 46d278569..2d525ed71 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java @@ -16,6 +16,7 @@ package org.springframework.data.redis.connection.lettuce; import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.Offset.offset; import static org.springframework.data.redis.connection.BitFieldSubCommands.*; import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow.*; @@ -2466,4 +2467,19 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests { .valueAt(BitFieldSubCommands.Offset.offset(1L).multipliedByTypeLength()))).containsExactly(100L, -56L); } + + @Test // DATAREDIS-1103 + public void setKeepTTL() { + + long expireSeconds = 10; + assertThat(clusterConnection.setEx(KEY_1_BYTES, expireSeconds, VALUE_2_BYTES)); + + assertThat(clusterConnection.get(KEY_1_BYTES)).isEqualTo(VALUE_2_BYTES); + + assertThat(clusterConnection.set(KEY_1_BYTES, VALUE_2_BYTES, Expiration.persistent(), SetOption.upsert(), true)); + + long ttl = clusterConnection.ttl(KEY_1_BYTES); + + assertThat(0 < ttl && ttl <= expireSeconds); + } }