Estratégias de busca (fetch) com Hibernate

Veja neste artigo como funcionam as Estratégias de Busca (Fetch Strategies) do Hibernate para melhorar o desempenho da sua aplicação e utilizá-las no momento adequado.

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:

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.

Artigos relacionados