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 b10831981..d5d5387e2 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 @@ import org.springframework.util.ObjectUtils; * @author Tugdual Grall * @author Andrey Shlykov * @author dengliming + * @author ihaohong */ public class DefaultStringRedisConnection implements StringRedisConnection, DecoratedRedisConnection { @@ -276,6 +277,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco 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[][]) @@ -1917,6 +1927,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco 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 804430c7e..c963dc25b 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -57,6 +57,7 @@ import org.springframework.lang.Nullable; * @author Tugdual Grall * @author Andrey Shlykov * @author dengliming + * @author ihaohong * @since 2.0 */ public interface DefaultedRedisConnection extends RedisConnection { @@ -84,6 +85,13 @@ public interface DefaultedRedisConnection extends RedisConnection { 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 9716a4a30..908c6ed60 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 @@ import org.springframework.util.Assert; * @author Costin Leau * @author Christoph Strobl * @author Mark Paluch + * @author ihaohong */ public interface RedisKeyCommands { @@ -71,6 +72,17 @@ public interface RedisKeyCommands { @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 5b812cedb..261921e90 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 @@ import org.springframework.util.CollectionUtils; * @author Tugdual Grall * @author Dengliming * @author Andrey Shlykov + * @author ihaohong + * * @see RedisCallback * @see RedisSerializer * @see StringRedisTemplate @@ -132,6 +134,17 @@ public interface StringRedisConnection extends RedisConnection { */ 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 627ab54e7..a78937187 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 @@ import org.springframework.util.ObjectUtils; /** * @author Christoph Strobl * @author Mark Paluch + * @author ihaohong * @since 2.0 */ class JedisClusterKeyCommands implements RedisKeyCommands { @@ -87,6 +88,18 @@ class JedisClusterKeyCommands implements RedisKeyCommands { .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 cf1e50192..a78b99aca 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 @@ import org.springframework.util.Assert; /** * @author Christoph Strobl * @author Mark Paluch + * @author ihaohong * @since 2.0 */ class JedisKeyCommands implements RedisKeyCommands { @@ -90,6 +91,17 @@ class JedisKeyCommands implements RedisKeyCommands { 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 708344cc2..edac71ad2 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 @@ import org.springframework.util.ObjectUtils; * @author Mark Paluch * @author Ninad Divadkar * @author Tamil Selvan + * @author ihaohong */ public class LettuceConnection extends AbstractRedisConnection { @@ -1161,6 +1162,7 @@ public class LettuceConnection extends AbstractRedisConnection { 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 f69cb7b83..3ff5da5f0 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 @@ import org.springframework.util.Assert; /** * @author Christoph Strobl * @author Mark Paluch + * @author ihaohong * @since 2.0 */ class LettuceKeyCommands implements RedisKeyCommands { @@ -90,6 +91,18 @@ class LettuceKeyCommands implements RedisKeyCommands { 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 14fdfbba6..5ac17f9e0 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 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Ninad Divadkar * @author Mark Paluch + * @author ihaohong */ public interface RedisOperations { @@ -199,6 +200,17 @@ public interface RedisOperations { @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 be3954151..6448a00fc 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 @@ import org.springframework.util.CollectionUtils; * @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 class RedisTemplate extends RedisAccessor implements RedisOperation 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 aff3ba5b0..e7f270765 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java @@ -138,6 +138,12 @@ public class DefaultStringRedisConnectionPipelineTests extends DefaultStringRedi 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 852174a2d..d7bc4474a 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java @@ -142,6 +142,13 @@ public class DefaultStringRedisConnectionPipelineTxTests extends DefaultStringRe 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 c91725cbc..e68204866 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java @@ -71,6 +71,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; * @author Ninad Divadkar * @author Mark Paluch * @author dengliming + * @author ihaohong */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -235,6 +236,13 @@ public class DefaultStringRedisConnectionTests { 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 4f730ced5..318549d09 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 @@ class RedisConnectionUnitTests { 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 55e3146ad..5a421a1d4 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 @@ import org.springframework.data.redis.test.util.CollectionAwareComparator; * @author Anqing Shao * @author Duobiao Ou * @author Mark Paluch + * @author ihaohong */ @MethodSource("testParams") public class RedisTemplateIntegrationTests { @@ -390,6 +391,20 @@ public class RedisTemplateIntegrationTests { 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() {