Solucionado (ver solução)
Solucionado
(ver solução)
2
respostas

[Bug] Cache feito para uma chamada sendo reutilizado em outra com parâmetros distintos

Na aula sobre Redis, as anotações de Cache foram movidas do Controller para o Service. Porém, uma chamada GET pode vir com ou sem parâmetros (nomeCurso para busca por nome, detalhes de paginação como Page e Size). Sendo assim, o Cache armazenado para uma busca específica, acaba sendo utilizado como retorno para outra busca.

Existe alguma forma nas anotações do Service de registrar os parametros que foram utilizados, para caso sejam distintos numa proxima requisição, não seja utilizado o cache?

A solução que pensei, não sei se a mais correta, seria deixar as anotações no controller e separar as chamadas como exemplo:

Chamada GET caso 1: Sem qualquer filtro

Chamada GET caso 2: Com paginação ou Busca por nome

Sendo que a unica que possuiria cache seria a sem filtro, visto que seria sempre a mesma requisição

Abaixo, trechos do código da aula:

class TopicoController(private val service: TopicoService) {

    @GetMapping
    fun listar(@RequestParam(required = false) nomeCurso : String?,
               @PageableDefault(size=5, sort = ["dataCriacao"], direction = Sort.Direction.DESC) paginacao: Pageable)
    : Page<TopicoView> = service.listar(nomeCurso, paginação)

// resto omitido
@Service
class TopicoService(
    private val repository: TopicoRepository,
    private val topicoViewMapper: TopicoViewMapper,
    private val topicoFormMapper : TopicoFormMapper,
    private val notFoundMessage : String = "Tópico não encontrado"
    )
{

    @Cacheable(cacheNames = ["topicos"], key = "#root.method.name")
    fun listar(nomeCurso: String?, paginacao: Pageable): Page<TopicoView> {
        val topicos = nomeCurso
            ?.let {repository.findByCursoNome(nomeCurso, paginacao)}
            ?: repository.findAll(paginacao)

        return topicos.map { topicoViewMapper.map(it) }
    }

// resto omitido
2 respostas

Solucionei da seguinte forma: Toda vez que que a chamada for feita, verifico se as variáveis passadas são iguais ao padrão setado no controller.

  • caso sim, salva em cache
  • caso não, apaga totalmente o cache e retorna a busca específica, sem salvar.
   @CacheEvict(cacheNames = ["topicos"], key = "#root.method.name", condition = "#nomeCurso != null || #paginacao.pageSize != 5", allEntries = true)
    @CachePut(cacheNames = ["topicos"], key = "#root.method.name", unless = "#nomeCurso != null || #paginacao.pageSize != 5")
    fun listar(nomeCurso: String?, paginacao: Pageable): Page<TopicoView> {
        val topicos = nomeCurso
            ?.let {repository.findByCursoNome(nomeCurso, paginacao)}
            ?: repository.findAll(paginacao)

        return topicos.map { topicoViewMapper.map(it) }
    }

Talvez para melhorar, a classe possa ter 2 properties para armazenar os últimos parâmetros utilizados e o método chamado possa atualizar essas properties, deixando os parâmetros condition e unless fazerem a verificação do passado no request com o anterior salvo nas properties... Não sei se ja existe anotação para isso, ou algo mais simples... mas por hora, essa solução acima resolveu parcialmente o problema.

solução!

Olá, Felipe! Entendo sua preocupação sobre o cache sendo reutilizado para chamadas distintas. No Spring, a anotação @Cacheable suporta a adição de múltiplos parâmetros na chave do cache. Isso significa que você pode incluir os parâmetros da sua função listar na chave do cache.

No seu caso, você poderia alterar a anotação @Cacheable no seu serviço para algo como:

@Cacheable(cacheNames = ["topicos"], key = "#nomeCurso + #paginacao")

Dessa forma, o cache será distinto para cada combinação de nomeCurso e paginacao.

Contudo, é importante considerar que isso pode levar a um grande número de entradas de cache se você tiver muitas combinações possíveis de parâmetros. Portanto, é sempre uma boa prática monitorar o uso do cache para garantir que ele não está consumindo mais memória do que o desejado.

Sobre sua solução de deixar as anotações no Controller e separar as chamadas, ela também é válida. No entanto, isso pode levar a uma maior duplicação de código, já que você terá que replicar a lógica de busca em cada método do Controller.

Espero ter ajudado e bons estudos!