Sobes.tech
Middle+

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:

  1. Lazy Loading (carregamento preguiçoso): Uso de FetchType.LAZY (por padrão para coleções e OneToMany/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.

  2. 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.

  3. 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;
    
  4. 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.

  5. 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();