Sobes.tech
Middle+

Hibernate-də N+1 problemi nədir və necə həll edilə bilər?

sobes.tech Süni İntellekt

AI-dan cavab

Hibernate-də N+1 problemi, məlumatların seçilməsi zamanı yaranır, burada valideyn obyektlərini almaq üçün bir sorğu icra edilir və sonra hər bir uşaq obyekti (və ya uşaq kolleksiyası) üçün ayrıca bir sorğu icra olunur. Bu, verilənlər bazasına N+1 sorğusu ilə nəticələnir, burada N valideyn obyektlərinin sayıdı, və ya optimal bir sorğu və ya bir neçə birləşmiş sorğu ilə əvəz olunur.

N+1 problemi ilə nümunə ssenari:

Tutaq ki, AuthorBook sinifləri var, burada müəllifin çox sayda kitabı ola bilər.

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

    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY) // LAZY fetch kolleksiyalar üçün standartdır
    private List<Book> books;

    // getters və setters
}

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

    @ManyToOne
    private Author author;

    // getters və setters
}

Əgər bütün müəllifləri və onların kitablarının adlarını göstərmək istəyiriksə:

Session session = sessionFactory.openSession();
List<Author> authors = session.createQuery("FROM Author", Author.class).list(); // 1 sorğu Author üçün

for (Author author : authors) {
    System.out.println("Author: " + author.getName());
    for (Book book : author.getBooks()) { // N sorğusu Book üçün (hər müəllif üçün bir)
        System.out.println("- Book: " + book.getTitle());
    }
}
session.close();

Bu nümunədə:

  1. Bütün Author-ları almaq üçün bir sorğu icra olunur.
  2. Sonra hər bir Author üçün books kolleksiyasını yükləmək üçün ayrıca bir sorğu icra olunur. Əgər 100 müəllif varsa, 100 əlavə sorğu Book cədvəlinə göndəriləcək. Ümumilikdə 1 (müəlliflər üçün) + 100 (kitablar üçün) = 101 sorğu.

N+1 probleminin həll yolları:

  1. JOIN FETCH istifadə edərək JPQL/HQL-də: Bağlı olan obyektləri bir sorğu ilə yükləyir.

    Session session = sessionFactory.openSession();
    List<Author> authors = session.createQuery("SELECT DISTINCT a FROM Author a JOIN FETCH a.books", Author.class).list(); // Author və onların Kitablarını bir sorğu ilə yükləyir
    
    for (Author author : authors) {
        System.out.println("Author: " + author.getName());
        for (Book book : author.getBooks()) {
            System.out.println("- Book: " + book.getTitle());
        }
    }
    session.close();
    

    DISTINCT operatoru, birləşmədə yaranan təkrarlanan sətirlərin qarşısını almaq üçün istifadə olunur.

  2. FetchType-i EAGER-ə dəyişmək: fetch = FetchType.LAZY (standartdır) yerinə fetch = FetchType.EAGER qoymaq.

    @Entity
    public class Author {
        // ... digər sahələr
        @OneToMany(mappedBy = "author", fetch = FetchType.EAGER) // EAGER fetch
        private List<Book> books;
        // ... getters və setters
    }
    

    Tövsiyə edilmir kolleksiyalar və ya çox əlaqəli obyektlər üçün, çünki artıq məlumatların yüklənməsinə və performans problemlərinə səbəb ola bilər.

  3. Criteria API-də FetchMode istifadə etmək: Bağlı obyektlərin necə yüklənəcəyini göstərir.

    Session session = sessionFactory.openSession();
    Criteria criteria = session.createCriteria(Author.class)
        .setFetchMode("books", FetchMode.JOIN); // LEFT OUTER JOIN istifadə edir
    
    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 annotasiyasından istifadə: Hibernate-ə bağlı obyektləri (və ya kolleksiyaları) müəyyən ölçüdə qruplar şəklində yükləməyə göstəriş verir.

    @Entity
    @BatchSize(size = 10) // Hibernate 10-lik partiyalarla yükləyəcək
    public class Author {
        // ... digər sahələr
        @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
        @BatchSize(size = 10) // Hibernate 10-lik partiyalarla kitabları yükləyəcək
        private List<Book> books;
        // ... getters və setters
    }
    

    Bu, kolleksiya books-a ilk dəfə baxanda, Hibernate digər 9 müəllif üçün də books-u yükləyəcək. Bu, N+1 sayını əhəmiyyətli dərəcədə azaldır.

  5. Entity Graphs istifadə etmək: Bağlı obyektləri və ya kolleksiyaları yükləmək üçün açıq şəkildə göstərə bilər.

    @NamedEntityGraph(name = "author-with-books",
        attributeNodes = @NamedAttributeNode("books")
    )
    @Entity
    public class Author {
        // ... sahələr və əlaqələr
    }
    
    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) // və ya fetchgraph, istənilən davranış üçün
        .getResultList();
    
    for (Author author : authors) {
        System.out.println("Author: " + author.getName());
        for (Book book : author.getBooks()) {
            System.out.println("- Book: " + book.getTitle());
        }
    }
    session.close();
    

Hansı həll yolunun seçilməsi kontekstdən, əlaqə növündən (bir-bir, bir-çox, çox-çox), məlumat həcminə və tələb olunan elastikliyə bağlıdır. JOIN FETCH və Entity Graphs adətən bütün əlaqəli məlumatları tək sorğu ilə yükləmək üçün üstünlük təşkil edir, BatchSize isə çox sayda obyektlə işləyərkən və JOIN FETCH çox böyük nəticələrə səbəb olarsa faydalıdır. EAGER yükləmə ehtiyatla istifadə olunmalıdır.