Sobes.tech
Middle+

Beszélj az N+1 problémáról a Hibernate-ben.

sobes.tech MI

Válasz az MI-től

N+1 probléma akkor fordul elő, amikor az eager betöltést kapcsolódó entitásokkal végezzük. Ahelyett, hogy egy lekérdezés történne a fő entités és kapcsolatai lekérésére, a Hibernate egy fő lekérdezést és N további lekérdezést hajt végre minden kapcsolódó entitásra, ahol N a fő entitások száma. Ez jelentős lassulást okozhat az alkalmazás működésében és növeli az adatbázis terhelését.

Példa:

// Fő entitás
@Entity
public class Order {

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "order")
    private List<OrderItem> orderItems;

    // ...
}
// Rendelések listájának lekérése
List<Order> orders = entityManager.createQuery("SELECT o FROM Order o").getResultList();

// Minden rendeléshez külön lekérdezés a OrderItem lekérésére
for (Order order : orders) {
    order.getOrderItems().size(); // N további lekérdezést indít az adatbázisban
}

Probléma megoldási lehetőségei:

  1. Lazy Loading (késleltetett betöltés): A FetchType.LAZY használata (alapértelmezett kollekciókra és OneToMany/ManyToMany). A kapcsolódó entitások csak akkor töltődnek be, amikor hozzáférünk hozzájuk.

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
    private List<OrderItem> orderItems;
    

    Ez a megközelítés problémákat okozhat a "lazy initialization" (LazyInitializationException) esetén, ha megpróbálunk hozzáférni a lazy betöltött adatokhoz egy aktív Hibernate sessionön kívül.

  2. Fetch Joins: Egyértelmű utasítás a Hibernate-nek, hogy egy lekérdezéssel töltse be a kapcsolódó entitásokat a JOIN FETCH használatával.

    List<Order> orders = entityManager.createQuery("SELECT o FROM Order o JOIN FETCH o.orderItems").getResultList();
    

    A Fetch Joins elkerülik az N+1 problémát, de duplikált sorokat eredményezhetnek a lekérdezés eredményében, ha a fő entitásnak sok kapcsolódó eleme van.

  3. Batch Fetching: A kapcsolódó entitások csoportos betöltése (BatchSize). A Hibernate több fő entitáshoz tartozó kapcsolódó entitásokat tölt be egy kérésben.

    @OneToMany(mappedBy = "order")
    @BatchSize(size = 10) // 10 rendeléshez tartozó OrderItem betöltése egyszerre
    private List<OrderItem> orderItems;
    
  4. Subselect Fetching: A kapcsolódó entitások külön kérésben töltődnek be, azonosítók alapján, IN klauzulákat használva.

    @OneToMany(mappedBy = "order")
    @Fetch(FetchMode.SUBSELECT)
    private List<OrderItem> orderItems;
    

    Ez kevésbé lehet hatékony nagy számú fő entitás esetén.

  5. Entity Graphs: A JPA 2.1 mechanizmusa, amely lehetővé teszi az entitás gráfjának meghatározását, amelyet be kell tölteni. Egyértelműen ellenőrzi, mely kapcsolatok töltődjenek be.

    // Entity Graph meghatározása
    @NamedEntityGraph(name = "order-with-items", attributeNodes = @NamedAttributeNode("orderItems"))
    @Entity
    public class Order { ... }
    
    // Entity Graph használata lekérdezéskor
    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();