Melhorando a Performance de suas buscas com paginação
Este post acompanha o video que gravei recentemente sobre o assunto: https://www.youtube.com/watch?v=-AgDQEQhOfw
Quem nunca se deparou com uma aplicação que ficou lenta ou até mesmo travou durante uma busca?
Pois é, uma das razões para esse problema pode ser a falta de implementação de paginação.
Vamos demonstrar como a paginação pode nos ajudar a obter máximo desempenho.
Entendendo o Funcionamento
Imaginem uma situação em que uma busca retorna uma grande quantidade de resultados, como centenas, milhares ou até milhões de registros. Processar todos esses registros de uma vez pode sobrecarregar a aplicação, tornando-a lenta ou até mesmo causando crashes e bugs.
Uma solução para esse problema é quebrar os resultados da busca em partes, ou seja, em páginas.
Por exemplo, digamos que temos um banco de dados com a tabela item
a seguir:
create table item (
id integer not null,
nome varchar(255)
)
Se tivermos cinco itens, podemos organizá-los em duas páginas, com três elementos na primeira e dois na segunda. Neste exemplo, estamos trabalhando com apenas cinco items, mas o mesmo conceito é aplicado quando temos muito mais registros em nosso banco de dados, poderiamos ter 10.000 items na tabela com 100 items em cada pagina . Isso torna o processamento mais eficiente e evita sobrecargas.
+--------+
pagina_0 | item_0 |
| item_1 |
| item_2 |
+--------+
+--------+
pagina_1 | item_3 |
| item_4 |
+--------+
Java JPA Paginação
Para ilustrar, consideraremos o nosso exemplo com cinco itens. Criaremos uma classe de teste que vai salvar cinco itens no repositório.
// Poderia ser um for loop, mas estou usando o IntStream pelo 'cool factor'
IntStream.rangeClosed(0, 4).forEach(i -> {
var item = new Item();
item.setNome("item_" + i);
itemRepository.save(item);
});
assertEquals(5, itemRepository.count());
A implementação da paginação propriamente dita começa com a utilização do método findAll
do repositório, que recebe um objeto Pageable
como argumento.
Para isso, criamos um PageRequest
com um tamanho de página de três elementos.
Isso representa a primeira página, que capturamos em uma variável chamada pagina_0
.
Verificamos se a pagina_0
contém apenas três elementos e se os elementos são mesmo os itens item_0
, item_1
, item_2
, garantindo assim o sucesso da busca inicial.
var pagina_0 = itemRepository.findAll(PageRequest.ofSize(3));
assertEquals(3, pagina_0.getNumberOfElements());
assertEquals("item_0", pagina_0.getContent().get(0).getNome());
assertEquals("item_1", pagina_0.getContent().get(1).getNome());
assertEquals("item_2", pagina_0.getContent().get(2).getNome());
Acessando Páginas Subsequentes
Quando precisamos acessar as páginas seguintes, utilizamos o método findAll
novamente, desta vez especificando o número da página desejada.
Neste caso, podemos construir a página utilizando o método PageRequest.of(1, 3)
ou pagina_0.nextPageable()
.
Verificamos se a pagina_1
contém apenas três elementos e se os elementos são mesmo os itens item_3
, item_4
.
var pagina_1 = itemRepository.findAll(pagina_0.nextPageable());
assertEquals(2, pagina_1.getNumberOfElements());
assertEquals("item_3", pagina_1.getContent().get(0).getNome());
assertEquals("item_4", pagina_1.getContent().get(1).getNome());
SQL Gerado
O JPA gera dois SQL quando faz a paginação
select
count(*)
from
item i1_0
select
i1_0.id,
i1_0.descricao,
i1_0.nome,
i1_0.qr_code_id
from
item i1_0 offset ? rows fetch first ? rows only
Considerações Finais
Vale lembrar que é importante ordenar os resultados quando utilizando paginação, mas este é um assunto que quero explorar em um post futuro.
Também quero mencionar que este é apenas um método de paginação, é um método genérico e fácil de implementar, existem outros métodos mais performáticos que também quero discutir em posts futuros.
Se tiver algum feedback, basta deixar nos comentários.