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 f9e378e3b..e9a153b56 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/RedisOperations.java @@ -45,6 +45,7 @@ import org.springframework.util.Assert; * @author Mark Paluch * @author ihaohong * @author Todd Merrill + * @author Chen Li */ public interface RedisOperations { @@ -259,6 +260,16 @@ public interface RedisOperations { @Nullable Set keys(K pattern); + /** + * Use a {@link Cursor} to iterate over keys.
+ * Important: Call {@link Cursor#close()} when done to avoid resource leak. + * + * @param options must not be {@literal null}. + * @return never {@literal null}. + * @see Redis Documentation: SCAN + */ + Cursor scan(ScanOptions options); + /** * Return a random key from the keyspace. * 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 2865da2ec..6c3951057 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -83,6 +83,7 @@ import org.springframework.util.CollectionUtils; * @author Mark Paluch * @author Denis Zavedeev * @author ihaohong + * @author Chen Li * @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 @@ -801,6 +802,15 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation return keySerializer != null ? SerializationUtils.deserialize(rawKeys, keySerializer) : (Set) rawKeys; } + @Override + public Cursor scan(ScanOptions options) { + Assert.notNull(options, "ScanOptions must not be null!"); + + return executeWithStickyConnection( + (RedisCallback>) connection -> new ConvertingCursor<>(connection.scan(options), + this::deserializeKey)); + } + @Override public Boolean persist(K key) { 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 caaecbb80..8f470920d 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java @@ -62,6 +62,7 @@ import org.springframework.data.redis.test.util.CollectionAwareComparator; * @author Mark Paluch * @author ihaohong * @author Hendrik Duerkop + * @author Chen Li */ @MethodSource("testParams") public class RedisTemplateIntegrationTests { @@ -126,6 +127,22 @@ public class RedisTemplateIntegrationTests { assertThat(redisTemplate.keys(keyPattern)).isNotNull(); } + @ParameterizedRedisTest // GH-2260 + void testScan() { + K key1 = keyFactory.instance(); + V value1 = valueFactory.instance(); + assumeThat(key1 instanceof String || key1 instanceof byte[]).isTrue(); + redisTemplate.opsForValue().set(key1, value1); + Cursor cursor = redisTemplate.scan(ScanOptions.scanOptions().count(1).build()); + long count = 0; + while (cursor.hasNext()) { + assertThat(cursor.next()).isEqualTo(key1); + count++; + } + cursor.close(); + assertThat(count).isEqualTo(1); + } + @SuppressWarnings("rawtypes") @ParameterizedRedisTest void testTemplateNotInitialized() throws Exception {