Posso definir um TTL para @Cacheable

101

Estou experimentando o @Cacheablesuporte de anotação para Spring 3.1 e me perguntando se há alguma maneira de limpar os dados em cache depois de um tempo, definindo um TTL. Agora, pelo que posso ver, preciso limpar sozinho usando o@CacheEvict , e usando isso junto com @Scheduledposso fazer uma implementação TTL sozinho, mas parece um pouco demais para uma tarefa tão simples?

Piotr
fonte

Respostas:

39

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

Como posso definir o recurso TTL / TTI / Política de despejo / XXX?

Diretamente por meio de seu provedor de cache. A abstração do cache é ... bem, uma abstração, não uma implementação do cache

Portanto, se você usa EHCache, use a configuração do EHCache para configurar o TTL.

Você também pode usar o CacheBuilder do Guava para construir um cache e passar a visualização ConcurrentMap desse cache para o método setStore do ConcurrentMapCacheFactoryBean .

JB Nizet
fonte
57

Spring 3.1 e Goiaba 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();
    }

}
Magnus heino
fonte
21
Para Spring 4.1 estenda CachingConfigurerSupport e somente sobrescreva cacheManager ().
Johannes Flügel
39

Aqui está um exemplo completo de configuração do Guava Cache no Spring. Usei Guava em vez de Ehcache porque é um pouco mais leve e a configuração parecia mais direta para mim.

Importar dependências Maven

Adicione essas dependências ao seu arquivo maven pom e execute clean and packages. Esses arquivos são os métodos Guava dep e Spring auxiliares para uso no 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 o Cache

Você precisa criar um arquivo CacheConfig para configurar o cache usando a configuração Java.

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

Anote o método a ser armazenado em cache

Adicione a anotação @Cacheable e passe o nome do cache.

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

Você pode ver um exemplo mais completo aqui com capturas de tela anotadas: Guava Cache in Spring

anataliocs
fonte
2
Observação: o cache do Guava foi descontinuado no Spring 5 ( stackoverflow.com/questions/44175085/… )
Amin Ziaei
33

Eu uso o hacking da vida assim

@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()));
}
Atum
fonte
Você está chamando o reportCacheEvictmétodo de qualquer lugar. Como o cacheEvict está acontecendo ??
Jaikrat de
Pegue. Não estamos chamando esse método de lugar nenhum. É chamado após o intervalo de tempo fixedDelay. Obrigado pela dica.
Jaikrat
1
Limpar o cache inteiro em um cronograma pode ser um truque útil para fazer as coisas funcionarem, mas esse método não pode ser usado para dar aos itens um TTL. Até mesmo o valor da condição só pode declarar se o cache inteiro deve ser excluído. Subjacente a isso está o fato de que ConcurrentMapCache armazena objetos sem nenhum carimbo de data / hora, portanto, não há como avaliar um TTL no estado em que se encontra.
jmb de
é o código perfeito (este método foi rabiscado :)).
Atum de
Abordagem agradável e limpa
lauksas
30

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;
}

}

e

@Cacheable(value="A", cacheManager="timeoutCacheManager")
public Object getA(){
...
}
jo8937
fonte
Surpreendente! Isso era exatamente o que eu estava procurando
MerLito
6

isso pode ser feito estendendo org.springframework.cache.interceptor.CacheInterceptor e substituindo o método "doPut" - org.springframework.cache.interceptor.AbstractCacheInvoker sua lógica de substituição deve usar o método put do provedor de cache que sabe definir TTL para entrada de cache (no meu caso, uso 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);



}

em sua configuração de cache, você precisa adicionar esses 2 métodos de bean, criando sua instância de interceptor customizada.

@Bean
public CacheOperationSource cacheOperationSource() {
    return new AnnotationCacheOperationSource();
}


@Primary
@Bean
public CacheInterceptor cacheInterceptor() {
    CacheInterceptor interceptor = new MyCustomCacheInterceptor();
    interceptor.setCacheOperationSources(cacheOperationSource());    
    return interceptor;
}

Esta solução é boa quando você deseja definir o TTL no nível de entrada, e não globalmente no nível de cache

lukass77
fonte
2

Desde Spring-boot 1.3.3, você pode definir o tempo de expiração no CacheManager usando RedisCacheManager.setExpires ou RedisCacheManager.setDefaultExpiration no bean de retorno de chamada CacheManagerCustomizer .

Andrew
fonte
0

Ao usar o Redis, o TTL pode ser definido no arquivo de propriedades como este:

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

Hamid Mohayeji
fonte
-2

Se você está trabalhando com redis e Java 8, pode dar uma olhada no JetCache :

@Cached(expire = 10, timeUnit = TimeUnit.MINUTES) User getUserById(long userId);

Huang Li
fonte
1
a pergunta é para a anotação @Cacheable da primavera
satyesht