Skip to content

Commit cf5c0da

Browse files
DATAREDIS-694 - Add support for TOUCH.
We now support TOUCH for both Lettuce and Jedis in imperative and reactive (Lettuce only) KeyCommands.
1 parent b633ed8 commit cf5c0da

13 files changed

+238
-10
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,15 @@ public DataType type(byte[] key) {
12051205
return convertAndReturn(delegate.type(key), identityConverter);
12061206
}
12071207

1208+
/*
1209+
* (non-Javadoc)
1210+
* @see org.springframework.data.redis.connection.RedisKeyCommands#touch(byte[][])
1211+
*/
1212+
@Override
1213+
public Long touch(byte[]... keys) {
1214+
return convertAndReturn(delegate.touch(keys), identityConverter);
1215+
}
1216+
12081217
/*
12091218
* (non-Javadoc)
12101219
* @see org.springframework.data.redis.connection.RedisTxCommands#unwatch()
@@ -2496,6 +2505,16 @@ public DataType type(String key) {
24962505
return type(serialize(key));
24972506
}
24982507

2508+
/*
2509+
* (non-Javadoc)
2510+
* @see org.springframework.data.redis.connection.StringRedisConnection#touch(java.lang.String[])
2511+
*/
2512+
@Nullable
2513+
@Override
2514+
public Long touch(String... keys) {
2515+
return touch(serializeMulti(keys));
2516+
}
2517+
24992518
/*
25002519
* (non-Javadoc)
25012520
* @see org.springframework.data.redis.connection.StringRedisConnection#zAdd(java.lang.String, double, java.lang.String)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ default DataType type(byte[] pattern) {
7575
return keyCommands().type(pattern);
7676
}
7777

78+
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
79+
@Override
80+
@Deprecated
81+
default Long touch(byte[]... keys) {
82+
return keyCommands().touch(keys);
83+
}
84+
7885
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
7986
@Override
8087
@Deprecated

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.nio.ByteBuffer;
2222
import java.time.Duration;
2323
import java.time.Instant;
24+
import java.util.Collection;
2425
import java.util.List;
2526

2627
import org.reactivestreams.Publisher;
@@ -87,6 +88,28 @@ default Mono<DataType> type(ByteBuffer key) {
8788
*/
8889
Flux<CommandResponse<KeyCommand, DataType>> type(Publisher<KeyCommand> keys);
8990

91+
/**
92+
* Alter the last access time of given {@code key(s)}.
93+
*
94+
* @param keys must not be {@literal null}.
95+
* @return {@link Mono} emitting the number of keys touched.
96+
* @see <a href="http://redis.io/commands/touch">Redis Documentation: TOUCH</a>
97+
* @since 2.1
98+
*/
99+
default Mono<Long> touch(Collection<ByteBuffer> keys) {
100+
return touch(Mono.just(keys)).next().map(NumericResponse::getOutput);
101+
}
102+
103+
/**
104+
* Alter the last access time of given {@code key(s)}.
105+
*
106+
* @param keys must not be {@literal null}.
107+
* @return
108+
* @see <a href="http://redis.io/commands/touch">Redis Documentation: TOUCH</a>
109+
* @since 2.1
110+
*/
111+
Flux<NumericResponse<Collection<ByteBuffer>, Long>> touch(Publisher<Collection<ByteBuffer>> keys);
112+
90113
/**
91114
* Find all keys matching the given {@literal pattern}.
92115
*

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ default Boolean exists(byte[] key) {
8080
@Nullable
8181
DataType type(byte[] key);
8282

83+
/**
84+
* Alter the last access time of given {@code key(s)}.
85+
*
86+
* @param keys must not be {@literal null}.
87+
* @return {@literal null} when used in pipeline / transaction.
88+
* @see <a href="http://redis.io/commands/touch">Redis Documentation: TOUCH</a>
89+
* @since 2.1
90+
*/
91+
@Nullable
92+
Long touch(byte[]... keys);
93+
8394
/**
8495
* Find all keys matching the given {@code pattern}.
8596
*

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ interface StringTuple extends Tuple {
123123
*/
124124
DataType type(String key);
125125

