Criando consultas complexas no Hibernate de maneira dinâmica

Neste post pretendo expor um padrão bastante simples a ser aplicado na criação de consultas usando a API Criteria do Hibernate. A API Criteria consiste em uma ferramenta extremamente poderosa que o Hibernate nos oferece para solucionar um problema muito comum no desenvolvimento de aplicações que precisem fazer consultas a banco de dados: a maldita concatenação de strings no momento de criação de nossas consultas.

Em nosso exemplo, utilizaremos a classe Pessoa, que encontra-se descrita no código abaixo:


class Pessoa {
private String nome;
private String sobrenome;
private String endereco;
private String observacoes;
// gets e sets omitidos
}

Para buscar valores já persistidos da classe Pessoa em um banco de dados, poderíamos criar uma nova classe chamada FactoryPessoa, responsável por encapsular as consults ao banco de dados. Suponhamos que esta nossa classe possua a implementação abaixo:

import org.hibernate.*;
import org.hibernate.criterion.*;
import java.util.*;

class FactoryPessoa extends FactoryBase {

/*
Suponha que exista uma classe chamada FactoryBase, que já possua métodos básicos para
a instanciação de objetos Session
*/

public List<Pessoa> getPessoaPorNome(String valor)
{
Session sessao = null;
try
{
sessao = getSession();
Criteria busca = sessao.createCriteria(Pessoa.class);
busca.add(Expression.ilike("nome", MatchMode.ANYWHERE, valor);
return busca.list();
}
catch (Exception ex) // Exception ex apenas para fins didáticos. Não tentem isto em casa
{
return null; // não tente isto em casa também
}
}

public List<String> getPessoaPorSobreNome(String valor)
{
try {
Session sessao = getSession();
Criteria busca = sessao.createCriteria(Pessoa.class);
busca.add(Expression.ilike("sobrenome", MatchMode.ANYWHERE, valor);
return busca.list();
} catch (Exception ex) {return null;}
}

}

Um factory simples com base em dois critérios de busca simples. Mas e se quisessemos buscar por nome e sobrenome ao mesmo tempo? Uma solução poderia ser criar mais um método. E depois, se quisessemos buscar por nome, sobrenome e alguma observação? Outro método, claro! E no final de pouco tempo teríamos uma classe com inúmeros métodos, um para cada tipo de busca que viesse à nossa mente. Hmm… cheira a merda, não acha?

Uma solução para este problema consiste em um padrão que criei e venho aplicando a já algum tempo. Consiste em primeiro criar uma classe que represente uma consulta genérica, que chamo de CriterioBusca, cuja implementação pode ser similar a abaixo:

class CriterioBusca {

           private int tipoBusca;
           private Object valorBusca;     
    
// gets e sets implícitos

}

O atributo tipoBusca identifica o tipo de busca que queremos fazer. Na classe responsável por fazer consultas ao banco de dados, deveremos criar uma série de constantes estáticas que definam os tipos de pesquisa que queremos fazer ao banco de dados. Sendo assim, poderíamos modificar nossa classe FactoryPessoa para se tornar semelhante à descrita abaixo:

class FactoryPessoa {
           // Constante que identifica uma busca por nome
           public static final int BUSCA_NOME = 1;
          // Constante que identifica uma busca por sobrenome
           public static final int BUSCA_SOBRENOME = 2;
          // Constante que identifica uma busca por observações
           public static final int BUSCA_OBSERVACOES = 3;
      // e quantas outras constantes forem necessárias
}

Já o atributo valor identifica o valor que queremos utilizar em nossa pesquisa. Opta-se por Object ao invés de String ou qualquer outro tipo para que tenhamos a busca mais flexível possível ao final de nosso projeto.

Em seguida, nossa classe FactoryUsuario pode também ter sua interface externa simplificada ao extremo. Por que ter 234987234 métodos de busca quando podemos ter apenas um?

Sendo assim, podemos finalizar a implementação de FactoryPessoa do seguinte modo:

class FactoryPessoa extends FactoryBase {
     (...)
    // as constantes encontram-se definidas acima
    public List<Pessoa> buscarPessoas(List<CriterioBusca> criterios)
    {
            try
              {
                  Session sessao = getSession();
                  Criteria busca = sessao.createCriteria(Pessoa.class);
                  for (CriterioBusca criterio : criterios)
                      {
                           prepararBusca(criterio, busca);
                      }
                  return busca.list();
               } catch (Exception ex) {return null;}
    }


  private void prepararBusca(CriterioBusca criterio, Criteria busca)
   {
         switch (criterio.getTipoBusca())
          {
               case BUSCA_NOME:
                     busca.add(Expression.ilike("nome", MatchMode.ANYWHERE, busca.getValor().toString());
                break;
                case BUSCA_SOBRENOME:
                      busca.add(Expression.ilike("sobrenome", MatchMode.ANYWHERE, busca.getValor().toString());
                break;
                // quantos outros casos de acordo com o número de constantes definidas na classe
           }
    }
}

O código acima foi super simplificado, no entanto já é possível ter uma noção básica da idéia por trás do padrão. Ao invés de termos uma série de métodos, um para cada tipo diferente de busca, teremos apenas um único método: e este fará qualquer tipo de combinação de busca que quisermos, basta passar o número de instancias de CriterioBusca necessárias.

No entanto, nem tudo são flores neste padrão. Há seus problemas: o maior que vejo consiste na implementação do método responsável por popular sua instância de Criteria. Quanto maior for o número de tipos de busca definidos na classe, maior será a sua complexidade. Outro problema consiste no fato de não ficar óbvio àqueles que estão tendo seu primeiro contato com o código como trabalhar com o mesmo. Afinal de contas, não há mais um método como buscarPessoasPorNome, que torna nítidida a sua utilização.

Com base no exemplo exposto neste artigo, podemos pensar em mais um refinamento em nosso padrão. E se quisermos trabalhar com operadores como maior, menor, igual ou diferente? Simples: adicionamos mais um atributo à nossa classe CriterioBusca chamada operador. O resto, deixo por conta de vocês como exercício. :)

3 thoughts on “Criando consultas complexas no Hibernate de maneira dinâmica

Leave a Reply

Your email address will not be published. Required fields are marked *