Introdução
A gestão de cache é um componente crucial para otimizar o desempenho de aplicações, reduzindo a latência e a carga sobre os bancos de dados ou serviços externos. O Spring Framework oferece uma abstração de cache robusta via Spring Cache, que pode ser facilmente integrada com diversas tecnologias de cache. Este artigo explora como configurar e utilizar o Spring Cache com Redis para aplicar tempos de vida (TTLs) específicos para diferentes caches, aproveitando a flexibilidade e o armazenamento distribuído do Redis.
Adicionando Dependências
Para integrar o Spring Cache com Redis, é necessário incluir as dependências apropriadas no arquivo pom.xml do seu projeto Maven (ou equivalente para Gradle):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
A dependência spring-boot-starter-data-redis fornece a integração com o Redis, enquanto commons-pool2 é necessária para o pool de conexões do Lettuce (cliente Redis padrão do Spring Boot).
Configuração do Redis no Application Properties
As propriedades de conexão com o servidor Redis são definidas no arquivo application.properties ou application.yml:
# Endereço do servidor Redis
spring.redis.host=127.0.0.1
# Porta do servidor Redis
spring.redis.port=6379
# Senha do servidor Redis (se houver, caso contrário, deixe vazio)
spring.redis.password=
# Tipo de cache, geralmente configurado automaticamente
spring.cache.type=redis
# Tempo limite de conexão com o Redis em milissegundos
spring.redis.timeout=10000
# Base de dados Redis a ser utilizada (0-15)
spring.redis.database=0
# Configurações do pool de conexões Lettuce
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
Configuração Personalizada do CacheManager
Para definir tempos de expiração específicos para diferentes caches, é preciso configurar o CacheManager. Isso geralmente é feito em uma classe de configuração Spring.
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Configuration
public class ConfiguracaoCacheRedis {
/**
* Configura o CacheManager para usar Redis, permitindo TTLs personalizados.
* @param connectionFactory A fábrica de conexões Redis.
* @return Uma instância de CacheManager configurada.
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
// Configuração padrão de serialização e tempo de vida para caches não especificados.
RedisCacheConfiguration defaultConfig = criarConfiguracaoCache(30 * 60); // 30 minutos padrão
Map<String, RedisCacheConfiguration> cacheSpecificConfigs = new HashMap<>();
// Configurações personalizadas por nome de cache.
cacheSpecificConfigs.put("cacheDiario", criarConfiguracaoCache(24 * 60 * 60)); // 1 dia
cacheSpecificConfigs.put("cacheCurtoPrazo", criarConfiguracaoCache(5)); // 5 segundos
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory))
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(cacheSpecificConfigs)
.build();
}
/**
* Cria uma configuração de cache Redis com serializador JSON e TTL.
* @param ttlSeconds Tempo de vida em segundos para o cache.
* @return Uma configuração de cache Redis.
*/
private RedisCacheConfiguration criarConfiguracaoCache(long ttlSeconds) {
// Serializador para converter objetos Java em JSON para o Redis.
Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// Habilita tipagem padrão para lidar com a desserialização de tipos complexos ou polimórficos.
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jsonSerializer.setObjectMapper(mapper);
return RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer))
.entryTtl(Duration.ofSeconds(ttlSeconds)); // Define o tempo de vida.
}
/**
* Define um gerador de chaves personalizado para o Spring Cache.
* Isso permite criar chaves de cache mais descritivas e customizadas.
* A chave gerada terá o formato: NomeDaClasse::NomeDoMetodo?arg1-arg2-...
* @return Uma instância de KeyGenerator.
*/
@Bean
public KeyGenerator geradorDeChavesPersonalizado() {
return (alvo, metodo, argumentos) -> {
StringBuilder chave = new StringBuilder();
chave.append(alvo.getClass().getSimpleName()).append("::");
chave.append(metodo.getName());
if (argumentos == null || argumentos.length == 0) {
return chave.toString();
}
// Concatena apenas os argumentos não nulos
String argsString = Arrays.stream(argumentos)
.filter(Objects::nonNull)
.map(Object::toString)
.collect(Collectors.joining("-"));
if (!argsString.isEmpty()) {
chave.append("?").append(argsString);
}
return chave.toString();
};
}
}
Nesta configuração:
- O método
cacheManageré responsável por construir oRedisCacheManager. Ele define uma política padrão de 30 minutos e um mapa de configurações específicas para os nomes de cache "cacheDiario" (1 dia) e "cacheCurtoPrazo" (5 segundos). - O método
criarConfiguracaoCacheé um utilitário que cria umaRedisCacheConfigurationcom base em um TTL fornecido. Ele utilizaJackson2JsonRedisSerializerpara serializar os objetos em JSON antes de armazená-los no Redis, garantindo que objetos complexos sejam armazenados e recuperados corretamente. - O
geradorDeChavesPersonalizadocria chaves de cache mais legíveis e consistentes, combinando o nome da classe, método e argumentos.
Utilizando Caches com TTLs Diferenciados
Com o CacheManager configurado, você pode usar a anotação @Cacheable em seus serviços, especificando o nome do cache que corresponde a uma das configurações de TTL definidas:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalTime;
@Service
public class ServicoDados {
/**
* Busca um dado que expira rapidamente (5 segundos).
* Usa o cache "cacheCurtoPrazo".
* @param id Identificador do dado.
* @return O dado simulado.
*/
@Cacheable(value = "cacheCurtoPrazo")
public String buscarDadoCurtoPrazo(String id) {
System.out.println("Buscando dado de curto prazo para id: " + id + " no banco de dados...");
// Simula uma operação de banco de dados ou serviço externo
try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
return "Dado de Curto Prazo para " + id + " @ " + LocalTime.now();
}
/**
* Busca um dado que tem um tempo de vida mais longo (1 dia).
* Usa o cache "cacheDiario".
* @param code Código do dado.
* @return O dado simulado.
*/
@Cacheable(value = "cacheDiario")
public String buscarDadoDiario(Integer code) {
System.out.println("Buscando dado diário para código: " + code + " no banco de dados...");
// Simula uma operação de banco de dados ou serviço externo
try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
return "Dado Diário para código " + code + " @ " + LocalDate.now();
}
}
Neste exemplo, o método buscarDadoCurtoPrazo utilizará a configuração de cache com 5 segundos de TTL, enquanto buscarDadoDiario usará a configuração de 1 dia. O Spring Cache gerenciará automaticamente o armazenamento e a expiração no Redis conforme as políticas definidas.