126+
/**
127+
* Alter the last access time of given {@code key(s)}.
128+
*
129+
* @param keys must not be {@literal null}.
130+
* @return {@literal null} when used in pipeline / transaction.
131+
* @see <a href="http://redis.io/commands/touch">Redis Documentation: TOUCH</a>
132+
* @since 2.1
133+
*/
134+
@Nullable
135+
Long touch(String... keys);
136+
126137
/**
127138
* Find all keys matching the given {@code pattern}.
128139
*

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,37 @@ private static byte[][] getCommandArguments(byte[] key, Collection<byte[]> args)
176176
return commandArgs;
177177
}
178178

179+
/**
180+
* Execute the given command for the {@code key} provided potentially appending args. <br />
181+
* This method, other than {@link #execute(String, byte[]...)}, dispatches the command to the {@code key} serving
182+
* master node.
183+
*
184+
* <pre>
185+
* <code>
186+
* // SET foo bar EX 10 NX
187+
* execute("SET", "foo".getBytes(), asBinaryList("bar", "EX", 10, "NX")
188+
* </code>
189+
* </pre>
190+
*
191+
* @param command must not be {@literal null}.
192+
* @param keys must not be {@literal null}.
193+
* @param args must not be {@literal null}.
194+
* @return command result as delivered by the underlying Redis driver. Can be {@literal null}.
195+
* @since 2.1
196+
*/
197+
@Nullable
198+
public <T> List<T> execute(String command, Collection<byte[]> keys, Collection<byte[]> args) {
199+
200+
Assert.notNull(command, "Command must not be null!");
201+
Assert.notNull(keys, "Key must not be null!");
202+
Assert.notNull(args, "Args must not be null!");
203+
204+
return clusterCommandExecutor.executeMultiKeyCommand((JedisMultiKeyClusterCommandCallback<T>) (client, key) -> {
205+
return JedisClientUtils.execute(command, new byte[][] { key }, args.toArray(new byte[args.size()][]),
206+
() -> client);
207+
}, keys).resultsAsList();
208+
}
209+
179210
/*
180211
* (non-Javadoc)
181212
* @see org.springframework.data.redis.connection.RedisConnection#geoCommands()
@@ -829,8 +860,7 @@ static class JedisClusterNodeResourceProvider implements ClusterNodeResourceProv
829860

830861
PropertyAccessor accessor = new DirectFieldAccessFallbackBeanWrapper(cluster);
831862
this.connectionHandler = accessor.isReadableProperty("connectionHandler")
832-
? (JedisClusterConnectionHandler) accessor.getPropertyValue("connectionHandler")
833-
: null;
863+
? (JedisClusterConnectionHandler) accessor.getPropertyValue("connectionHandler") : null;
834864
} else {
835865
this.connectionHandler = null;
836866
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
2222
import java.util.Collection;
23+
import java.util.Collections;
2324
import java.util.HashSet;
2425
import java.util.List;
2526
import java.util.Set;
@@ -97,6 +98,20 @@ public DataType type(byte[] key) {
9798
}
9899
}
99100

101+
/*
102+
* (non-Javadoc)
103+
* @see org.springframework.data.redis.connection.RedisKeyCommands#touch(byte[][])
104+
*/
105+
@Nullable
106+
@Override
107+
public Long touch(byte[]... keys) {
108+
109+
Assert.notNull(keys, "Keys must not be null!");
110+
111+
return connection.<Long> execute("TOUCH", Arrays.asList(keys), Collections.emptyList()).stream()
112+
.mapToLong(val -> val).sum();
113+
}
114+
100115
/*
101116
* (non-Javadoc)
102117
* @see org.springframework.data.redis.connection.RedisKeyCommands#keys(byte[])

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ public DataType type(byte[] key) {
149149
}
150150
}
151151

152+
/*
153+
* (non-Javadoc)
154+
* @see org.springframework.data.redis.connection.RedisKeyCommands#touch(byte[][])
155+
*/
156+
@Nullable
157+
@Override
158+
public Long touch(byte[]... keys) {
159+
160+
Assert.notNull(keys, "Keys must not be null!");
161+
162+
return Long.class.cast(connection.execute("TOUCH", keys));
163+
}
164+
152165
/*
153166
* (non-Javadoc)
154167
* @see org.springframework.data.redis.connection.RedisKeyCommands#keys(byte[])

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,30 @@ public DataType type(byte[] key) {
152152
}
153153
}
154154

155+
/*
156+
* (non-Javadoc)
157+
* @see org.springframework.data.redis.connection.RedisKeyCommands#touch(byte[][])
158+
*/
159+
@Override
160+
public Long touch(byte[]... keys) {
161+
162+
Assert.notNull(keys, "Keys must not be null!");
163+
164+
try {
165+
if (isPipelined()) {
166+
pipeline(connection.newLettuceResult(getAsyncConnection().touch(keys)));
167+
return null;
168+
}
169+
if (isQueueing()) {
170+
transaction(connection.newLettuceTxResult(getConnection().touch(keys)));
171+
return null;
172+
}
173+
return getConnection().touch(keys);
174+
} catch (Exception ex) {
175+
throw convertLettuceAccessException(ex);
176+
}
177+
}
178+
155179
/*
156180
* (non-Javadoc)
157181
* @see org.springframework.data.redis.connection.RedisKeyCommands#keys(byte[])

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import reactor.core.publisher.Mono;
2121

2222
import java.nio.ByteBuffer;
23+
import java.util.Collection;
2324
import java.util.List;
2425
import java.util.stream.Collectors;
2526

@@ -86,6 +87,22 @@ public Flux<CommandResponse<KeyCommand, DataType>> type(Publisher<KeyCommand> co
8687
}));
8788
}
8889

90+
/*
91+
* (non-Javadoc)
92+
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#touch(org.reactivestreams.Publisher)
93+
*/
94+
@Override
95+
public Flux<NumericResponse<Collection<ByteBuffer>, Long>> touch(Publisher<Collection<ByteBuffer>> keysCollection) {
96+
97+
return connection.execute(cmd -> Flux.from(keysCollection).concatMap((keys) -> {
98+
99+
Assert.notEmpty(keys, "Keys must not be null!");
100+
101+
return cmd.touch(keys.toArray(new ByteBuffer[keys.size()]))
102+
.map((value) -> new NumericResponse<>(keys, value));
103+
}));
104+
}
105+
89106
/*
90107
* (non-Javadoc)
91108
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#keys(org.reactivestreams.Publisher)

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,8 +2685,7 @@ public void hStrLenReturnsFieldLength() {
26852685
actual.add(connection.hSet("hash-hstrlen", "key-2", "value-2"));
26862686
actual.add(connection.hStrLen("hash-hstrlen", "key-2"));
26872687

2688-
verifyResults(
2689-
Arrays.asList(new Object[] { Boolean.TRUE, Boolean.TRUE, Long.valueOf("value-2".length()) }));
2688+
verifyResults(Arrays.asList(new Object[] { Boolean.TRUE, Boolean.TRUE, Long.valueOf("value-2".length()) }));
26902689
}
26912690

26922691
@Test // DATAREDIS-698
@@ -2695,17 +2694,33 @@ public void hStrLenReturnsZeroWhenFieldDoesNotExist() {
26952694
actual.add(connection.hSet("hash-hstrlen", "key-1", "value-1"));
26962695
actual.add(connection.hStrLen("hash-hstrlen", "key-2"));
26972696

2698-
verifyResults(
2699-
Arrays.asList(new Object[] { Boolean.TRUE, 0L }));
2697+
verifyResults(Arrays.asList(new Object[] { Boolean.TRUE, 0L }));
27002698
}
27012699

27022700
@Test // DATAREDIS-698
27032701
public void hStrLenReturnsZeroWhenKeyDoesNotExist() {
27042702

27052703
actual.add(connection.hStrLen("hash-no-exist", "key-2"));
27062704

2707-
verifyResults(
2708-
Arrays.asList(new Object[] { 0L }));
2705+
verifyResults(Arrays.asList(new Object[] { 0L }));
2706+
}
2707+
2708+
@Test // DATAREDIS-694
2709+
public void touchReturnsNrOfKeysTouched() {
2710+
2711+
connection.set("touch.this", "Can't touch this! - oh-oh oh oh oh-oh-oh");
2712+
2713+
actual.add(connection.touch("touch.this", "touch.that"));
2714+
2715+
verifyResults(Arrays.asList(new Object[] { 1L }));
2716+
}
2717+
2718+
@Test // DATAREDIS-694
2719+
public void touchReturnsZeroIfNoKeysTouched() {
2720+
2721+
actual.add(connection.touch("touch.this", "touch.that"));
2722+
2723+
verifyResults(Arrays.asList(new Object[] { 0L }));
27092724
}
27102725

27112726
protected void verifyResults(List<Object> expected) {

src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,16 @@
2828

2929
import java.io.IOException;
3030
import java.nio.charset.Charset;
31-
import java.util.*;
31+
import java.util.Arrays;
32+
import java.util.Collection;
33+
import java.util.Collections;
34+
import java.util.HashMap;
35+
import java.util.HashSet;
36+
import java.util.LinkedHashMap;
37+
import java.util.List;
38+
import java.util.Map;
39+
import java.util.Properties;
40+
import java.util.Set;
3241
import java.util.concurrent.TimeUnit;
3342

3443
import org.junit.After;
@@ -375,7 +384,7 @@ public void executeWithKeyAndArgs() {
375384

376385
@Test(expected = IllegalArgumentException.class) // DATAREDIS-689
377386
public void executeWithNoKeyAndArgsThrowsException() {
378-
clusterConnection.execute("KEYS", null, Collections.singletonList("*".getBytes()));
387+
clusterConnection.execute("KEYS", (byte[]) null, Collections.singletonList("*".getBytes()));
379388
}
380389

381390
@Test // DATAREDIS-529
@@ -2195,4 +2204,19 @@ public void zUnionStoreShouldWorkForSameSlotKeys() {
21952204
assertThat(nativeConnection.zrange(SAME_SLOT_KEY_3_BYTES, 0, -1),
21962205
hasItems(VALUE_1_BYTES, VALUE_2_BYTES, VALUE_3_BYTES));
21972206
}
2207+
2208+
@Test // DATAREDIS-694
2209+
public void touchReturnsNrOfKeysTouched() {
2210+
2211+
nativeConnection.set(KEY_1, VALUE_1);
2212+
nativeConnection.set(KEY_2, VALUE_1);
2213+
2214+
assertThat(clusterConnection.keyCommands().touch(KEY_1_BYTES, KEY_2_BYTES, KEY_3_BYTES), is(2L));
2215+
}
2216+
2217+
@Test // DATAREDIS-694
2218+
public void touchReturnsZeroIfNoKeysTouched() {
2219+
assertThat(clusterConnection.keyCommands().touch(KEY_1_BYTES), is(0L));
2220+
}
2221+
21982222
}

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,4 +287,23 @@ public void shouldMoveToDatabase() {
287287
.verify();
288288
assertThat(nativeCommands.exists(KEY_1), is(0L));
289289
}
290+
291+
@Test // DATAREDIS-694
292+
public void touchReturnsNrOfKeysTouched() {
293+
294+
nativeCommands.set(KEY_1, VALUE_1);
295+
nativeCommands.set(KEY_2, VALUE_2);
296+
297+
StepVerifier.create(connection.keyCommands().touch(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER, KEY_3_BBUFFER)))
298+
.expectNext(2L) //
299+
.verifyComplete();
300+
}
301+
302+
@Test // DATAREDIS-694
303+
public void touchReturnsZeroIfNoKeysTouched() {
304+
305+
StepVerifier.create(connection.keyCommands().touch(Arrays.asList(KEY_1_BBUFFER))) //
306+
.expectNext(0L) //
307+
.verifyComplete();
308+
}
290309
}

0 commit comments

Comments
 (0)