O Hibernate é um poderoso container que auxilia nas tarefas que envolvem o banco de dados, fazendo com que o desenvolvedor trabalhe diretamente com objetos e deixe que o Hibernate se encarregue de fazer a “tradução” de objetos para tabelas (relacional).

Este artigo aplica-se a um dos milhares de conceitos envolvidos neste poderoso container, a estratégia de busca, principalmente utilizando a linguagem HQL (Hibernate Query Language).

Quando uma pesquisa é feita no Hibernate, ele utiliza umas das estratégias de busca que serão mostradas abaixo, e o uso adequado destas pode trazer uma melhora significativa no desempenho da sua aplicação.

Estratégias de Busca (Fetching Strategies)

São 4 as estratégias utilizadas para busca de dados com o Hibernate:

  • Join
  • Select
  • Subselect
  • Batch-size

Estratégia SELECT

Se você tem uma classe Venda e uma classe ItemVenda, onde a relação de Venda para ItemVenda é um @OneToMany, provavelmente você terá uma coleção de ItemVenda dentro de Venda. Utilizando esta estratégia, quando você carregar uma Venda os itens desta venda não serão carregados até que você explicitamente os chame, ou seja, enquanto você não precisa destes itens eles não serão carregados.

Listagem 1: Código principal


//chama o select da venda
Venda venda = (Venda)session.get(Venda.class, 114); 
Set sets = venda.getAllItens();
 
//chama o select do ItemVenda
for ( Iterator iter = sets.iterator();iter.hasNext(); ) { 
      ItemVenda item = (ItemVenda) iter.next();
      System.out.println(item.getNome());
      System.out.println(item.getValor());

Veja na saída abaixo que o Hibernate só constrói o SQL do ItemVenda quando você o chama, ou seja, no laço “for”.

Listagem 2: Saída do Fetch SELECT


Hibernate: 
    select ...from venda
    where venda0_.VENDA_ID=?
 
Hibernate: 
    select ...from itemvenda
    where itemvenda0_.VENDA_ID=?

Estratégia JOIN

Ao contrário da SELECT, a JOIN desabilita o Lazy Loading, isto significa que ao carregar o objeto Venda, automaticamente todos seus itens são carregados, mesmo que você nunca precise destes.

Ainda seguindo o código da Listagem 1, você poderá ver na Listagem 3 o que muda do FETCH SELECT para FETCH JOIN.

Listagem 3: Saída do Fetch JOIN


Hibernate: 
    select ...
    from
        venda venda0_ 
    left outer join
        itemvenda itemvenda1_ 
            on venda0_.VENDA_ID=itemvenda1_.VENDA_ID 
    where
        venda0_.VENDA_ID=?

Estratégia SUBSELECT

Para esta estratégia utilizaremos um outro código principal para melhor ilustrá-lo. O que ocorre aqui é a criação de uma “sub-query” para captura das collections. Veja o código principal abaixo.

Listagem 4: Código Principal para Subselect


List<Venda> list = session.createQuery("from Venda").list();
 
for(Venda venda : list){
 
    Set sets = venda.getAllItens();
 
    for ( Iterator iter = sets.iterator();iter.hasNext(); ) { 
            ItemVenda item = (ItemVenda) iter.next();
            System.out.println(item.getNome());
            System.out.println(item.getValor());
    }
}

Aplicando a estratégia de SUBSELECT a saída será:

Listagem 5: Saída do SUBSELECT


Hibernate: 
    select ...
    from venda venda0_
 
Hibernate: 
    select ...
    from
        itemvenda itemvenda0_ 
    where
        itemvenda0_.VENDA_ID in (
            select
                venda0_.VENDA_ID 
            from
                venda venda0_
        )

Com o subselect serão criados 2 SELECTs, sendo o primeiro para retornar apenas as Vendas e o segundo para retornar os Itens desta venda.

Estratégia Batch Size

Vamos explicar essa estratégia com exemplos, mas antes de iniciarmos tenha em mente o seguinte conceito: “A Estratégia de Batch Size não define quantos registros são carregados dentro das collections. Ao invés disso, ela define quantas collections deverão ser carregadas”.

Veja o exemplo de código abaixo que utilizaremos.

Listagem 6: Código para Exemplo Batch Size


List<Venda> list = session.createQuery("from Venda").list();
 
for(Venda venda : list){
 	Set sets = venda.getAllItens();
 
    for ( Iterator iter = sets.iterator();iter.hasNext(); ) { 
            ItemVenda item = (ItemVenda) iter.next();
            System.out.println(item.getNome());
            System.out.println(item.getValor());
    }
}

Sem utilizar o Batch Size a saída do Hibernate para o código acima, será:

Listagem 7: Saída sem Batch Size


Hibernate: 
    select ...
    from venda venda0_
 
Hibernate: 
    select ...
    from itemvenda itemvenda0_ 
    where itemvenda0_.VENDA_ID=?
 
Hibernate: 
    select ...
    from itemvenda itemvenda0_ 
    where itemvenda0_.VENDA_ID=?

Dependendo de quantas Vendas estiverem no banco, ele ficará criando SELECTs e mais SELECTs. Para resolver isso usamos o Batch Size, que transformará a saída acima na saída abaixo (utilizando um Batch Size de tamanho 10).

Listagem 8: Saída com Batch Size = 10


Hibernate: 
    select ...
    from venda venda0_
 
Hibernate: 
    select ...
    from itemvenda itemvenda0_ 
    where
        itemvenda0_.VENDA_ID in (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )

O que o Hibernate faz é juntar todos aqueles SELECTS em uma clausula “IN”, o que torna a consulta milhões de vezes mais ágil.

Conclusão

É fato que o entendimento de cada uma dessas estratégias pode fazer diferença na hora de colocar sua aplicação em produção, isso porque a não utilização adequada do mesmo pode trazer diversos problemas: lentidão, estouro de pilha (stack overflow) e principalmente a constante reclamação do usuário.

Uma aplicação não é composta apenas de código e retornos, pelo contrário, este é apenas o baseline desta, há necessidades constantes de análise, projetos, testes e muitos outros recursos que completam a mesma. Se você ainda trabalha com aplicações pequenas com uma ínfima quantidade de dados, poderá não sentir diferença na aplicação das estratégias de busca, mas é ideal e quase que obrigatório o seu aprendizado desde o inicio para ao trabalhar com grandes aplicação não ter surpresas.