diff --git a/src/main/asciidoc/reference/redis.adoc b/src/main/asciidoc/reference/redis.adoc index cd00917a6..5fd0f5693 100644 --- a/src/main/asciidoc/reference/redis.adoc +++ b/src/main/asciidoc/reference/redis.adoc @@ -567,6 +567,18 @@ RedisCacheManager cm = RedisCacheManager.build(RedisCacheWriter.lockingRedisCach ... ---- +By default any `key` for a cache entry gets prefixed with the actual cache name followed by 2 colons. +This behavior can be changed to a static as well as a computed prefix. + +[source,java] +---- +// static key prefix +RedisCacheConfiguration.defaultCacheConfig().prefixKeysWith("( ͡° ᴥ ͡°)"); + +// computed key prefix +RedisCacheConfiguration.defaultCacheConfig().computePrefixWith(cacheName -> "¯\_(ツ)_/¯" + cacheName); +---- + .RedisCacheManager defaults [width="80%",cols="<1,<2",options="header"] |==== diff --git a/src/main/java/org/springframework/data/redis/cache/CacheKeyPrefix.java b/src/main/java/org/springframework/data/redis/cache/CacheKeyPrefix.java new file mode 100644 index 000000000..41e446932 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/CacheKeyPrefix.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.cache; + +/** + * {@link CacheKeyPrefix} provides a hook for creating custom prefixes prepended to the actual {@literal key} stored in + * Redis. + * + * @author Christoph Strobl + * @since 2.0.4 + */ +@FunctionalInterface +public interface CacheKeyPrefix { + + /** + * Compute the prefix for the actual {@literal key} stored in Redis. + * + * @param cacheName will never be {@literal null}. + * @return never {@literal null}. + */ + String compute(String cacheName); +} diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCache.java b/src/main/java/org/springframework/data/redis/cache/RedisCache.java index 48f9058ea..c9456db3d 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCache.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCache.java @@ -300,7 +300,7 @@ public class RedisCache extends AbstractValueAdaptingCache { } private String prefixCacheKey(String key) { - return cacheConfig.getKeyPrefix().orElseGet(() -> name + "::") + key; + return cacheConfig.getKeyPrefixFor(name).orElseGet(() -> name + "::") + key; } private static T valueFromLoader(Object key, Callable valueLoader) { diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java b/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java index e39c744f6..32030c263 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.data.redis.cache; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Optional; +import java.util.function.Function; import org.springframework.cache.Cache; import org.springframework.cache.interceptor.SimpleKey; @@ -43,7 +44,7 @@ public class RedisCacheConfiguration { private final Duration ttl; private final boolean cacheNullValues; - private final @Nullable String keyPrefix; + private final @Nullable CacheKeyPrefix keyPrefix; private final boolean usePrefix; private final SerializationPair keySerializationPair; @@ -52,9 +53,9 @@ public class RedisCacheConfiguration { private final ConversionService conversionService; @SuppressWarnings("unchecked") - private RedisCacheConfiguration(Duration ttl, Boolean cacheNullValues, Boolean usePrefix, @Nullable String keyPrefix, - SerializationPair keySerializationPair, SerializationPair valueSerializationPair, - ConversionService conversionService) { + private RedisCacheConfiguration(Duration ttl, Boolean cacheNullValues, Boolean usePrefix, + @Nullable CacheKeyPrefix keyPrefix, SerializationPair keySerializationPair, + SerializationPair valueSerializationPair, ConversionService conversionService) { this.ttl = ttl; this.cacheNullValues = cacheNullValues; @@ -122,10 +123,39 @@ public class RedisCacheConfiguration { Assert.notNull(prefix, "Prefix must not be null!"); + return prefixKeysWith((cacheName) -> prefix); + } + + /** + * Use the given {@link CacheKeyPrefix} computing the prefix based on the {@literal cache name}. + * + * @param prefix must not be {@literal null}. + * @return new {@link RedisCacheConfiguration}. + * @since 2.0.4 + */ + public RedisCacheConfiguration prefixKeysWith(CacheKeyPrefix prefix) { + + Assert.notNull(prefix, "Prefix must not be null!"); + return new RedisCacheConfiguration(ttl, cacheNullValues, true, prefix, keySerializationPair, valueSerializationPair, conversionService); } + /** + * Use the given {@link Function} to compute the prefix for the actual Redis {@literal key} on the + * {@literal cache name}. + * + * @param function must not be {@literal null}. + * @return new {@link RedisCacheConfiguration}. + * @since 2.0.4 + */ + public RedisCacheConfiguration computePrefixWith(Function function) { + + Assert.notNull(function, "Function for computing prefix must not be null!"); + + return prefixKeysWith(function::apply); + } + /** * Disable caching {@literal null} values.
* NOTE any {@link org.springframework.cache.Cache#put(Object, Object)} operation involving @@ -196,14 +226,36 @@ public class RedisCacheConfiguration { /** * @return never {@literal null}. + * @deprecated since 2.0.4. Please use {@link #getKeyPrefixFor(String)}. */ + @Deprecated() public Optional getKeyPrefix() { + return getCacheKeyPrefix().map(val -> val.compute("")); + } + + /** + * Get the computed {@literal key} prefix for a given {@literal cacheName}. + * + * @return never {@literal null}. + * @since 2.0.4 + */ + public Optional getKeyPrefixFor(String cacheName) { + return getCacheKeyPrefix().map(val -> val.compute(cacheName)); + } + + /** + * Obtain the {@link CacheKeyPrefix} used to compute the actual {@literal key} prefix. + * + * @return never {@literal null}. + * @since 2.0.4 + */ + public Optional getCacheKeyPrefix() { return Optional.ofNullable(keyPrefix); } /** - * @return {@literal true} if cache keys need to be prefixed with the {@link #getKeyPrefix()} if present or the - * default which resolves to {@link Cache#getName()}. + * @return {@literal true} if cache keys need to be prefixed with the {@link #getKeyPrefixFor(String)} if present or + * the default which resolves to {@link Cache#getName()}. */ public boolean usePrefix() { return usePrefix; diff --git a/src/test/java/org/springframework/data/redis/cache/RedisCacheTests.java b/src/test/java/org/springframework/data/redis/cache/RedisCacheTests.java index c35ebff7d..878cd4813 100644 --- a/src/test/java/org/springframework/data/redis/cache/RedisCacheTests.java +++ b/src/test/java/org/springframework/data/redis/cache/RedisCacheTests.java @@ -269,6 +269,37 @@ public class RedisCacheTests { }); } + @Test // DATAREDIS-715 + public void computePrefixCreatesCacheKeyCorrectly() { + + RedisCache cacheWithCustomPrefix = new RedisCache("cache", new DefaultRedisCacheWriter(connectionFactory), + RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(SerializationPair.fromSerializer(serializer)) + .computePrefixWith(cacheName -> "_" + cacheName + "_")); + + cacheWithCustomPrefix.put("key-1", sample); + + doWithConnection(connection -> { + + assertThat(connection.stringCommands().get("_cache_key-1".getBytes(Charset.forName("UTF-8")))) + .isEqualTo(binarySample); + }); + } + + @Test // DATAREDIS-715 + public void fetchKeyWithComputedPrefixReturnsExpectedResult() { + + doWithConnection(connection -> connection.set("_cache_key-1".getBytes(Charset.forName("UTF-8")), binarySample)); + + RedisCache cacheWithCustomPrefix = new RedisCache("cache", new DefaultRedisCacheWriter(connectionFactory), + RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(SerializationPair.fromSerializer(serializer)) + .computePrefixWith(cacheName -> "_" + cacheName + "_")); + + ValueWrapper result = cacheWithCustomPrefix.get(key); + + assertThat(result).isNotNull(); + assertThat(result.get()).isEqualTo(sample); + } + void doWithConnection(Consumer callback) { RedisConnection connection = connectionFactory.getConnection(); try {