Skip to content

Commit 0806fe6

Browse files
DATAREDIS-529 - Add support for EXISTS taking multiple keys.
1 parent f23ebf8 commit 0806fe6

14 files changed

+269
-23
lines changed

src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,8 @@
1515
*/
1616
package org.springframework.data.redis.connection;
1717

18-
import java.util.ArrayList;
19-
import java.util.Collection;
20-
import java.util.HashMap;
21-
import java.util.LinkedHashMap;
22-
import java.util.LinkedList;
23-
import java.util.List;
24-
import java.util.Map;
18+
import java.util.*;
2519
import java.util.Map.Entry;
26-
import java.util.Properties;
27-
import java.util.Queue;
28-
import java.util.Set;
2920
import java.util.concurrent.TimeUnit;
3021

3122
import org.apache.commons.logging.Log;
@@ -303,6 +294,24 @@ public Boolean exists(byte[] key) {
303294
return convertAndReturn(delegate.exists(key), identityConverter);
304295
}
305296

297+
/*
298+
* (non-Javadoc)
299+
* @see org.springframework.data.redis.connection.StringRedisConnection#exists(String[])
300+
*/
301+
@Override
302+
public Long exists(String... keys) {
303+
return convertAndReturn(delegate.exists(Arrays.asList(serializeMulti(keys))), identityConverter);
304+
}
305+
306+
/*
307+
* (non-Javadoc)
308+
* @see org.springframework.data.redis.connection.RedisKeyCommands#exists(java.util.Collection)
309+
*/
310+
@Override
311+
public Long exists(Collection<byte[]> keys) {
312+
return convertAndReturn(delegate.exists(keys), identityConverter);
313+
}
314+
306315
/*
307316
* (non-Javadoc)
308317
* @see org.springframework.data.redis.connection.RedisKeyCommands#expire(byte[], long)
@@ -3472,8 +3481,8 @@ private <T> T convertAndReturn(@Nullable Object value, Converter converter) {
34723481
return null;
34733482
}
34743483

3475-
3476-
return value == null ? null : ObjectUtils.nullSafeEquals(converter, identityConverter) ? (T) value : (T) converter.convert(value);
3484+
return value == null ? null
3485+
: ObjectUtils.nullSafeEquals(converter, identityConverter) ? (T) value : (T) converter.convert(value);
34773486
}
34783487

34793488
private void addResultConverter(Converter<?, ?> converter) {

src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.redis.connection;
1717

18+
import java.util.Collection;
1819
import java.util.List;
1920
import java.util.Map;
2021
import java.util.Map.Entry;
@@ -54,6 +55,13 @@ default Boolean exists(byte[] key) {
5455
return keyCommands().exists(key);
5556
}
5657

58+
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
59+
@Override
60+
@Deprecated
61+
default Long exists(Collection<byte[]> keys) {
62+
return keyCommands().exists(keys);
63+
}
64+
5765
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
5866
@Override
5967
@Deprecated

src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
*/
1616
package org.springframework.data.redis.connection;
1717

18+
import java.util.Collection;
19+
import java.util.Collections;
1820
import java.util.List;
1921
import java.util.Set;
2022
import java.util.concurrent.TimeUnit;
2123

2224
import org.springframework.data.redis.core.Cursor;
2325
import org.springframework.data.redis.core.ScanOptions;
2426
import org.springframework.lang.Nullable;
27+
import org.springframework.util.Assert;
2528

2629
/**
2730
* Key-specific commands supported by Redis.
@@ -40,7 +43,23 @@ public interface RedisKeyCommands {
4043
* @see <a href="http://redis.io/commands/exists">Redis Documentation: EXISTS</a>
4144
*/
4245
@Nullable
43-
Boolean exists(byte[] key);
46+
default Boolean exists(byte[] key) {
47+
48+
Assert.notNull(key, "Key must not be null!");
49+
Long count = exists(Collections.singleton(key));
50+
return count != null ? count > 0 : null;
51+
}
52+
53+
/**
54+
* Count how many of the given {@code keys} exists. Providing the very same {@code key} more than once also counts
55+
* multiple times.
56+
*
57+
* @param keys must not be {@literal null}.
58+
* @return the number of keys existing among the ones specified as arguments.
59+
* @since 2.1
60+
*/
61+
@Nullable
62+
Long exists(Collection<byte[]> keys);
4463

4564
/**
4665
* Delete given {@code keys}.

src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ interface StringTuple extends Tuple {
9191
*/
9292
Boolean exists(String key);
9393

