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, Author və Book 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ə:
- Bütün
Author-ları almaq üçün bir sorğu icra olunur. - Sonra hər bir
Authorüçünbookskolleksiyasını yükləmək üçün ayrıca bir sorğu icra olunur. Əgər 100 müəllif varsa, 100 əlavə sorğuBookcə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ı:
-
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();DISTINCToperatoru, birləşmədə yaranan təkrarlanan sətirlərin qarşısını almaq üçün istifadə olunur. -
FetchType-i EAGER-ə dəyişmək:
fetch = FetchType.LAZY(standartdır) yerinəfetch = FetchType.EAGERqoymaq.@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.
-
Criteria API-də
FetchModeistifadə 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(); -
BatchSizeannotasiyası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. -
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.