Skip to content

Commit 3c1dac4

Browse files
committed
Implement retrieve(key) and retrieve(key, :Supplier<CompletableFuture<T>>) operations in RedisCache.
Closes spring-projects#2650
1 parent 624d7c6 commit 3c1dac4

File tree

6 files changed

+350
-21
lines changed

6 files changed

+350
-21
lines changed

src/main/java/org/springframework/data/redis/cache/DefaultRedisCacheWriter.java

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515
*/
1616
package org.springframework.data.redis.cache;
1717

18+
import reactor.core.publisher.Mono;
19+
20+
import java.nio.ByteBuffer;
1821
import java.nio.charset.StandardCharsets;
1922
import java.time.Duration;
2023
import java.util.concurrent.TimeUnit;
24+
import java.util.function.BiFunction;
2125
import java.util.function.Consumer;
2226
import java.util.function.Function;
27+
import java.util.function.Supplier;
2328

2429
import org.springframework.dao.PessimisticLockingFailureException;
30+
import org.springframework.data.redis.connection.ReactiveRedisConnection;
31+
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
2532
import org.springframework.data.redis.connection.RedisConnection;
2633
import org.springframework.data.redis.connection.RedisConnectionFactory;
2734
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
@@ -114,8 +121,8 @@ public byte[] get(String name, byte[] key, @Nullable Duration ttl) {
114121
Assert.notNull(key, "Key must not be null");
115122

116123
byte[] result = shouldExpireWithin(ttl)
117-
? execute(name, connection -> connection.stringCommands().getEx(key, Expiration.from(ttl)))
118-
: execute(name, connection -> connection.stringCommands().get(key));
124+
? execute(name, connection -> connection.stringCommands().getEx(key, Expiration.from(ttl)))
125+
: execute(name, connection -> connection.stringCommands().get(key));
119126

120127
statistics.incGets(name);
121128

@@ -128,6 +135,66 @@ public byte[] get(String name, byte[] key, @Nullable Duration ttl) {
128135
return result;
129136
}
130137

138+
@Override
139+
public Mono<ByteBuffer> retrieve(String name, byte[] key, @Nullable Duration ttl) {
140+
141+
Assert.notNull(name, "Name must not be null");
142+
Assert.notNull(key, "Key must not be null");
143+
144+
Mono<ByteBuffer> result = nonBlockingExecutionStrategy(name).apply(key, ttl);
145+
146+
result = result.doOnSuccess(byteBuffer -> {
147+
if (byteBuffer != null) {
148+
statistics.incHits(name);
149+
}
150+
else {
151+
statistics.incMisses(name);
152+
}
153+
}).doFirst(() -> statistics.incGets(name));
154+
155+
return result;
156+
}
157+
158+
private BiFunction<byte[], Duration, Mono<ByteBuffer>> nonBlockingExecutionStrategy(String cacheName) {
159+
160+
boolean isReactive = this.connectionFactory instanceof ReactiveRedisConnectionFactory;
161+
162+
// Execution strategy that will be applied when Lettuce is used
163+
if (isReactive) {
164+
165+
return (key, ttl) -> {
166+
167+
ByteBuffer wrappedKey = ByteBuffer.wrap(key);
168+
169+
Mono<ByteBuffer> result = shouldExpireWithin(ttl)
170+
? executeReactively(connection -> connection.stringCommands().getEx(wrappedKey, Expiration.from(ttl)))
171+
: executeReactively(connection -> connection.stringCommands().get(wrappedKey));
172+
173+
result.doFirst(() -> executeLockFree(connection ->
174+
checkAndPotentiallyWaitUntilUnlocked(cacheName, connection)));
175+
176+
return result;
177+
};
178+
}
179+
180+
// Execution strategy that will be applied when Jedis is used
181+
return (key, ttl) -> {
182+
183+
Supplier<ByteBuffer> getKey = () -> execute(cacheName, connection ->
184+
nullSafeByteBufferWrap(connection.stringCommands().get(key)));
185+
186+
Supplier<ByteBuffer> getKeyWithExpiration = () -> execute(cacheName, connection ->
187+
nullSafeByteBufferWrap(connection.stringCommands().getEx(key, Expiration.from(ttl))));
188+
189+
return shouldExpireWithin(ttl) ? Mono.fromSupplier(getKeyWithExpiration) : Mono.fromSupplier(getKey);
190+
};
191+
}
192+
193+
@Nullable
194+
private ByteBuffer nullSafeByteBufferWrap(@Nullable byte[] value) {
195+
return value != null ? ByteBuffer.wrap(value) : null;
196+
}
197+
131198
@Override
132199
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
133200

@@ -308,6 +375,18 @@ private void executeLockFree(Consumer<RedisConnection> callback) {
308375
}
309376
}
310377

