From 4a6c3e8f5dda538c07d41f424e6f15a51c410ada Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 30 Nov 2023 12:05:16 +0100 Subject: [PATCH] Fix reactive retrieval of cached null value for empty Mono Closes gh-31722 --- .../cache/interceptor/CacheAspectSupport.java | 4 +- .../cache/CacheReproTests.java | 45 +++++++++++++++++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index af0f27c7e98..eb742122d07 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -1068,13 +1068,13 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker if (adapter.isMultiValue()) { return adapter.fromPublisher(Flux.from( Mono.fromFuture(cachedFuture) - .flatMap(value -> (Mono) evaluate(Mono.just(unwrapCacheValue(value)), invoker, method, contexts))) + .flatMap(value -> (Mono) evaluate(Mono.justOrEmpty(unwrapCacheValue(value)), invoker, method, contexts))) .flatMap(v -> (v instanceof Iterable iv ? Flux.fromIterable(iv) : Flux.just(v))) .switchIfEmpty(Flux.defer(() -> (Flux) evaluate(null, invoker, method, contexts)))); } else { return adapter.fromPublisher(Mono.fromFuture(cachedFuture) - .flatMap(value -> (Mono) evaluate(Mono.just(unwrapCacheValue(value)), invoker, method, contexts)) + .flatMap(value -> (Mono) evaluate(Mono.justOrEmpty(unwrapCacheValue(value)), invoker, method, contexts)) .switchIfEmpty(Mono.defer(() -> (Mono) evaluate(null, invoker, method, contexts)))); } } diff --git a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java index c07b3f3dad3..c97bc9db67e 100644 --- a/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java +++ b/spring-context/src/test/java/org/springframework/cache/CacheReproTests.java @@ -202,6 +202,10 @@ class CacheReproTests { assertThat(bean.findById("tb2").join()).isNotSameAs(tb); assertThat(cache.get("tb2")).isNull(); + assertThat(bean.findByIdEmpty("").join()).isNull(); + assertThat(bean.findByIdEmpty("").join()).isNull(); + assertThat(cache.get("").get()).isNull(); + context.close(); } @@ -226,6 +230,10 @@ class CacheReproTests { assertThat(bean.findById("tb1").get()).isSameAs(tb); assertThat(cache.get("tb1").get()).isSameAs(tb); + assertThat(bean.findById("").join()).isNull(); + assertThat(bean.findById("").join()).isNull(); + assertThat(cache.get("").get()).isNull(); + context.close(); } @@ -257,6 +265,10 @@ class CacheReproTests { assertThat(bean.findById("tb2").block()).isNotSameAs(tb); assertThat(cache.get("tb2")).isNull(); + assertThat(bean.findByIdEmpty("").block()).isNull(); + assertThat(bean.findByIdEmpty("").block()).isNull(); + assertThat(cache.get("").get()).isNull(); + context.close(); } @@ -281,6 +293,10 @@ class CacheReproTests { assertThat(bean.findById("tb1").block()).isSameAs(tb); assertThat(cache.get("tb1").get()).isSameAs(tb); + assertThat(bean.findById("").block()).isNull(); + assertThat(bean.findById("").block()).isNull(); + assertThat(cache.get("").get()).isNull(); + context.close(); } @@ -312,6 +328,10 @@ class CacheReproTests { assertThat(bean.findById("tb2").collectList().block()).isNotEqualTo(tb); assertThat(cache.get("tb2")).isNull(); + assertThat(bean.findByIdEmpty("").collectList().block()).isEmpty(); + assertThat(bean.findByIdEmpty("").collectList().block()).isEmpty(); + assertThat(cache.get("").get()).isEqualTo(Collections.emptyList()); + context.close(); } @@ -336,6 +356,10 @@ class CacheReproTests { assertThat(bean.findById("tb1").collectList().block()).isEqualTo(tb); assertThat(cache.get("tb1").get()).isEqualTo(tb); + assertThat(bean.findById("").collectList().block()).isEmpty(); + assertThat(bean.findById("").collectList().block()).isEmpty(); + assertThat(cache.get("").get()).isEqualTo(Collections.emptyList()); + context.close(); } @@ -568,6 +592,11 @@ class CacheReproTests { return CompletableFuture.completedFuture(new TestBean(id)); } + @Cacheable(value = "itemCache") + public CompletableFuture findByIdEmpty(String id) { + return CompletableFuture.completedFuture(null); + } + @CachePut(cacheNames = "itemCache", key = "#item.name") public CompletableFuture insertItem(TestBean item) { return CompletableFuture.completedFuture(item); @@ -584,7 +613,7 @@ class CacheReproTests { @Cacheable(value = "itemCache", sync = true) public CompletableFuture findById(String id) { - return CompletableFuture.completedFuture(new TestBean(id)); + return CompletableFuture.completedFuture(id.isEmpty() ? null : new TestBean(id)); } @CachePut(cacheNames = "itemCache", key = "#item.name") @@ -601,6 +630,11 @@ class CacheReproTests { return Mono.just(new TestBean(id)); } + @Cacheable(value = "itemCache") + public Mono findByIdEmpty(String id) { + return Mono.empty(); + } + @CachePut(cacheNames = "itemCache", key = "#item.name") public Mono insertItem(TestBean item) { return Mono.just(item); @@ -617,7 +651,7 @@ class CacheReproTests { @Cacheable(value = "itemCache", sync = true) public Mono findById(String id) { - return Mono.just(new TestBean(id)); + return (id.isEmpty() ? Mono.empty() : Mono.just(new TestBean(id))); } @CachePut(cacheNames = "itemCache", key = "#item.name") @@ -636,6 +670,11 @@ class CacheReproTests { return Flux.just(new TestBean(id), new TestBean(id + (counter++))); } + @Cacheable(value = "itemCache") + public Flux findByIdEmpty(String id) { + return Flux.empty(); + } + @CachePut(cacheNames = "itemCache", key = "#id") public Flux insertItem(String id, List item) { return Flux.fromIterable(item); @@ -654,7 +693,7 @@ class CacheReproTests { @Cacheable(value = "itemCache", sync = true) public Flux findById(String id) { - return Flux.just(new TestBean(id), new TestBean(id + (counter++))); + return (id.isEmpty() ? Flux.empty() : Flux.just(new TestBean(id), new TestBean(id + (counter++)))); } @CachePut(cacheNames = "itemCache", key = "#id")