Skip to content

Allow to dynamically provide TTL configuration #2587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,6 +41,7 @@
* @author Christoph Strobl
* @author Mark Paluch
* @author John Blum
* @author Koy Zhuang
* @since 2.0
*/
public class RedisCacheConfiguration {
Expand Down Expand Up @@ -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);
Expand All @@ -119,17 +121,17 @@ public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader c

private final ConversionService conversionService;

private final Duration ttl;
private final BiFunction<Object, Object, Duration> ttlProvider;

private final SerializationPair<String> keySerializationPair;
private final SerializationPair<Object> valueSerializationPair;

@SuppressWarnings("unchecked")
private RedisCacheConfiguration(Duration ttl, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix,
private RedisCacheConfiguration(BiFunction<Object, Object, Duration> ttlProvider, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix,
SerializationPair<String> keySerializationPair, SerializationPair<?> valueSerializationPair,
ConversionService conversionService) {

this.ttl = ttl;
this.ttlProvider = ttlProvider;
this.cacheNullValues = cacheNullValues;
this.usePrefix = usePrefix;
this.keyPrefix = keyPrefix;
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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}.
Expand All @@ -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<Object, Object, Duration> 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.
*
Expand All @@ -219,7 +236,7 @@ public RedisCacheConfiguration serializeKeysWith(SerializationPair<String> 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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -301,9 +318,21 @@ public SerializationPair<Object> 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
*
Expand Down Expand Up @@ -56,6 +59,21 @@ void shouldAllowConverterRegistration() {
assertThat(config.getConversionService().canConvert(DomainType.class, String.class)).isTrue();
}


@Test
void shouldGetDynamicTtlGivenTtlProvider() {

BiFunction<Object, Object, Duration> 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 {

}
Expand Down