Can I set a TTL for @Cacheable

JavaSpring

Java 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

See http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/htmlsingle/spring-framework-reference.html#cache-specific-config:

> 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 with spring-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);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPiotrView Question on Stackoverflow
Solution 1 - JavaMagnus HeinoView Answer on Stackoverflow
Solution 2 - JavaAtumView Answer on Stackoverflow
Solution 3 - JavaJB NizetView Answer on Stackoverflow
Solution 4 - JavaanataliocsView Answer on Stackoverflow
Solution 5 - Javajo8937View Answer on Stackoverflow
Solution 6 - Javalukass77View Answer on Stackoverflow
Solution 7 - JavaHamid MohayejiView Answer on Stackoverflow
Solution 8 - JavaAleksander LechView Answer on Stackoverflow
Solution 9 - JavaAndrewView Answer on Stackoverflow
Solution 10 - JavaHereAndBeyondView Answer on Stackoverflow
Solution 11 - JavaHuang LiView Answer on Stackoverflow