Consultas dinâmicas e o desafio do criteriabuilder

Recentemente me deparei com um problema ao tentar criar filtros dinâmicos para uma das telas do sistema Crops System, esta se trata da tela de eventos, por ser uma tela com opções de filtros o back-end deveria ser algo dinâmicos onde recebo os filtros de forma não obrigatória e realiazo a consulta no banco de dados, estou ultilizando Spring Data Jpa e boa parte do básico de consultar é resolvido simplesmente ao ultizar o JpaRepository porem com as consultas ficando mais avançadas é dinâmicas é preciso ultilizar outras abordagens e entre essas testei algumas como @Query criando novos tipos dentro do repository porem se tornou muito verboso e com querys grandes para o caso tornando assim sua manutenção dificio, tambem encontrei muitos casos de uso com o Exaple junto ao criteriabuilder porem estes so serviriam até o ponto onde não se tinham relacionamento entre tabelas sendo preciso então criar joins, parti então para a montagem de consultas usando direto o criteriabuilder e apesar de conseguir algo não se tratava de um código ainda dinâmico, então construi uma classe para receber as opções atravéz da verificação de valors usando IF/ELSE ia ali montando de uma forma mais dinâmica a consulta, como precisava enviar ao front essa consulta paginada tive que criar uma varivavel para a paginação usando Pageable, mas com essa abordagem para obter os valores totais corretos da paginação eu precisava realizar duas consultas uma para os items e outra para o total sem limitar a query, nesse ponto estava feliz que finalmente estava ficando dinâmico mas não estava satisfeito, para uma consulta pequena até poderia passar mas para consultas maiores tornaria o processamento muito pesado e isso inviabilizou a abordagem neste momento para mim, pesquisando um pouco mais encontrei o JpaSpecificationExecutor, com este recurso é possivel fazer uma consulta já tendo o retorno paginado com os valores corretos e como a essa altura eu já tinha a classe que me trazia os valores dos filtros poderia então dentro deste método fazer as verificações do que veio do cliente para retornar a consulta expecifica, tudo certo então terminamos...
    

Como pode ver acima chegamos então a uma consulta dinâmica e com paginação porém ainda estava me incomodando ao olhar para código pois tinham muitos IFs aninhados no mesmo método para verificar os atributos do objeto de filtro, então depois de muita pesquisa e olhar para ele pensei em algo e que tornaria menos verboso o método e mais dinâmico para quem fosse ultilizar aquele código, eventualmente estendê lo e dar manutenção (Sempre pensem no próximo ao escrever seus códigos o próximo pode ser você amanhã ou eu 😂.). Bom a solução se tratou então de me aproveitar do próprio JpaSpecificationExecutor e dos valores que ele já trazia pra mim como o criteriabuilder, criteriaquery e o predicate, com esses valores e o objeto de filtros em mão dei início então a implementação de um método filtrar que saia pra fora do método principal do Repository e nele então faço a chamada a uma classe que nomeei como PredicateSearchBuilder nesta classe recebo o criteriabuilder como parâmetro do construtor e ultilizando uma nova instância de Preficate implementei alguns métodos dinâmicos no Padrão Builder neste builder então é onde implementei métodos dinâmicos e reaproveitaveis para outros métodos e até projetos ( Trasformei a solução em uma lib 😊 ), para que funcione e sejá facilmente estendito ela conta com métodos como equalIntegerRoot este método recebe como parametro o Root como já devem ter imaginado (Como ele apenas recebe um Root sem dizer de que é possível então reulizar em todo o projeto para outros filtros.), recebe tambem qual a coluna e valor da consulta  assim como um booleam, este booleam e usado para descidir se entrara ou não da criação dessa nova consulta dessa forma na chamada do método o programador pode simplesmente passar a condição que ele desejar ou até mesmo chamar algum tipo de validação que retorne um booleam, a ainda no builder a mesma estrutura citada a pouco mas para o Join e com outros tipos de filtros como datas e strings, agora caso surja alguma nova necessidade de filtro basta que o desenvolvedor implemente esse novo filtro na classe e pronto estara disponível em todo o projeto.
Muito obrigado por ler até aqui, se tiver alguma dica, duvida ou sugestão pode deixar nos comentários, a baixo anexarei algumas imagens de como ficou o código final.


Controller que recebe a requisição.

Service dos filtros.



Classe responsável por fazer os filtros usando Repository e a Biblioteca PredicateSearchBuilder.


💻 É isso, bora codar!💻

Comentários

Postagens mais visitadas