💻 Exemplos: Capítulo 14: Cache Corporativo com Spring Data Redis (@Cacheable)
Especialização em Backend com Java & Spring Boot • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 01 — @EnableCaching e Configuração Básica do RedisCacheManager
package com.empresa.api.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching // ativa o proxy AOP que intercepta @Cacheable/@CacheEvict/@CachePut
public class CacheConfig {
}
package com.empresa.api.service;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class CotacaoService {
@Cacheable(value = "cotacoes", key = "#moeda")
public double buscarCotacaoAtual(String moeda) {
System.out.println("LOG: consultando serviço externo de câmbio para " + moeda);
// Simulação de chamada cara a uma API externa de câmbio.
return switch (moeda) {
case "USD" -> 5.42;
case "EUR" -> 5.87;
default -> throw new IllegalArgumentException("Moeda não suportada: " + moeda);
};
}
}
💻 Código de Exemplo 02 — Prevenção de Cache Stampede com Lock Distribuído Redisson
package com.empresa.api.service;
import com.empresa.api.model.Produto;
import com.empresa.api.repository.ProdutoRepository;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class ProdutoStampedeSafeService {
private final ProdutoRepository produtoRepository;
private final RedissonClient redissonClient;
public ProdutoStampedeSafeService(ProdutoRepository produtoRepository, RedissonClient redissonClient) {
this.produtoRepository = produtoRepository;
this.redissonClient = redissonClient;
}
@Cacheable(value = "produtos-populares", key = "#id")
public Produto buscarComProtecaoContraStampede(Long id) throws InterruptedException {
RLock lock = redissonClient.getLock("lock:produto:" + id);
// Apenas a primeira requisição que perde o cache recalcula; as demais aguardam até 5s
// pelo lock e, ao obtê-lo, o Spring Cache já terá sido populado pela primeira chamada.
boolean adquiriu = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!adquiriu) {
throw new IllegalStateException("Não foi possível adquirir o lock para recalcular o cache");
}
try {
System.out.println("LOG: cache-miss real, consultando banco para produto " + id);
return produtoRepository.buscarPorId(id);
} finally {
lock.unlock();
}
}
}
💻 Código de Exemplo 03 — Lock Distribuído para Baixa Segura de Estoque em Cluster
package com.empresa.api.service;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class EstoqueLockService {
private final RedissonClient redissonClient;
private final EstoqueRepository estoqueRepository;
public EstoqueLockService(RedissonClient redissonClient, EstoqueRepository estoqueRepository) {
this.redissonClient = redissonClient;
this.estoqueRepository = estoqueRepository;
}
public void reservarItem(String produtoId, int quantidade) throws InterruptedException {
RLock rLock = redissonClient.getLock("estoque:" + produtoId);
// Espera até 5s pelo lock; se obtido, expira sozinho em 10s (evita deadlock se a instância cair).
boolean adquiriu = rLock.tryLock(5, 10, TimeUnit.SECONDS);
if (!adquiriu) {
throw new IllegalStateException("Estoque do produto " + produtoId + " ocupado, tente novamente");
}
try {
int disponivel = estoqueRepository.consultarQuantidade(produtoId);
if (disponivel < quantidade) {
throw new IllegalStateException("Estoque insuficiente para o produto " + produtoId);
}
estoqueRepository.decrementarQuantidade(produtoId, quantidade);
} finally {
rLock.unlock();
}
}
}