Persistence Context — Hibernate'in Hafızası
Önce şunu oku: JPA N+1 Problemi
30 saniyede özet
Hibernate yüklediği her nesnenin fotoğrafını tutar ve iş bitince farkı kendisi kaydeder; save() demene gerek yok. İş bittikten sonra yapılan değişiklik ise hiçbir yere yazılmaz.
Bir otel odasından çıkarken kat görevlisi neyin değiştiğini deftere yazar, sen bir şey söylemesen bile. Ama çıkış yaptıktan sonra odaya kimse bakmaz.
-
Bayt: Kodda tek bir save() yok ama fiyat veritabanında değişmiş!
-
Sen: Öbür metotta da save() yok ve fiyat değişmemiş.
-
Bayt: İki metot arasındaki tek fark bir anotasyon. Neden?
-
Bayt: Kat görevlisi odaya girerken fotoğraf çekiyor olabilir mi?
Kodda tek bir save() yok, ama veritabanında fiyat değişmiş. Başka bir metotta save() yok, fiyat değişmemiş. İki metot arasındaki tek fark bir anotasyon.
Hibernate fotoğraf çeker
persistence contextHibernate'in yönettiği entity'leri tuttuğu birinci seviye önbellek. Transaction ile birlikte açılır ve kapanır; kapandıktan sonra lazy ilişkilere dokunmak hata verir.Sözlükte gör →, Hibernate’in o an izlediği entity’lerdir. Spring’de varsayılan olarak transaction’la açılır ve commit’le kapanır.
@Transactional bir metot ürünü yükleyip fiyatını değiştiriyor. save() çağrılmıyor. UPDATE atılır mı? Cevabı göster
Atılır. Hibernate ürünü yüklerken bir anlık görüntü sakladı. Commit öncesi ikisini karşılaştırır ve farkı yazar.
Adım adım oku
- Transaction içinde ürün yüklenince Hibernate onun o anki hâlinin bir kopyasını, yani fotoğrafını saklar.
- Kod fiyatı 120 yapar, ama hiçbir yerde save() çağırmaz.
- Commit anında Hibernate nesneyi fotoğrafla karşılaştırır, farkı bulur ve UPDATE gönderir.
- Transaction bittikten sonra nesne detached olur. Fiyatı 150 yapsan da kimse karşılaştırmaz ve hiçbir şey yazılmaz.
Buna dirty checkingHibernate'in managed entity'lerin yüklendikleri andaki anlık görüntüsünü saklayıp flush sırasında şimdiki hâlle karşılaştırması ve farkı UPDATE olarak yazması.Sözlükte gör → denir. Managed bir entity’de değişiklik yapmak, onu kaydetmek demektir.
Kafam karıştı, daha basit anlat
Hibernate yüklediği her nesnenin fotoğrafını çeker. İş bitince nesneye tekrar bakar, fotoğraftan farklıysa değişikliği kendisi kaydeder. Senin save demene gerek kalmaz.
JPA'daki persistence context en kısa hâliyle nedir?
SQL loglaması açık. Metot çağrıldığında konsolda hangi sıra görünür?
Managed ve detached
| Durum | Ne demek | Değişiklik yazılır mı |
|---|---|---|
| Transient | new ile oluştu, hiç kaydedilmedi | Hayır |
| Managed | Açık bir persistence context izliyor | Evet, flush’ta |
| Detached | Bir zamanlar izleniyordu, artık değil | Hayır |
Transaction bitince her entity detached entityPersistence context'i kapanmış bir entity. Nesne hâlâ elinde, ama değişikliklerini kimse izlemez ve tembel ilişkileri artık yüklenemez.Sözlükte gör → olur. Service’ten controller’a dönen entity artık izlenmez; tembel ilişkileri de yüklenemez.
Spring Boot bunu varsayılan olarak gizler: spring.jpa.open-in-view=true isteği baştan sona açık tutar ve açılışta bir uyarı basar.
Kafam karıştı, daha basit anlat
Transaction bitince Hibernate izlemeyi bırakır. Nesne elinde kalır ama artık kimse onun fotoğrafına bakmaz, tembel ilişkileri de yüklenemez.
Aynı `reprice` metodunda @Transactional yoksa (ve open-in-view kapalıysa) ne olur?
Her anda entity hangi durumda? (open-in-view kapalı)
Kendin gör
Persistence context — Hibernate neyi, ne zaman yazıyor?
Tohum 786094Product#1henüz yüklenmedi
- Oynat ya da adımla.
Şu an ne oldu?
Başlıyor
Servis ürünü yüklüyor ve fiyatını değiştiriyor. Kodda save() çağrısı yok.
Görevler0/4
save() çağırmadan UPDATE attıraçık
İpucu
Dirty checking yalnızca managed bir entity'de ve açık bir transaction içinde çalışır.
Bir değişikliği sessizce kaybetaçık
İpucu
Aynı senaryo, ama nesneyi izleyen bir persistence context olmadan.
LazyInitializationException alaçık
İpucu
Boot'un varsayılanı bu hatayı gizliyor. Hangi ayar?
İki doğru isteği yanlış bir stokla bitiraçık
İpucu
İki istek aynı anda okursa ve UPDATE hiçbir şeyi kontrol etmezse?
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat. Transaction yok: fiyat değişti ama hiçbir UPDATE yok.
- @Transactional’ı aç. save() olmadan UPDATE atıldı.
- “Tembel koleksiyon” seç. Çalışıyor — ama sorgu controller’dan geliyor.
- open-in-view’u kapat. Aynı kod: LazyInitializationException.
- “İki istek” seç, sonra @Version’ı aç. 5 yerine 2: çakışma yakalandı.
`LazyInitializationException: could not initialize proxy - no Session` ne anlatır?
Satır satır: iki istek, tek stok
Kim sonra yazarsa kazanır
@Transactionalpublic void reserve(long productId, int qty) { var product = products.findById(productId).orElseThrow(); if (product.getStock() < qty) throw new OutOfStockException(); product.setStock(product.getStock() - qty);}Debug
istek A Stok 10 okundu.
- A görüyor
- = 10
Sol/sağ ok tuşlarıyla da gezebilirsin.
Hiçbir satır yanlış değil; yanlış olan iki doğru isteğin iç içe geçmesi. optimistic lockingKilit almadan, yazarken "okuduğumdan beri değişmedi mi?" diye kontrol etmek. JPA'da @Version alanı UPDATE'e bir sürüm koşulu ekler.Koşul tutmazsa UPDATE sıfır satır etkiler ve OptimisticLockException fırlatılır. Doğru tepki, bütün işlemi taze veriyle yeniden denemektir.Sözlükte gör → bunu yazma anında fark eder. Doğru tepki, bütün işlemi taze veriyle yeniden denemektir.
Kafam karıştı, daha basit anlat
İki kişi aynı rafa aynı anda bakıp “bir tane kalmış” diyor ve ikisi de alıyor. Optimistic locking, ikincisinin kasada “raf değişti” uyarısı almasıdır.
Entity'ye `@Version long version` eklendi. İki transaction aynı satırı okuyup ikisi de değiştirirse ikincisinin commit'inde ne olur?
Tuzaklar
Yazan metotta readOnly = true. Flush atlanır; değişiklik hatasız kaybolur.
LazyInitializationException’a EAGER. Hata gider, her sorgu ilişkiyi de çeker. Gerekeni o sorguda yükle: fetch join, @EntityGraph, DTO.
Open-in-view’a güvenmek. Sorgular controller’a kayar, bağlantı istek boyunca tutulur. Yeni projede kapat.
Bayat nesneyi yeniden kaydetmek. OptimisticLockException sonrası aynı nesneyi save etmek aynı çakışmayı tekrarlar.
Controller'da `order.getItems()` LazyInitializationException veriyor. En iyi düzeltme hangisi?
Ürün adı değiştirme endpoint'i hata vermiyor ama ad hiçbir zaman değişmiyor. Hangi satır sorunun kaynağı?
Aşağıdaki örnek bir bankanın kart yönetim ekranından. Persistence context’in üç sonucu bir arada: save çağırmadan yazan dirty checking, iki personelin aynı limiti değiştirmesini yakalayan @Version ve çakışmayı düzgün bir 409’a çeviren hata yönetimi.
Derinleş · Kredi kartı limiti güncelleme: iki personel, bir kart 6 dosya · ~118 satır · ilk okumada atlayabilirsin
Kendini sına
Managed bir entity'deki değişiklik commit'te save() çağrılmadan yazılabilir.
@Transactional bir metotta findById ile gelen entity değiştirildikten sonra `repository.save(entity)` çağrılıyor. Bu çağrı ne yapar?
Aklında kalacak üç şey
- 1 Transaction içinde yüklenen bir nesneyi değiştirmek yeter; Hibernate farkı bulup UPDATE'i kendisi yazar. Transaction yoksa değişiklik sessizce kaybolur.
- 2 LazyInitializationException, oturum kapandıktan sonra tembel bir ilişkiye dokunulduğunu söyler. Çözüm EAGER değil, gerekeni sorguda yüklemektir.
- 3 Aynı satırı iki kişi okuyup yazarsa sonra yazan öncekini siler. @Version bu kayıp güncellemeyi yakalanabilir bir hataya çevirir.
5 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.
- Spring Boot EkosistemiJPA Projection ve DTO — Üç Alan İçin Ne Kadar Veri?Listede yalnızca isim ve fiyat göstereceksen bütün nesneyi yüklemek neden pahalı?Derse git
- Hibernate ve JPAID Üretimi ve Batch Insert — 200 Satır Kaç Gidiş Dönüş?Toplu yazmayı açtın ama içe aktarma hiç hızlanmadı. Engel neydi?Derse git