Consultas usando JPA com Performance

July 31, 2017

Olá leitores. Demorou um tempinho, mas finalmente saiu o artigo!

Neste artigo irei compartilhar algumas dicas que irão ajudar sua aplicação a ter uma performance mais rápida usando JPA, e o Hibernate como provedor da especificação. JPA pode causar bastante dor de cabeça se não for usado da forma correta desde o início do projeto. Em algumas empresas, este framework conseguiu uma reputação tão ruim ao ponto de que muitas operações acabam tendo que ser feita utilizando JDBC puro.

Para garantir que consultas com JPA irão ter boa performance, basicamente precisamos ter os seguintes pontos em mente:

  • Carregamento Eager e Lazy;
  • Paginação de dados;
  • Considere não utilizar 'Select * FROM';
  • Não consulte dentro de um loop;
  • Não tenha medo de usar SQL Nativo.

Carregamento Eager e Lazy

Carregamento eager acontece para atributos anotados com @ManyToOne por padrão, e quando explicitamente declarados com FetchType.EAGER. O que isto significa é que toda vez que uma entidade é recuperada do banco, outro SELECT será disparado para cada atributo eager.

Por este motivo, eu sempre utilizo as anotações @OneToMany and @ManyToOne com FetchType.LAZY. Quando eu preciso carregar o atributo lazy utilizo a palavra-chave "fetch", e em alguns casos, uma Query à parte pode ser disparada também. Por exemplo:

SELECT per.name FROM Person per
JOIN FETCH per.addressList adr
    
Paginação de dados

Em 99% das situações em que você precisa carregar dados de uma tabela com muitos registros, você muito provavelmente não precisa recuperar tudo. Dito isso, a não ser que você esteja consultando uma tabela pequena que tem certeza que não haverá crescimento exponencial, pagine sua query utilizando a seguinte sintaxe sempre que possível:

Query query = entityManager.createQuery("SELECT per from Person per");
int pageNumber = 1;
int pageSize = 10;
query.setFirstResult((pageNumber - 1) * pageSize);
query.setMaxResults(pageSize);
List<Person> personList = query.getResultList();
    
Considere não utilizar 'Select * FROM'

Se você está consultando uma tabela que possui mais de 30 atributos, esta sintaxe pode causar lentidão no banco. Principalmente se algum destes atributos forem do tipo BLOB ou CLOB. Já vi uma situação em que a página que carregava um atributo BLOB estava demorando 30 segundos para carregar, e foi para menos de um segundo após a query ser alterada para carregar atributos específicos.

Nestes casos é preferível que você crie um VO (Value Object) que irá conter os atributos específicos da tabela necessários para sua funcionalidade.

Não consulte dentro de um loop

Geralmente é melhor carregar todos os dados em um comando só, utilizando JOIN FETCH por exemplo. Porém, isso nem sempre é possível. Mas independente do que você fizer, não consulte dentro de um loop.

Tente ao máximo criar a query que irá recuperar todos os dados (podendo ser paginada se a mesma retornar muitos registros). Só tome cuidado na utilização do operador IN para recuperar dependências. O comando IN costuma causar lentidão no banco se utilizado com mais de muitos registros (100+ por exemplo).

Existem algumas técnicas que são mais rápidas do que utilizar IN, como:

  • Criar uma tabela temporária com os parâmetros do IN;
  • Utilizar uma XMLTable para passar os valores que seriam passados no IN (se o banco possuir suporte para isso).

Não tenha medo de usar SQL Nativo

O banco de dados pode otimizar muito o seu processo. Então utilize-o a seu favor. Se o processo pode ser otimizado para recuperar/inserir todos os dados em um ou mais comandos SQLs, que seriam mais lentos se executados de individualmente no código Java, não tenha medo de usar SQL Nativo.

Por exemplo, um processo feito no Java dessa forma:

  • Iterar os inputs passados pelo usuário;
  • Para cada item, atualizar mais de 10000 registros e inserir os dados;

Poderia ficar bem mais rápido e otimizado se feito desta forma:

  • Criar uma trigger em uma tabela que será responsável por atualizar os 10000 registros;
  • Criar uma tabela temporária e inserir todos os dados nela;
  • Executar um comando que efetua um 'INSERT SELECT' da tabela temporária para a definitiva;
  • Como somente um comando foi disparado, a trigger será ativada apenas uma vez, fazendo com que a operação fique muito mais rápida;

Conclusão

Conforme descrito neste artigo, JPA pode ser usado sem que hajam problemas de performance, se o uso correto do mesmo for reforçado. Eu espero que a informação apresentada aqui tenha sido útil. Até a próxima!