Fale sobre o problema N+1 no Hibernate.
sobes.tech IA
Resposta da IA
O problema N+1 ocorre durante o carregamento ansioso de entidades relacionadas. Em vez de fazer uma única consulta para obter a entidade principal e as suas relações, o Hibernate executa uma consulta para a entidade principal e N consultas adicionais para cada uma das N entidades relacionadas, onde N é o número de entidades principais. Isto leva a uma desaceleração significativa da aplicação e ao aumento da carga na base de dados.
Exemplo:
// Entidade principal
@Entity
public class Order {
@OneToMany(fetch = FetchType.EAGER, mappedBy = "order")
private List<OrderItem> orderItems;
// ...
}
// Obter lista de pedidos
List<Order> orders = entityManager.createQuery("SELECT o FROM Order o").getResultList();
// Para cada pedido será executada uma consulta separada para obter OrderItem
for (Order order : orders) {
order.getOrderItems().size(); // Inicia N consultas adicionais à BD
}
Soluções para o problema:
-
Lazy Loading (carregamento preguiçoso): Uso de
FetchType.LAZY(por padrão para coleções eOneToMany/ManyToMany). As entidades relacionadas são carregadas apenas quando acessadas.@OneToMany(fetch = FetchType.LAZY, mappedBy = "order") private List<OrderItem> orderItems;Esta abordagem pode levar ao problema de "inicialização preguiçosa" (
LazyInitializationException) se tentar aceder aos dados carregados preguiçosamente fora de uma sessão Hibernate ativa. -
Join Fetch: Especificar explicitamente ao Hibernate para carregar entidades relacionadas numa única consulta usando
JOIN FETCH.List<Order> orders = entityManager.createQuery("SELECT o FROM Order o JOIN FETCH o.orderItems").getResultList();Os Join Fetch evitam o problema N+1, mas podem causar duplicados nos resultados se a entidade principal tiver muitos elementos relacionados.
-
Batch Fetching: Otimização do carregamento de entidades relacionadas em grupos (
BatchSize). O Hibernate carregará entidades relacionadas para várias entidades principais numa única consulta.@OneToMany(mappedBy = "order") @BatchSize(size = 10) // Carregar OrderItem para 10 pedidos de cada vez private List<OrderItem> orderItems; -
Subselect Fetching: Carregamento de entidades relacionadas numa consulta separada, usando os identificadores das entidades principais em cláusulas
IN.@OneToMany(mappedBy = "order") @Fetch(FetchMode.SUBSELECT) private List<OrderItem> orderItems;Pode ser menos eficiente para um grande número de entidades principais.
-
Entity Graphs: Mecanismo no JPA 2.1 para definir um grafo de entidades que deve ser carregado. Permite controlar explicitamente quais as ligações que devem ser carregadas.
// Definição do Entity Graph @NamedEntityGraph(name = "order-with-items", attributeNodes = @NamedAttributeNode("orderItems")) @Entity public class Order { ... } // Uso do Entity Graph na consulta Map<String, Object> hints = new HashMap<>(); hints.put("javax.persistence.fetchgraph", entityManager.getEntityGraph("order-with-items")); List<Order> orders = entityManager.createQuery("SELECT o FROM Order o").setHint("javax.persistence.fetchgraph", entityManager.getEntityGraph("order-with-items")).getResultList();