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 5c7193b521..69967eee44 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCache.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCache.java @@ -185,7 +185,7 @@ public void put(Object key, @Nullable Object value) { } getCacheWriter().put(getName(), createAndConvertCacheKey(key), serializeCacheValue(cacheValue), - getCacheConfiguration().getTtl()); + getCacheConfiguration().getTtl(key, value)); } @Override @@ -198,7 +198,7 @@ public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { } byte[] result = getCacheWriter().putIfAbsent(getName(), createAndConvertCacheKey(key), - serializeCacheValue(cacheValue), getCacheConfiguration().getTtl()); + serializeCacheValue(cacheValue), getCacheConfiguration().getTtl(key, value)); return result != null ? new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result))) : null; } 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 da9b06215b..7b57253a87 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java @@ -17,6 +17,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.function.BiFunction; import java.util.function.Consumer; import org.springframework.cache.Cache; @@ -40,6 +41,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author John Blum + * @author Koy Zhuang * @since 2.0 */ public class RedisCacheConfiguration { @@ -106,7 +108,7 @@ public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader c registerDefaultConverters(conversionService); - return new RedisCacheConfiguration(Duration.ZERO, DEFAULT_CACHE_NULL_VALUES, DEFAULT_USE_PREFIX, + return new RedisCacheConfiguration((k, v) -> Duration.ZERO, DEFAULT_CACHE_NULL_VALUES, DEFAULT_USE_PREFIX, CacheKeyPrefix.simple(), SerializationPair.fromSerializer(RedisSerializer.string()), SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService); @@ -119,17 +121,17 @@ public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader c private final ConversionService conversionService; - private final Duration ttl; + private final BiFunction ttlProvider; private final SerializationPair keySerializationPair; private final SerializationPair valueSerializationPair; @SuppressWarnings("unchecked") - private RedisCacheConfiguration(Duration ttl, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix, + private RedisCacheConfiguration(BiFunction ttlProvider, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix, SerializationPair keySerializationPair, SerializationPair valueSerializationPair, ConversionService conversionService) { - this.ttl = ttl; + this.ttlProvider = ttlProvider; this.cacheNullValues = cacheNullValues; this.usePrefix = usePrefix; this.keyPrefix = keyPrefix; @@ -165,7 +167,7 @@ public RedisCacheConfiguration computePrefixWith(CacheKeyPrefix cacheKeyPrefix) Assert.notNull(cacheKeyPrefix, "Function for computing prefix must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, DEFAULT_USE_PREFIX, cacheKeyPrefix, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, DEFAULT_USE_PREFIX, cacheKeyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -178,7 +180,7 @@ public RedisCacheConfiguration computePrefixWith(CacheKeyPrefix cacheKeyPrefix) * @return new {@link RedisCacheConfiguration}. */ public RedisCacheConfiguration disableCachingNullValues() { - return new RedisCacheConfiguration(ttl, DO_NOT_CACHE_NULL_VALUES, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, DO_NOT_CACHE_NULL_VALUES, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -191,12 +193,12 @@ public RedisCacheConfiguration disableCachingNullValues() { */ public RedisCacheConfiguration disableKeyPrefix() { - return new RedisCacheConfiguration(ttl, cacheNullValues, DO_NOT_USE_PREFIX, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, DO_NOT_USE_PREFIX, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } /** - * Set the ttl to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache. + * Set the constant ttl to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache. * * @param ttl must not be {@literal null}. * @return new {@link RedisCacheConfiguration}. @@ -205,10 +207,25 @@ public RedisCacheConfiguration entryTtl(Duration ttl) { Assert.notNull(ttl, "TTL duration must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration((k, v) -> ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } + /** + * Set the ttl Provider, which can dynamic provide ttl to apply for cache entries. + * @param ttlProvider {@link BiFunction} calculate ttl with the actual original cache key and value, + * which must not be {@literal null}, and the ttl must not be {@literal null} either. + * + * @return new {@link RedisCacheConfiguration}. + */ + public RedisCacheConfiguration entryTtlProvider(BiFunction ttlProvider) { + + Assert.notNull(ttlProvider, "ttlProvider must not be null"); + + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + valueSerializationPair, conversionService); + } + /** * Define the {@link SerializationPair} used for de-/serializing cache keys. * @@ -219,7 +236,7 @@ public RedisCacheConfiguration serializeKeysWith(SerializationPair keySe Assert.notNull(keySerializationPair, "KeySerializationPair must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -233,7 +250,7 @@ public RedisCacheConfiguration serializeValuesWith(SerializationPair valueSer Assert.notNull(valueSerializationPair, "ValueSerializationPair must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -247,7 +264,7 @@ public RedisCacheConfiguration withConversionService(ConversionService conversio Assert.notNull(conversionService, "ConversionService must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -301,9 +318,21 @@ public SerializationPair getValueSerializationPair() { } /** - * @return The expiration time (ttl) for cache entries. Never {@literal null}. + * @return The constant expiration time (ttl) for cache entries. Never {@literal null}. */ public Duration getTtl() { + Duration ttl = ttlProvider.apply(Object.class, Object.class); + Assert.notNull(ttl, "TTL duration must not be null"); + return ttl; + } + + /** + * @return The expiration time (ttl) for cache entries with original key and value. Never {@literal null}. + */ + public Duration getTtl(Object key, Object val) { + Duration ttl = ttlProvider.apply(key, val); + Assert.notNull(ttl, "TTL duration must not be null"); + return ttl; } diff --git a/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java b/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java index a86c9b23fb..7dde2f40a0 100644 --- a/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java @@ -24,6 +24,9 @@ import org.springframework.instrument.classloading.ShadowingClassLoader; import org.springframework.lang.Nullable; +import java.time.Duration; +import java.util.function.BiFunction; + /** * Unit tests for {@link RedisCacheConfiguration}. * @@ -56,6 +59,21 @@ void shouldAllowConverterRegistration() { assertThat(config.getConversionService().canConvert(DomainType.class, String.class)).isTrue(); } + + @Test + void shouldGetDynamicTtlGivenTtlProvider() { + + BiFunction ttlProvider = (key, val) -> + Duration.ofSeconds(Integer.parseInt(key + String.valueOf(val))); + + RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() + .entryTtlProvider(ttlProvider); + + assertThat(defaultCacheConfiguration.getTtl(1, 12)).isEqualTo(Duration.ofSeconds(112)); + assertThat(defaultCacheConfiguration.getTtl(15, 22)).isEqualTo(Duration.ofSeconds(1522)); + assertThat(defaultCacheConfiguration.getTtl(77, 0)).isEqualTo(Duration.ofSeconds(770)); + } + private static class DomainType { }