Bugün layihəmdə qarşılaşdığım problem: LazyInitializationException
Problem nədən qaynaqlandı?
Səbəb çox aydın idi. Belə bir problemin yaşanması üçün ən azı 2 entity və onlar arasında ən azı 1 relation olması, həmin relation-un da FetchType.LAZY olması lazım idi.
Günahkarları tapdım)
Sonra düşündüm ki, bunu necə həll edə bilərəm və araşdırmağa başladım.
Ağlıma ilk 2 üsul gəldi:
FetchType = EAGER@Transactional
1. FetchType = EAGER
FetchType.EAGER etsəm, relation hər dəfə lazım olmasa belə yüklənə bilərdi.
Layihə böyüdükcə bunun əlavə yüklənmə yaratması ehtimalı var idi. Ona görə bunu istəmədim.
2. @Transactional
İkinci üsul — @Transactional — ilk başda mənə ən məntiqli gəldi.
Bir az da tənbəllik var idi, ən rahat yol bu idi,mən də tətbiq etdim
Amma sonra başqa problem çıxdı.
Metodumda findAll() nəticəsində entity-lər gəlirdi. Daha sonra ExpenseMapper daxilində relation-a müraciət etdikcə hər entity üçün yenidən bazaya sorğu gedirdi.
Hər yerdə buna N+1 problem deyirlər.
1 əsas sorğu + N əlavə sorğu.
Nəticədə @Transactional LazyInitializationException problemini həll etsə də, database-ə lazımsız sayda sorğu göndərilməsinə səbəb oldu.
3. JPQL + JOIN FETCH
Bu üsul da pis deyil. Relation-u lazım olan query ilə birlikdə gətirmək mümkündür.
1 relation olduğuna görə də böyük yüklənməyə səbəb olmayacaqdı.Lakin JPQL-lərin sayı artsa,həm yavaşlanmaya səbəb olacaqdı,həm də oxunaqlılıq itəcəkdi.
Bir tərəfdən problemi həll edəcəkdi, digər tərəfdən mən query-yə baxıb:
“Mən burada nə yazmışam?”
deyəcəkdim.
4. EntityGraph
Sonda EntityGraph variantını seçdim.
Səliqəli, oxunaqlıdır və hansı relation-u konkret query-də yükləmək istədiyimi göstərə bilirəm.
Ən əsası da FetchType.LAZY-ni ümumi olaraq dəyişmədən, yalnız ehtiyacım olan yerdə relation-u yükləyə bilirəm.
Mənim konkret vəziyyətim üçün ən uyğun variant bu oldu.
@EntityGraph(attributePaths = {"expenseType"})
Page<ExpenseEntity> findAll(
Specification<ExpenseEntity> spec,
Pageable pageable
);
5. DTO
Bir də DTO ilə bağlı yanaşma var idi.
İşləyirsə, çox da qurdalamaq lazım deyil deyib, tənbəllik etdim və araşdırıb tətbiq etmədim.
Bəlkə növbəti dəfə.
Bugünkü nəticə
Bir problemi həll edəndə təkcə “işləyir?” sualını yox, “bunu həll edəndə başqa problem yaratdım?” sualını da vermək lazımdır.
Bu gün də LazyInitializationException mənə bunu öyrətdi.
Bəs AI bu prosesdə necə kömək etdi?
Son olaraq bir şeyi də qeyd edim.
Bu variantları araşdırarkən AI-dan da istifadə etdim. Maraqlısı odur ki, AI mənə sırasıyla:
FetchType.EAGER@TransactionalJPQL + JOIN FETCH
variantlarını təklif etdi.
Amma EntityGraph ümumiyyətlə təklif olunmadı.
Göründüyü kimi, verilən ilk cavabı birbaşa götürüb tətbiq etmək həmişə ən yaxşı yol deyil.
Mənim üçün əsas məsələ də elə bu oldu: təklif olunan hər üsulun nə etdiyini, hansı problemin yarana biləcəyini başa düşmək və sonra öz layihəmə uyğun qərar vermək.
Sonda EntityGraph-ı özüm tapdım və konkret vəziyyətim üçün daha uyğun olduğunu düşündüm.
Yəni AI çox kömək edir, amma “AI belə dedi, deməli belədir” yanaşması ilə işləmək də düzgün deyil.
Kodun məsuliyyəti yenə mənim üzərimdədir. :)
Top comments (0)