From 5ee0bd33bc9640760d7c604481d406c2b8d0219e Mon Sep 17 00:00:00 2001 From: ihaohong Date: Tue, 4 May 2021 18:55:20 +0800 Subject: [PATCH] Add support for COPY via RedisOperations see #2040 --- .../DefaultStringRedisConnection.java | 19 +++++++++++++++++++ .../connection/DefaultedRedisConnection.java | 8 ++++++++ .../redis/connection/RedisKeyCommands.java | 12 ++++++++++++ .../connection/StringRedisConnection.java | 13 +++++++++++++ .../jedis/JedisClusterKeyCommands.java | 13 +++++++++++++ .../connection/jedis/JedisKeyCommands.java | 12 ++++++++++++ .../connection/lettuce/LettuceConnection.java | 2 ++ .../lettuce/LettuceKeyCommands.java | 13 +++++++++++++ .../data/redis/core/RedisOperations.java | 12 ++++++++++++ .../data/redis/core/RedisTemplate.java | 10 ++++++++++ ...ultStringRedisConnectionPipelineTests.java | 6 ++++++ ...tStringRedisConnectionPipelineTxTests.java | 7 +++++++ .../DefaultStringRedisConnectionTests.java | 8 ++++++++ .../connection/RedisConnectionUnitTests.java | 4 ++++ .../core/RedisTemplateIntegrationTests.java | 15 +++++++++++++++ 15 files changed, 154 insertions(+) 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 89820ee1e8..6da75c6c76 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -70,6 +70,7 @@ * @author Tugdual Grall * @author Andrey Shlykov * @author dengliming + * @author ihaohong */ public class DefaultStringRedisConnection implements StringRedisConnection, DecoratedRedisConnection { @@ -276,6 +277,15 @@ public Long del(byte[]... keys) { return convertAndReturn(delegate.del(keys), Converters.identityConverter()); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) + */ + @Override + public Boolean copy(byte[] sourceKey, byte[] targetKey) { + return convertAndReturn(delegate.copy(sourceKey, targetKey), Converters.identityConverter()); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) @@ -1899,6 +1909,15 @@ public Long del(String... keys) { return del(serializeMulti(keys)); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#copy(java.lang.String[]) + */ + @Override + public Boolean copy(String sourceKey, String targetKey) { + return copy(serialize(sourceKey), serialize(targetKey)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#unlink(java.lang.String[]) 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 6178634997..d70a785da7 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -56,6 +56,7 @@ * @author Mark Paluch * @author Tugdual Grall * @author Andrey Shlykov + * @author ihaohong * @since 2.0 */ public interface DefaultedRedisConnection extends RedisConnection { @@ -83,6 +84,13 @@ default Long del(byte[]... keys) { return keyCommands().del(keys); } + /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ + @Override + @Deprecated + default Boolean copy(byte[] sourceKey, byte[] targetKey) { + return keyCommands().copy(sourceKey, targetKey); + } + /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ @Override @Deprecated diff --git a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java index 9716a4a30a..908c6ed60a 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java @@ -31,6 +31,7 @@ * @author Costin Leau * @author Christoph Strobl * @author Mark Paluch + * @author ihaohong */ public interface RedisKeyCommands { @@ -71,6 +72,17 @@ default Boolean exists(byte[] key) { @Nullable Long del(byte[]... keys); + /** + * Copy given {@code sourceKey} to {@code targetKey}. + * + * @param sourceKey must not be {@literal null}. + * @param targetKey must not be {@literal null}. + * @return + * @see Redis Documentation: COPY + */ + @Nullable + Boolean copy(byte[] sourceKey, byte[] targetKey); + /** * Unlink the {@code keys} from the keyspace. Unlike with {@link #del(byte[]...)} the actual memory reclaiming here * happens asynchronously. diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java index c3f2ad9fbe..c3d87498ad 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -64,6 +64,8 @@ * @author Tugdual Grall * @author Dengliming * @author Andrey Shlykov + * @author ihaohong + * * @see RedisCallback * @see RedisSerializer * @see StringRedisTemplate @@ -132,6 +134,17 @@ interface StringTuple extends Tuple { */ Long del(String... keys); + /** + * Copy given {@code sourceKey} to {@code targetKey}. + * + * @param sourceKey must not be {@literal null}. + * @param targetKey must not be {@literal null}. + * @return + * @see Redis Documentation: COPY + * @see RedisKeyCommands#copy(byte[], byte[]) + */ + Boolean copy(String sourceKey, String targetKey); + /** * Unlink the {@code keys} from the keyspace. Unlike with {@link #del(String...)} the actual memory reclaiming here * happens asynchronously. diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java index 627ab54e7b..a789371873 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java @@ -53,6 +53,7 @@ /** * @author Christoph Strobl * @author Mark Paluch + * @author ihaohong * @since 2.0 */ class JedisClusterKeyCommands implements RedisKeyCommands { @@ -87,6 +88,18 @@ public Long del(byte[]... keys) { .resultsAsList().size(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) + */ + @Override + public Boolean copy(byte[] sourceKey, byte[] targetKey) { + Assert.notNull(sourceKey, "source key must not be null!"); + Assert.notNull(targetKey, "target key must not be null!"); + + return connection.getCluster().copy(sourceKey, targetKey, false); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java index 969727ce94..81cfb1d5b6 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java @@ -41,6 +41,7 @@ /** * @author Christoph Strobl * @author Mark Paluch + * @author ihaohong * @since 2.0 */ class JedisKeyCommands implements RedisKeyCommands { @@ -90,6 +91,17 @@ public Long del(byte[]... keys) { return connection.invoke().just(BinaryJedis::del, MultiKeyPipelineBase::del, keys); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) + */ + public Boolean copy(byte[] sourceKey, byte[] targetKey) { + Assert.notNull(sourceKey, "source key must not be null!"); + Assert.notNull(targetKey, "target key must not be null!"); + + return connection.invoke().just(BinaryJedis::copy, MultiKeyPipelineBase::copy, sourceKey, targetKey, false); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java index 708344cc28..edac71ad27 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java @@ -86,6 +86,7 @@ * @author Mark Paluch * @author Ninad Divadkar * @author Tamil Selvan + * @author ihaohong */ public class LettuceConnection extends AbstractRedisConnection { @@ -1161,6 +1162,7 @@ static class TypeHints { COMMAND_OUTPUT_TYPE_MAPPING.put(DECR, IntegerOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(DECRBY, IntegerOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(DEL, IntegerOutput.class); + COMMAND_OUTPUT_TYPE_MAPPING.put(COPY, IntegerOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(GETBIT, IntegerOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(HDEL, IntegerOutput.class); COMMAND_OUTPUT_TYPE_MAPPING.put(HINCRBY, IntegerOutput.class); diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java index f69cb7b832..3ff5da5f05 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java @@ -41,6 +41,7 @@ /** * @author Christoph Strobl * @author Mark Paluch + * @author ihaohong * @since 2.0 */ class LettuceKeyCommands implements RedisKeyCommands { @@ -90,6 +91,18 @@ public Long del(byte[]... keys) { return connection.invoke().just(RedisKeyAsyncCommands::del, keys); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#copy(byte[], byte[]) + */ + @Override + public Boolean copy(byte[] sourceKey, byte[] targetKey) { + Assert.notNull(sourceKey, "source key must not be null!"); + Assert.notNull(targetKey, "target key must not be null!"); + + return connection.invoke().just(RedisKeyAsyncCommands::copy, targetKey, sourceKey); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisKeyCommands#unlink(byte[][]) diff --git a/src/main/java/org/springframework/data/redis/core/RedisOperations.java b/src/main/java/org/springframework/data/redis/core/RedisOperations.java index 14fdfbba64..5ac17f9e04 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/RedisOperations.java @@ -43,6 +43,7 @@ * @author Christoph Strobl * @author Ninad Divadkar * @author Mark Paluch + * @author ihaohong */ public interface RedisOperations { @@ -199,6 +200,17 @@ T execute(RedisScript script, RedisSerializer argsSerializer, RedisSer @Nullable Long delete(Collection keys); + /** + * Copy given {@code sourceKey} to {@code targetKey}. + * + * @param sourceKey must not be {@literal null}. + * @param targetKey must not be {@literal null}. + * @return + * @see Redis Documentation: COPY + */ + @Nullable + Boolean copy(K sourceKey, K targetKey); + /** * Unlink the {@code key} from the keyspace. Unlike with {@link #delete(Object)} the actual memory reclaiming here * happens asynchronously. diff --git a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java index be39541513..6448a00fc9 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -82,6 +82,8 @@ * @author Anqing Shao * @author Mark Paluch * @author Denis Zavedeev + * @author ihaohong + * * @param the Redis key type against which the template works (usually a String) * @param the Redis value type against which the template works * @see StringRedisTemplate @@ -710,6 +712,14 @@ public Boolean delete(K key) { return result != null && result.intValue() == 1; } + @Override + public Boolean copy(K source, K target) { + byte[] sourceKey = rawKey(source); + byte[] targetKey = rawKey(target); + + return execute(connection -> connection.copy(sourceKey, targetKey), true); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#delete(java.util.Collection) diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java index 84e501c269..0a0348aa6b 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java @@ -137,6 +137,12 @@ public void testDel() { super.testDel(); } + @Test + public void testCopy() { + doReturn(Collections.singletonList(Boolean.TRUE)).when(nativeConnection).closePipeline(); + super.testCopy(); + } + @Test public void testEchoBytes() { doReturn(Arrays.asList(new Object[] { barBytes })).when(nativeConnection).closePipeline(); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java index 882d11f595..86f302d2d4 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java @@ -141,6 +141,13 @@ public void testDel() { super.testDel(); } + @Test + public void testCopy() { + doReturn(Collections.singletonList(Collections.singletonList(Boolean.TRUE))).when(nativeConnection).closePipeline(); + super.testCopy(); + } + + @Test public void testEchoBytes() { doReturn(Collections.singletonList(Arrays.asList(new Object[] { barBytes }))).when(nativeConnection) diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java index 0bce55b824..b09c19e666 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java @@ -70,6 +70,7 @@ * @author Christoph Strobl * @author Ninad Divadkar * @author Mark Paluch + * @author ihaohong */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -234,6 +235,13 @@ public void testDel() { verifyResults(Collections.singletonList(1L)); } + @Test + public void testCopy() { + doReturn(Boolean.TRUE).when(nativeConnection).copy(fooBytes, barBytes); + actual.add(connection.copy(foo, bar)); + verifyResults(Collections.singletonList(Boolean.TRUE)); + } + @Test public void testEchoBytes() { doReturn(barBytes).when(nativeConnection).echo(fooBytes); diff --git a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java index 4f730ced5a..318549d098 100644 --- a/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/RedisConnectionUnitTests.java @@ -187,6 +187,10 @@ public Long del(byte[]... keys) { return delegate.del(keys); } + public Boolean copy(byte[] sourceKey, byte[] targetKey) { + return delegate.copy(sourceKey, targetKey); + } + public void close() throws DataAccessException { super.close(); } diff --git a/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java index 55e3146ad6..5a421a1d4d 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java @@ -58,6 +58,7 @@ * @author Anqing Shao * @author Duobiao Ou * @author Mark Paluch + * @author ihaohong */ @MethodSource("testParams") public class RedisTemplateIntegrationTests { @@ -390,6 +391,20 @@ void testDelete() { assertThat(redisTemplate.hasKey(key1)).isFalse(); } + @ParameterizedRedisTest + void testCopy() { + K key1 = keyFactory.instance(); + K key2 = keyFactory.instance(); + V value1 = valueFactory.instance(); + + redisTemplate.opsForValue().set(key1, value1); + + assertThat(redisTemplate.hasKey(key2)).isFalse(); + redisTemplate.copy(key1, key2); + assertThat(redisTemplate.hasKey(key2)).isTrue(); + assertThat(redisTemplate.opsForValue().get(key2)).isEqualTo(value1); + } + @ParameterizedRedisTest // DATAREDIS-688 void testDeleteMultiple() {