94+
/**
95+
* Count how many of the if given {@code keys} exists.
96+
*
97+
* @param keys must not be {@literal null}.
98+
* @return
99+
* @see <a href="http://redis.io/commands/exists">Redis Documentation: EXISTS</a>
100+
* @see RedisKeyCommands#exists(java.util.Collection)
101+
* @since 2.1
102+
*/
103+
@Nullable
104+
Long exists(String... keys);
105+
94106
/**
95107
* Delete given {@code keys}.
96108
*

src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737
import org.springframework.data.redis.connection.jedis.JedisClusterConnection.JedisMultiKeyClusterCommandCallback;
3838
import org.springframework.data.redis.core.Cursor;
3939
import org.springframework.data.redis.core.ScanOptions;
40+
import org.springframework.lang.Nullable;
4041
import org.springframework.util.Assert;
4142
import org.springframework.util.CollectionUtils;
43+
import org.springframework.util.ObjectUtils;
4244

4345
/**
4446
* @author Christoph Strobl
@@ -471,15 +473,23 @@ public Long sort(byte[] key, SortParameters params, byte[] storeKey) {
471473

472474
/*
473475
* (non-Javadoc)
474-
* @see org.springframework.data.redis.connection.RedisKeyCommands#exists(byte[])
476+
* @see org.springframework.data.redis.connection.RedisKeyCommands#exists(java.util.Collection)
475477
*/
478+
@Nullable
476479
@Override
477-
public Boolean exists(byte[] key) {
480+
public Long exists(Collection<byte[]> keys) {
478481

479-
Assert.notNull(key, "Key must not be null!");
482+
Assert.notNull(keys, "Keys must not be null!");
480483

481484
try {
482-
return connection.getCluster().exists(key);
485+
486+
return keys.stream() //
487+
.parallel()
488+
.map(key -> connection.getClusterCommandExecutor()
489+
.executeCommandOnSingleNode((JedisClusterCommandCallback<Boolean>) client -> client.exists(key),
490+
connection.getTopologyProvider().getTopology().getKeyServingMasterNode(key))
491+
.getValue())
492+
.mapToLong(val -> ObjectUtils.nullSafeEquals(val, Boolean.TRUE) ? 1L : 0L).sum();
483493
} catch (Exception ex) {
484494
throw convertJedisAccessException(ex);
485495
}

src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import redis.clients.jedis.ScanParams;
2121
import redis.clients.jedis.SortingParams;
2222

23+
import java.util.Collection;
2324
import java.util.List;
2425
import java.util.Set;
2526
import java.util.concurrent.TimeUnit;
@@ -70,6 +71,33 @@ public Boolean exists(byte[] key) {
7071
}
7172
}
7273

74+
/*
75+
* (non-Javadoc)
76+
* @see org.springframework.data.redis.connection.RedisKeyCommands#exists(java.util.Collection)
77+
*/
78+
@Nullable
79+
@Override
80+
public Long exists(Collection<byte[]> keys) {
81+
82+
Assert.notNull(keys, "Keys must not be null!");
83+
84+
try {
85+
if (isPipelined()) {
86+
pipeline(
87+
connection.newJedisResult(connection.getRequiredPipeline().exists(keys.toArray(new byte[keys.size()][]))));
88+
return null;
89+
}
90+
if (isQueueing()) {
91+
transaction(connection
92+
.newJedisResult(connection.getRequiredTransaction().exists(keys.toArray(new byte[keys.size()][]))));
93+
return null;
94+
}
95+
return connection.getJedis().exists(keys.toArray(new byte[keys.size()][]));
96+
} catch (Exception ex) {
97+
throw connection.convertJedisAccessException(ex);
98+
}
99+
}
100+
73101
/*
74102
* (non-Javadoc)
75103
* @see org.springframework.data.redis.connection.RedisKeyCommands#del(byte[][])

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import lombok.NonNull;
2424
import lombok.RequiredArgsConstructor;
2525

26+
import java.util.Collection;
2627
import java.util.List;
2728
import java.util.Set;
2829
import java.util.concurrent.TimeUnit;
@@ -38,6 +39,7 @@
3839
import org.springframework.data.redis.core.ScanCursor;
3940
import org.springframework.data.redis.core.ScanIteration;
4041
import org.springframework.data.redis.core.ScanOptions;
42+
import org.springframework.lang.Nullable;
4143
import org.springframework.util.Assert;
4244

4345
/**
@@ -76,6 +78,31 @@ public Boolean exists(byte[] key) {
7678
}
7779
}
7880

81+
/*
82+
* (non-Javadoc)
83+
* @see org.springframework.data.redis.connection.RedisKeyCommands#exists(java.util.Collection)
84+
*/
85+
@Nullable
86+
@Override
87+
public Long exists(Collection<byte[]> keys) {
88+
89+
Assert.notNull(keys, "Keys must not be null!");
90+
91+
try {
92+
if (isPipelined()) {
93+
pipeline(connection.newLettuceResult(getAsyncConnection().exists(keys.toArray(new byte[keys.size()][]))));
94+
return null;
95+
}
96+
if (isQueueing()) {
97+
transaction(connection.newLettuceTxResult(getAsyncConnection().exists(keys.toArray(new byte[keys.size()][]))));
98+
return null;
99+
}
100+
return getConnection().exists(keys.toArray(new byte[keys.size()][]));
101+
} catch (Exception ex) {
102+
throw convertLettuceAccessException(ex);
103+
}
104+
}
105+
79106
/*
80107
* (non-Javadoc)
81108
* @see org.springframework.data.redis.connection.RedisKeyCommands#del(byte[][])

src/main/java/org/springframework/data/redis/core/RedisOperations.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ <T> T execute(RedisScript<T> script, RedisSerializer<?> argsSerializer, RedisSer
162162
@Nullable
163163
Boolean hasKey(K key);
164164

165+
/**
166+
* Count the number of {@code keys} that exists.
167+
*
168+
* @param keys must not be {@literal null}.
169+
* @return The number of keys existing among the ones specified as arguments. Keys mentioned multiple times and
170+
* existing are counted multiple times.
171+
* @see <a href="http://redis.io/commands/exists">Redis Documentation: EXISTS</a>
172+
* @since 2.1
173+
*/
174+
@Nullable
175+
Long countExistingKeys(Collection<K> keys);
176+
165177
/**
166178
* Delete given {@code key}.
167179
*

src/main/java/org/springframework/data/redis/core/RedisTemplate.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.Closeable;
1919
import java.lang.reflect.Proxy;
2020
import java.util.ArrayList;
21+
import java.util.Arrays;
2122
import java.util.Collection;
2223
import java.util.Collections;
2324
import java.util.Date;
@@ -739,6 +740,19 @@ public Boolean hasKey(K key) {
739740
return execute(connection -> connection.exists(rawKey), true);
740741
}
741742

743+
/*
744+
* (non-Javadoc)
745+
* @see org.springframework.data.redis.core.RedisOperations#countExistingKeys(java.util.Collection)
746+
*/
747+
@Override
748+
public Long countExistingKeys(Collection<K> keys) {
749+
750+
Assert.notNull(keys, "Keys must not be null!");
751+
752+
byte[][] rawKeys = rawKeys(keys);
753+
return execute(connection -> connection.exists(Arrays.asList(rawKeys)), true);
754+
}
755+
742756
/*
743757
* (non-Javadoc)
744758
* @see org.springframework.data.redis.core.RedisOperations#expire(java.lang.Object, long, java.util.concurrent.TimeUnit)

src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,36 @@ public void testExists() {
908908
verifyResults(Arrays.asList(new Object[] { true, false }));
909909
}
910910

911+
@Test // DATAREDIS-529
912+
public void testExistsWithMultipleKeys() {
913+
914+
connection.set("exist-1", "true");
915+
connection.set("exist-2", "true");
916+
connection.set("exist-3", "true");
917+
918+
actual.add(connection.exists("exist-1", "exist-2", "exist-3", "nonexistent"));
919+
920+
verifyResults(Arrays.asList(new Object[] { 3L }));
921+
}
922+
923+
@Test // DATAREDIS-529
924+
public void testExistsWithMultipleKeysNoneExists() {
925+
926+
actual.add(connection.exists("no-exist-1", "no-exist-2"));
927+
928+
verifyResults(Arrays.asList(new Object[] { 0L }));
929+
}
930+
931+
@Test // DATAREDIS-529
932+
public void testExistsSameKeyMultipleTimes() {
933+
934+
connection.set("existent", "true");
935+
936+
actual.add(connection.exists("existent", "existent"));
937+
938+
verifyResults(Arrays.asList(new Object[] { 2L }));
939+
}
940+
911941
@SuppressWarnings("unchecked")
912942
@Test
913943
public void testKeys() throws Exception {

0 commit comments

Comments
 (0)