Can I set a TTL for @Cacheable
JavaSpringJava Problem Overview
I am trying out the @Cacheable
annotation support for Spring 3.1 and wondering if there is any way to make the cached data clear out after a time by setting a TTL?
Right now from what I can see I need to clear it out myself by using the @CacheEvict
, and by using that together with @Scheduled
I can make a TTL implementation myself but it seems a bit much for such a simple task?
Java Solutions
Solution 1 - Java
Spring 3.1 and Guava 1.13.1:
@EnableCaching
@Configuration
public class CacheConfiguration implements CachingConfigurer {
@Override
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager() {
@Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name,
CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).maximumSize(100).build().asMap(), false);
}
};
return cacheManager;
}
@Override
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
}
Solution 2 - Java
I use life hacking like this
@Configuration
@EnableCaching
@EnableScheduling
public class CachingConfig {
public static final String GAMES = "GAMES";
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(GAMES);
return cacheManager;
}
@CacheEvict(allEntries = true, value = {GAMES})
@Scheduled(fixedDelay = 10 * 60 * 1000 , initialDelay = 500)
public void reportCacheEvict() {
System.out.println("Flush Cache " + dateFormat.format(new Date()));
}
Solution 3 - Java
> How can I set the TTL/TTI/Eviction policy/XXX feature? > > Directly through your cache provider. The cache abstraction is... > well, an abstraction not a cache implementation
So, if you use EHCache, use EHCache's configuration to configure the TTL.
You could also use Guava's CacheBuilder to build a cache, and pass this cache's ConcurrentMap view to the setStore method of the ConcurrentMapCacheFactoryBean.
Solution 4 - Java
Here is a full example of setting up Guava Cache in Spring. I used Guava over Ehcache because it's a bit lighter weight and the config seemed more straight forward to me.
Import Maven Dependencies
Add these dependencies to your maven pom file and run clean and packages. These files are the Guava dep and Spring helper methods for use in the CacheBuilder.
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
Configure the Cache
You need to create a CacheConfig file to configure the cache using Java config.
@Configuration
@EnableCaching
public class CacheConfig {
public final static String CACHE_ONE = "cacheOne";
public final static String CACHE_TWO = "cacheTwo";
@Bean
public Cache cacheOne() {
return new GuavaCache(CACHE_ONE, CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.MINUTES)
.build());
}
@Bean
public Cache cacheTwo() {
return new GuavaCache(CACHE_TWO, CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.build());
}
}
Annotate the method to be cached
Add the @Cacheable annotation and pass in the cache name.
@Service
public class CachedService extends WebServiceGatewaySupport implements CachedService {
@Inject
private RestTemplate restTemplate;
@Cacheable(CacheConfig.CACHE_ONE)
public String getCached() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> reqEntity = new HttpEntity<>("url", headers);
ResponseEntity<String> response;
String url = "url";
response = restTemplate.exchange(
url,
HttpMethod.GET, reqEntity, String.class);
return response.getBody();
}
}
You can see a more complete example here with annotated screenshots: Guava Cache in Spring
Solution 5 - Java
Springboot 1.3.8
import java.util.concurrent.TimeUnit;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.guava.GuavaCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.cache.CacheBuilder;
@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
@Override
@Bean
public CacheManager cacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager();
return cacheManager;
}
@Bean
public CacheManager timeoutCacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager();
CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(5, TimeUnit.SECONDS);
cacheManager.setCacheBuilder(cacheBuilder);
return cacheManager;
}
}
and
@Cacheable(value="A", cacheManager="timeoutCacheManager")
public Object getA(){
...
}
Solution 6 - Java
this can be done by extending org.springframework.cache.interceptor.CacheInterceptor , and override "doPut" method - org.springframework.cache.interceptor.AbstractCacheInvoker your override logic should use the cache provider put method that knows to set TTL for cache entry (in my case I use HazelcastCacheManager)
@Autowired
@Qualifier(value = "cacheManager")
private CacheManager hazelcastCacheManager;
@Override
protected void doPut(Cache cache, Object key, Object result) {
//super.doPut(cache, key, result);
HazelcastCacheManager hazelcastCacheManager = (HazelcastCacheManager) this.hazelcastCacheManager;
HazelcastInstance hazelcastInstance = hazelcastCacheManager.getHazelcastInstance();
IMap<Object, Object> map = hazelcastInstance.getMap("CacheName");
//set time to leave 18000 secondes
map.put(key, result, 18000, TimeUnit.SECONDS);
}
on your cache configuration you need to add those 2 bean methods , creating your custom interceptor instance .
@Bean
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
@Primary
@Bean
public CacheInterceptor cacheInterceptor() {
CacheInterceptor interceptor = new MyCustomCacheInterceptor();
interceptor.setCacheOperationSources(cacheOperationSource());
return interceptor;
}
This solution is good when you want to set the TTL on the entry level , and not globally on cache level
Solution 7 - Java
When using Redis, TTL can be set in properties file like this:
spring.cache.redis.time-to-live=1d # 1 day
spring.cache.redis.time-to-live=5m # 5 minutes
spring.cache.redis.time-to-live=10s # 10 seconds
Solution 8 - Java
The easiest way for me was using the Caffeine cache which is configurable directly in your application.yml
file.
You can setup the TTL by the expireAfterWrite
parameter. Example configuration would look as follows:
spring:
cache:
caffeine:
spec: expireAfterWrite=15m
cache-names: mycache
This will evict the elements after 15 minutes.
Solution 9 - Java
Since Spring-boot 1.3.3, you may set expire time in CacheManager by using RedisCacheManager.setExpires or RedisCacheManager.setDefaultExpiration in CacheManagerCustomizer call-back bean.
Solution 10 - Java
Redis based solution
- Spring boot: 2.2.6
- Cache store: Redis (
spring-boot-starter-cache
along withspring-boot-starter-data-redis
) - Java version: 8
Implementation:
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import java.time.Duration;
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManagerBuilderCustomizer customizer() {
return builder -> builder
.withCacheConfiguration("cacheKey1",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(14)))
.withCacheConfiguration("cacheKey2",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)))
.withCacheConfiguration("cacheKey3",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)));
}
}
Solution 11 - Java
If you are working with redis and Java 8, you can take a look at JetCache:
User getUserById(long userId);