JPA Projection ve DTO — Üç Alan İçin Ne Kadar Veri?
Önce şunu oku: JPA N+1 Problemi , Persistence Context — Hibernate'in Hafızası
30 saniyede özet
Sadece bir listede isim ve fiyat göstereceksen bütün nesneyi yüklemene gerek yok. Projection ya da record DTO yalnızca gereken sütunları getirir. Ama @Value ile yazılan açık projection nesnenin tamamını yine yükler.
Bir müşterinin telefon numarasını öğrenmek için arşivden bütün dosyasını getirttiğini düşün. Numara ilk sayfada yazıyor.
Adım adım oku
- Liste ekranı yalnızca isim ve fiyat gösteriyor, ama entity yüklemek bütün sütunları getirir.
- Uzun notlar metni de taşınır; Hibernate ayrıca her nesnenin bir karşılaştırma kopyasını saklar.
- Projection ile sorgu yalnızca isim ve fiyat sütunlarını ister.
- Daha az sütun taşınır, kopya saklanmaz, bellek ve ağ rahatlar.
-
Bayt: Sipariş listesi yavaşlayınca entity yerine bir projection yazdık.
-
Sen: Hızlandı mı?
-
Bayt: Hayır! Sorgu sayısı da taşınan veri de aynı kaldı.
-
Bayt: Projection yazmak her zaman yalnızca gerekeni mi getirir? Önce bir tahmin yapalım.
Sipariş listesi yavaşlayınca ekip entity yerine bir “projection” yazdı. Beklenen hızlanma gelmedi: sorgu sayısı aynı kaldı.
Bütün dosyayı taşımak
Bir entity yüklemek bütün sütunlarını getirmek demektir, büyük bir notes metni dahil. Entity persistence context’e girer ve Hibernate onun bir fotoğrafını dirty checking için saklar.
LAZY bir ilişkiye dokunmak her satırda bir sorgu daha üretir. Fetch join bunu tek sorguya indirir, ama sütunları ve izlenen nesneleri azaltmaz.
Kafam karıştı, daha basit anlat
Bir müşterinin yalnızca adını soracaksan bütün dosyasını getirme. Entity yüklemek dosyanın tamamını masaya koymaktır.
Liste ekranında sipariş no, müşteri adı ve tutar gösteriliyor; kullanıcı bu ekranda hiçbir şeyi değiştirmiyor. Sorgu ne döndürmeli?
JOIN FETCH ile N+1 çözüldü. Aynı liste ekranında DTO projection'a geçmek ne kazandırır?
Sadece gerekeni iste
Bir projectionSorgudan entity yerine yalnızca gereken alanları taşıyan bir tip döndürmek: interface ya da record. Seçilen sütunlar azalır, persistence context'e hiçbir şey girmez.Sözlükte gör →, sorgudan entity yerine yalnızca gereken alanları döndürür:
- Kapalı interface projection: getter’lar alanlara ya da sorgudaki alias’lara karşılık gelir.
- Record DTO:
select new OrderRow(o.id, c.name, o.total)ile doğrudan oluşturulur. - Dinamik projection:
<T> List<T> findByStatus(Status s, Class<T> type), dönüş tipini çağıran seçer.
Interface projection'da getCustomerName() @Value('#{target.customer.name}') ile işaretli. Spring Data kaç sütun seçer? Cevabı göster
Hepsini. SpEL ifadesi entity’nin kendisine (target) erişir; Spring Data hangi alanın gerekeceğini bilemez. Entity yüklenir ve LAZY customer her satırda ayrı sorguyla gelir.
Buna open projectionGetter'ı @Value ile bir SpEL ifadesine bağlanan interface projection. İfade entity'nin kendisine eriştiği için Spring Data bütün entity'yi yükler.Sözlükte gör → denir: kolaylık sağlar, performans sağlamaz.
Kafam karıştı, daha basit anlat
Projection, dosyadan yalnızca istediğin satırların fotokopisini çekmektir. Daha hafiftir ve Hibernate onu izlemek zorunda kalmaz.
Aşağıdaki projection'ı kullanan bir sorgu için Spring Data ne seçer? interface OrderRow { Long getId(); @Value("#{target.customer.name}") String getCustomerName(); }
Spring Data JPA repository'sinde aynı sorguyu bazen OrderRow, bazen OrderDetail olarak döndürmek istiyorsun. Hangi özellik bunu sağlar?
Kendin gör
Üç alan için ne kadar veri çekiyorsun?
Tohum 708145Oynat ya da adımla.
Şu an ne oldu?
Açık projection (@Value SpEL)
Ekranda sipariş no, müşteri adı ve tutar var. Veritabanından ne kadarı geliyor?
Görevler0/3
Bir "projection" ile N+1 üretaçık
İpucu
SpEL'e bak.
Tek sorgu, ama 3 alan için 20 sütunaçık
İpucu
N+1'i entity'yi bırakmadan çöz.
200 satırlık sayfayı sıfır entity ile getiraçık
İpucu
Record ya da kapalı projection.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat. Açık projection: 21 sorgu, 20 sütun, 40 entity.
- “JOIN FETCH” seç. Tek sorgu, ama sütunlar ve entity’ler aynı.
- “record DTO” seç. Tek sorgu, 3 sütun, sıfır entity.
- Sayfayı 200 yap, sonra
List<Order>seç. 201 sorgu.
Sipariş listesi uç noktası yavaş ve JSON'da müşterinin parola hash'i görünüyor. Hatalı satır hangisi?
Her ihtiyacı en uygun veri çekme biçimine göre ayır.
Tuzaklar
Entity’yi API’ye döndürmek. Jackson her getter’ı dolaşır: LAZY ilişkiler yüklenir, istemediğin alanlar JSON’a çıkar. API’nin sözleşmesi bir DTO olmalı.
Projection’ı değiştirmeye çalışmak. Projection yönetilmez; değişikliği kimse kaydetmez. Değişiklik için id ile entity al ve domain metodunu çağır.
Native sorguda alias’sız sütun. Interface projection sütunları adla eşler. full_name sütunu getCustomerName()’e kendiliğinden bağlanmaz; AS customerName yaz.
Kafam karıştı, daha basit anlat
Fotokopinin üzerine yazmak asıl dosyayı değiştirmez. Değiştirmek istiyorsan asıl dosyayı getir ve onun üzerinde çalış.
Native bir SQL sorgusunun sonucunu interface projection'a eşlemek istiyorsun: SELECT o.id, c.full_name, o.total FROM … Projection'da getCustomerName() var ve hep null dönüyor. Neden?
Aşağıdaki örnek bir bankanın düzenli ödeme talimatları ekranından. Aynı entity üzerinde okuma ve yazma yolları ayrılıyor: liste ekranı projection ile, talimat iptali entity ve domain metoduyla.
Derinleş · Düzenli ödeme talimatları: okuma projection, yazma entity 5 dosya · ~123 satır · ilk okumada atlayabilirsin
Kendini sına
Bir entity yüklemek, bütün sütunlarını getirmek demektir.
1 milyon siparişi CSV'ye aktaran bir iş OutOfMemoryError veriyor. findAll() ile Order entity'leri okunuyor. En etkili değişiklik hangisi?
Aklında kalacak üç şey
- 1 Yalnızca gösterilecek veri için entity değil, gereken alanları taşıyan bir record ya da kapalı interface projection döndürülür.
- 2 @Value ile yazılan açık projection bütün entity'yi yükler ve tembel ilişkilerde N+1 üretebilir.
- 3 Projection salt okunurdur ve Hibernate tarafından izlenmez. Bir şey değiştirilecekse entity yüklenir.
4 kart sonraki derste seni bekliyor