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