Caso real · Java & Spring Boot

@Cacheable não funciona no Spring Boot? O problema da chamada via this

Edson Feitosa de Oliveira Junior
Edson Feitosa de Oliveira Junior Fundador · PruPru Digital Services · Natal/RN

Num sistema interno que construí para os times comercial e de logística de uma distribuidora aqui de Natal, as telas de relatório estavam lentas mesmo com @Cacheable nos métodos que consultavam o banco de BI. Os logs mostravam a mesma consulta pesada sendo executada a cada clique. A anotação estava lá, o cache estava habilitado com @EnableCaching e mesmo assim nada era cacheado.

A causa era uma das armadilhas mais comuns do Spring: o método anotado era chamado de dentro da própria classe. Este post mostra por que isso acontece e como corrigi.

O código que parecia certo

@Service
public class ComercialService {

    public PainelDTO montarPainel(String regiao) {
        // chamada interna: passa por "this", não pelo proxy
        List<VendaDTO> vendas = this.buscarVendas(regiao);
        return new PainelDTO(vendas);
    }

    @Cacheable("vendas-por-regiao")
    public List<VendaDTO> buscarVendas(String regiao) {
        return biRepository.consultaPesada(regiao);
    }
}

O controller chama montarPainel(), que chama buscarVendas(). Quem lê o código espera que a segunda chamada já venha do cache. Não vem.

Por que o cache é ignorado

O Spring implementa @Cacheable (assim como @Transactional e @Async) com proxies de AOP. O objeto que outros beans recebem por injeção não é a sua classe pura: é um proxy que envolve a classe e verifica o cache antes de repassar a chamada.

A documentação do Spring chama isso de self-invocation. Não aparece erro nem aviso. O método só executa normalmente, sem cache.

Como confirmar que é isso

  1. Ligue o log do cache: logging.level.org.springframework.cache=TRACE no application.properties. Se a chamada interna não gera nenhuma linha de “cache hit” ou “cache miss”, o proxy não está sendo usado.
  2. Coloque um log dentro do método cacheado. Se ele aparece em toda chamada com o mesmo parâmetro, o cache não está funcionando.
  3. Procure no código chamadas ao método anotado feitas pela mesma classe, com ou sem this. explícito.

4 formas de corrigir

1. Extrair o método para outro bean (a que eu usei)

É a solução mais simples de entender e a que eu adotei em produção: criei um bean dedicado só para as consultas cacheadas e injetei no serviço original.

@Service
public class ComercialDataCacheService {

    @Cacheable("vendas-por-regiao")
    public List<VendaDTO> buscarVendas(String regiao) {
        return biRepository.consultaPesada(regiao);
    }
}

@Service
@RequiredArgsConstructor
public class ComercialService {

    private final ComercialDataCacheService cache;

    public PainelDTO montarPainel(String regiao) {
        return new PainelDTO(cache.buscarVendas(regiao)); // passa pelo proxy
    }
}

Além de resolver o cache, a separação deixou claro onde ficam todas as consultas cacheadas, o que facilita invalidar com @CacheEvict depois.

2. Injetar o próprio bean com @Lazy

@Service
public class ComercialService {

    @Autowired @Lazy
    private ComercialService self;

    public PainelDTO montarPainel(String regiao) {
        return new PainelDTO(self.buscarVendas(regiao));
    }
}

Funciona, mas esconde a dependência circular e confunde quem chega no código depois. Eu evito.

3. AopContext.currentProxy()

Com @EnableAspectJAutoProxy(exposeProxy = true), dá para chamar ((ComercialService) AopContext.currentProxy()).buscarVendas(regiao). Prende o código de negócio ao Spring AOP e é fácil de esquecer. Só vale como remendo temporário.

4. AspectJ em modo de compilação

@EnableCaching(mode = AdviceMode.ASPECTJ) com weaving em tempo de compilação ou de carga resolve a auto-invocação de verdade, porque o código do cache passa a ficar dentro da própria classe. Em troca, o build fica bem mais complexo. Raramente compensa num projeto pequeno ou médio.

Outros motivos para o @Cacheable não funcionar

A mesma armadilha vale para @Transactional e @Async

Um método @Transactional chamado via this não abre transação. Um @Async chamado via this roda na mesma thread. Se uma dessas anotações “não faz nada”, a primeira coisa a verificar é se a chamada vem de dentro da mesma classe.

Perguntas frequentes

Por que o @Cacheable funciona no controller mas não dentro do serviço?

Porque a chamada que vem do controller passa pelo proxy que o Spring cria em volta do serviço. A chamada interna, via this, vai direto ao objeto real e pula o proxy, então a lógica de cache não roda.

Qual a melhor forma de corrigir a auto-invocação no Spring?

Na maioria dos casos, mover o método anotado para outro bean e injetá-lo. É explícito, fácil de testar e não depende de recursos avançados de AOP.

Isso também afeta @Transactional?

Sim. @Transactional, @Async, @Cacheable e outras anotações baseadas em proxy são ignoradas quando o método é chamado pela própria classe.

Precisa de ajuda com um sistema assim?

A PruPru desenvolve e corrige sistemas web em Java/Spring Boot e React para empresas de Natal/RN e do restante do Brasil. Conte o problema pelo WhatsApp e receba um diagnóstico sem compromisso.

Ver serviços →

Sobre o autor

Edson Feitosa de Oliveira Junior é fundador da PruPru Digital Services, em Natal/RN. Trabalhou 15 anos como técnico em segurança eletrônica (2002–2017), migrou para o desenvolvimento de software e atuou como desenvolvedor no Mercado Livre (2022–2025). Hoje é desenvolvedor backend Java em uma distribuidora de Natal e cursa Engenharia de Software (UniFatecie) e Sistemas para Internet (IFRN). Os casos deste blog marcados como “caso real” vêm de sistemas que ele construiu e mantém em produção.

Falar no WhatsApp agora