Sobes.tech
Middle+

Hibernate'da N+1 muammosi nima va uni qanday hal qilish mumkin?

sobes.tech AI

AIdan javob

Hibernate'da N+1 muammosi ma'lumotlarni tanlashda yuzaga keladi, bunda ota ob'ektlarni olish uchun bitta so'rov bajariladi, so'ngra har bir bolalar ob'ekti (yoki bolalar kolleksiyasi) uchun alohida so'rov bajariladi. Bu N+1 so'rovlar bazaga yuborilishiga olib keladi, bu yerda N ota ob'ektlar soni, optimal bo'lgan bitta yoki bir nechta so'rovlar o'rniga.

N+1 muammosi bilan bog'liq scenariy misoli:

Author va Book sinflariga ega bo'lsak, muallifda ko'plab kitoblar bo'lishi mumkin.

// Author entitasi
@Entity
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY) // LAZY fetch odatda kolleksiyalar uchun
    private List<Book> books;

    // getter va setterlar
}

// Book entitasi
@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;

    @ManyToOne
    private Author author;

    // getter va setterlar
}

Agar barcha mualliflarni va ularning kitob nomlarini chiqarishni istasak:

Session session = sessionFactory.openSession();
List<Author> authors = session.createQuery("FROM Author", Author.class).list(); // 1 so'rov Author uchun

for (Author author : authors) {
    System.out.println("Author: " + author.getName());
    for (Book book : author.getBooks()) { // N so'rov Book uchun (har bir muallif uchun)
        System.out.println("- Book: " + book.getTitle());
    }
}
session.close();

Bu misolda:

  1. Bitta so'rov bilan barcha Author lar olinadi.
  2. So'ng, har bir Author uchun alohida so'rov bilan books kolleksiyasi yuklanadi. Agar 100 ta muallif bo'lsa, 100 ta qo'shimcha so'rov Book jadvaliga yuboriladi. Umumiy: 1 (mualliflar uchun) + 100 (kitoblar uchun) = 101 so'rov.

N+1 muammosini hal qilish uchun yechimlar:

  1. JPQL/HQL'da JOIN FETCH ishlatish: Bog'langan entitlarni bitta so'rovda yuklaydi.

    Session session = sessionFactory.openSession();
    List<Author> authors = session.createQuery("SELECT DISTINCT a FROM Author a JOIN FETCH a.books", Author.class).list(); // Author va ularning kitoblarini bitta so'rovda yuklaydi
    
    for (Author author : authors) {
        System.out.println("Author: " + author.getName());
        for (Book book : author.getBooks()) {
            System.out.println("- Book: " + book.getTitle());
        }
    }
    session.close();
    

    DISTINCT operatori natijada takrorlanishni oldini olish uchun ishlatiladi, bu join natijasida yuzaga kelishi mumkin.

  2. Fetch turini EAGER ga o'zgartirish: fetch = FetchType.LAZY (default) o'rniga fetch = FetchType.EAGER ga o'zgartirish.

    @Entity
    public class Author {
        // ... boshqa maydonlar
        @OneToMany(mappedBy = "author", fetch = FetchType.EAGER) // EAGER fetch
        private List<Book> books;
        // ... getter va setterlar
    }
    

    Tavsiya etilmaydi kolleksiyalar yoki katta bog'lanishlarga ega entitilar uchun, chunki bu ortiqcha ma'lumotlarni yuklash va ishlash muammolariga olib kelishi mumkin ("kartsiyano mahsul" effekt). ManyToOne/OneToOne bog'lanishlar uchun mos, chunki bog'langan ob'ekt doimo kerak bo'lishi kutiladi.

  3. Criteria API'da FetchMode ishlatish: Bog'langan entitlarni qanday yuklashni belgilash imkonini beradi.

    Session session = sessionFactory.openSession();
    Criteria criteria = session.createCriteria(Author.class)
        .setFetchMode("books", FetchMode.JOIN); // LEFT OUTER JOIN yordamida books yuklanadi
    
    List<Author> authors = criteria.list();
    
    for (Author author : authors) {
        System.out.println("Author: " + author.getName());
        for (Book book : author.getBooks()) {
            System.out.println("- Book: " + book.getTitle());
        }
    }
    session.close();
    
  4. BatchSize annotatsiyasidan foydalanish: Hibernate bog'langan ob'ektlarni (yoki kolleksiyalarni) belgilangan o'lchamdagi guruhlarda yuklash uchun ko'rsatadi, bu so'rovlar sonini kamaytiradi, garchi ularni bitta qilishmaydi.

    @Entity
    @BatchSize(size = 10) // Hibernate 10 ta muallifni partiyalarda yuklaydi
    public class Author {
        // ... boshqa maydonlar
        @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
        @BatchSize(size = 10) // Hibernate 10 ta muallif uchun books yuklaydi
        private List<Book> books;
        // ... getter va setterlar
    }
    

    Birinchi muallif uchun books kolleksiyasini o'rganuvchi paytda, Hibernate keyingi 9 muallif uchun booksni ham yuklaydi (agar ular shu sessiyada yuklangan bo'lsa). Bu N+1 ga nisbatan so'rovlar sonini sezilarli darajada kamaytiradi.

  5. Entity Graphs'dan foydalanish: Bog'langan ob'ektlar yoki kolleksiyalarni aniq belgilash imkonini beradi.

    @NamedEntityGraph(name = "author-with-books",
        attributeNodes = @NamedAttributeNode("books")
    )
    @Entity
    public class Author {
        // ... maydonlar va bog'lanishlar
    }
    
    Session session = sessionFactory.openSession();
    jakarta.persistence.EntityGraph<Author> entityGraph = session.createEntityGraph(Author.class);
    entityGraph.addAttributeNodes("books");
    
    List<Author> authors = session.createQuery("SELECT a FROM Author a", Author.class)
        .setHint("jakarta.persistence.fetchgraph", entityGraph) // yoki fetchgraph kerakli xulq uchun
        .getResultList();
    
    for (Author author : authors) {
        System.out.println("Author: " + author.getName());
        for (Book book : author.getBooks()) {
            System.out.println("- Book: " + book.getTitle());
        }
    }
    session.close();
    

Tanlangan yechim kontekst, bir-to-bir/one-to-many/many-to-many munosabatlar, ma'lumotlar hajmi va talab qilingan moslashuvchanlik darajasiga bog'liq. JOIN FETCH va Entity Graphs odatda barcha bog'langan ma'lumotlarni bitta so'rovda yuklash uchun afzal, BatchSize katta miqdordagi entitilar bilan ishlashda samarali va JOIN FETCH juda katta natijalarga olib keladigan holatlarda foydalidir. EAGER yuklash ehtiyotkorlik bilan ishlatilishi kerak.