Query by Example - Filtros dinâmicos no Hibernate
Aprenda como criar filtros dinâmicos de maneira simples com a API de Criteria do Hibernate através da classe Example
Mais de 4 anos após o lançamento da JPA 2.0 e sua nova API de Criteria o mercado ainda hoje continua dividido entre sua API verbosa mas type-safe e a API antiga do Hibernate, que é simples de usar e possui maior adoção no mercado. Apesar dessa batalha entre estas duas APIs, sempre que surge a necessidade de criar consultas com filtros dinâmicos ou opcionais na aplicação não tem outra, precisamos trabalhar com uma API orientada a objetos em vez de concatenar JPQL dentro dos nossos DAOs.
Ambas nos permitem construir consultas de acordo com os filtros preenchidos pelo usuário do sistema, dessa forma evitamos concatenação de strings com JPQL. No código abaixo, podemos ver uma consulta com filtros dinâmicos dentro da classe BugDao
utilizando a Criteria do Hibernate:
public List<Bug> buscaBugsPor(String sumario, Status status) {
Criteria criteria = session.createCriteria(Bug.class);
if (sumario != null)
criteria.add(Restrictions.ilike("sumario", sumario));
if (status != null)
criteria.add(Restrictions.eq("status", status));
return criteria.list();
}
De acordo com a complexidade do relatório ou desejo do usuário, talvez seja necessário adicionar mais filtros a consulta, o que poderia ocasionar em mais argumentos no método buscaBugsPor
e, consequentemente, poderia levar a mudanças em dois ou mais pontos no código.
Por exemplo, se precisássemos adicionar um novo filtro para o atributo descricao
da entidade Bug
, nós acabaríamos mudando a assinatura do método da classe BugDao
para o código a seguir:
public List<Bug> buscaBugsPor(String sumario, Status status, String descricao) {
// ...
}
Uma maneira de contornar isso seria aproveitar a própria entidade que estamos buscando como argumento do método, pois é muito comum que os filtros de um relatório tenham uma relação 1 para 1 com os atributos da entidade.:
public List<Bug> buscaBugsPor(Bug bug) {
Criteria criteria = session.createCriteria(Bug.class);
if (bug.getSumario() != null)
criteria.add(Restrictions.ilike("sumario", bug.getSumario()));
if (bug.getStatus() != null)
criteria.add(Restrictions.eq("status", bug.getStatus()));
if (bug.getDescricao() != null)
criteria.add(Restrictions.ilike("descricao", bug.getDescricao()));
return criteria.list();
}
Com essa abordagem nós diminuímos a quantidade de modificação no código quando novos filtros são adicionados a consulta e estes casam com os atributos da nossa entidade.
Apesar das melhorias, ainda temos que nos preocupar com cada novo filtro da consulta, ou seja, temos que adicionar mais um if
no código. O ideal seria que o Hibernate entendesse quais atributos foram preenchidos na instância de Bug
passada para o método e soubesse aplicar os filtros na consulta.
Query by Example
Esse tipo de problema e necessidade é muito comum na maioria das aplicações corporativas, principalmente em funcionalidades de cadastros e relatórios. E foi pensando nisso que o Hibernate disponibilizou a classe Example dentro da API de Criteria, dessa forma podemos gerar um conjunto de critérios na consulta baseado na instância de uma entidade.
Usar a classe Example
é muito simples! Tomando como base o código anterior, bastaria removermos todos aqueles ifs
em favor do seguinte código:
import org.hibernate.criterion.Example;
public List<Bug> buscaBugsPor(Bug bug) {
// cria filtros com base nos valores de bug
Example example = Example.create(bug);
Criteria criteria = session.createCriteria(Bug.class);
criteria.add(example); // aplica filtros gerados
return criteria.list();
}
Quem fosse utilizar nosso método de busca de bugs, teria apenas que instanciar um Bug
e preencher seus atributos pelo qual deseja filtrar a consulta:
// filtra por sumario e status
Bug filtro = new Bug();
filtro.setSumario("falha no IEca");
filtro.setStatus(Status.ABERTO);
List<Bug> bugs = bugDao.buscaBugsPor(filtro);
Podemos ir mais longe e ajustar como a Example
é aplicada para cada atributo e/ou comparações da classe, dessa forma podemos criar um código mais genérico e reutilizável dentro do projeto:
Example example = Example.create(bug)
.excludeZeroes() // exclui atributos com valor igual a zero
.excludeProperty("descricao") // exclui atributo "descricao"
.ignoreCase() // usa case-insensitive para comparações de Strings
.enableLike(); // usa like para comparações de Strings
De forma similar, também podemos criar examples para associações da entidade:
Criteria criteria = session.createCriteria(Bug.class)
.add(Example.create(bug)) // filtros para entidade bug
.createCriteria("responsavel")
.add(Example.create(bug.getResponsavel())); // filtros para relacionamento
List<Bug> bugs = criteria.list();
É importante salientar que chaves primárias e associações são ignoradas. Além disso, por padrão, atributos com valor null
também são ignorados.
Não é algo novo
Embora queries by example seja um recurso antigo no Hibernate, ele ainda é pouco utilizado e até desconhecido por muitos desenvolvedores. Seu uso pode simplificar o desenvolvimento e melhorar a produtividade em funcionalidades rotineiras, como cadastros básicos (CRUDs) e relatórios.
Infelizmente, mesmo a especificação mais recente da JPA ainda não suporta o uso de query by example. Mas assim como o Hibernate, os demais provedores da JPA tem suporte a ele, como EclipseLink e OpenJPA.
A API de Criteria é um dos recursos mais poderosos quando trabalhamos com um framework ORM, por esse motivo no nosso curso de Persistência com JPA 2 e Hibernate nós estudamos a fundo a Criteria do Hibernate, inclusive o uso de examples.
E você, já conhecia a classe Example
do Hibernate? Consegue ver alguma utilidade dela nas suas classes de DAO genérico?
Desenvolvedor e instrutor na TriadWorks