378+
private <T> T executeReactively(Function<ReactiveRedisConnection, T> callback) {
379+
380+
ReactiveRedisConnection connection = getReactiveRedisConnectionFactory().getReactiveConnection();
381+
382+
try {
383+
return callback.apply(connection);
384+
}
385+
finally {
386+
connection.closeLater();
387+
}
388+
}
389+
311390
private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {
312391

313392
if (!isLockingCacheWriter()) {
@@ -333,11 +412,15 @@ private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection c
333412
}
334413
}
335414

415+
private ReactiveRedisConnectionFactory getReactiveRedisConnectionFactory() {
416+
return (ReactiveRedisConnectionFactory) this.connectionFactory;
417+
}
418+
336419
private static byte[] createCacheLockKey(String name) {
337420
return (name + "~lock").getBytes(StandardCharsets.UTF_8);
338421
}
339422

340-
private boolean isTrue(@Nullable Boolean value) {
423+
private static boolean isTrue(@Nullable Boolean value) {
341424
return Boolean.TRUE.equals(value);
342425
}
343426

src/main/java/org/springframework/data/redis/cache/RedisCache.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import org.springframework.util.ObjectUtils;
4646
import org.springframework.util.ReflectionUtils;
4747

48+
import reactor.core.publisher.Mono;
49+
4850
/**
4951
* {@link org.springframework.cache.Cache} implementation using for Redis as the underlying store for cache data.
5052
* <p>
@@ -293,12 +295,21 @@ protected Object preProcessCacheValue(@Nullable Object value) {
293295

294296
@Override
295297
public CompletableFuture<?> retrieve(Object key) {
296-
return super.retrieve(key);
298+
return retrieveMono(key).toFuture();
297299
}
298300

299301
@Override
302+
@SuppressWarnings("unchecked")
300303
public <T> CompletableFuture<T> retrieve(Object key, Supplier<CompletableFuture<T>> valueLoader) {
301-
return super.retrieve(key, valueLoader);
304+
305+
return retrieveMono(key)
306+
.map(value -> (T) deserializeCacheValue(ByteUtils.getBytes(value)))
307+
.or(Mono.fromCompletionStage(valueLoader))
308+
.toFuture();
309+
}
310+
311+
private Mono<ByteBuffer> retrieveMono(Object key) {
312+
return getCacheWriter().retrieve(getName(), createAndConvertCacheKey(key));
302313
}
303314

304315
/**

src/main/java/org/springframework/data/redis/cache/RedisCacheWriter.java

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

18+
import java.nio.ByteBuffer;
1819
import java.time.Duration;
1920

2021
import org.springframework.data.redis.connection.RedisConnectionFactory;
2122
import org.springframework.lang.Nullable;
2223
import org.springframework.util.Assert;
2324

25+
import reactor.core.publisher.Mono;
26+
2427
/**
2528
* {@link RedisCacheWriter} provides low-level access to Redis commands ({@code SET, SETNX, GET, EXPIRE,...}) used for
2629
* caching.
@@ -135,6 +138,40 @@ default byte[] get(String name, byte[] key, @Nullable Duration ttl) {
135138
return get(name, key);
136139
}
137140

141+
/**
142+
* Returns the {@link Mono value} to which the {@link RedisCache} maps the given {@link byte[] key}.
143+
* <p>
144+
* This operation does not block.
145+
*
146+
* @param name {@link String} containing the name of the {@link RedisCache}.
147+
* @param key {@link byte[] key} mapped to the {@link Mono value} in the {@link RedisCache}.
148+
* @return the {@link Mono value} to which the {@link RedisCache} maps the given {@link byte[] key}.
149+
* @throws IllegalStateException if the Redis connection factory is not reactive.
150+
* @see reactor.core.publisher.Mono
151+
* @see java.nio.ByteBuffer
152+
* @since 3.2.0
153+
*/
154+
default Mono<ByteBuffer> retrieve(String name, byte[] key) {
155+
return retrieve(name, key, null);
156+
}
157+
158+
/**
159+
* Returns the {@link Mono value} to which the {@link RedisCache} maps the given {@link byte[] key}
160+
* setting the {@link Duration TTL expiration} for the cache entry.
161+
* <p>
162+
* This operation does not block.
163+
*
164+
* @param name {@link String} containing the name of the {@link RedisCache}.
165+
* @param key {@link byte[] key} mapped to the {@link Mono value} in the {@link RedisCache}.
166+
* @param ttl {@link Duration} specifying the {@literal expiration timeout} for the cache entry.
167+
* @return the {@link Mono value} to which the {@link RedisCache} maps the given {@link byte[] key}.
168+
* @throws IllegalStateException if the Redis connection factory is not reactive.
169+
* @see reactor.core.publisher.Mono
170+
* @see java.nio.ByteBuffer
171+
* @since 3.2.0
172+
*/
173+
Mono<ByteBuffer> retrieve(String name, byte[] key, @Nullable Duration ttl);
174+
138175
/**
139176
* Write the given key/value pair to Redis and set the expiration time if defined.
140177
*

0 commit comments

Comments
 (0)