İçeriğe geç

Flush ve SQL Sırası — save() Satırında Neden Hiçbir Şey Olmuyor?

Orta 8 dk Çok sık karşılaşılır

Önce şunu oku: ID Üretimi ve Batch Insert — 200 Satır Kaç Gidiş Dönüş?

30 saniyede özet

save() dediğinde veritabanına hemen bir şey gitmez; değişiklik bir listeye yazılır. Liste sonra toplu gönderilir, üstelik senin sıranla değil: önce eklemeler, en son silmeler. Hatalar da o an ortaya çıkar.

Kod önce eski hesabı siliyor, sonra aynı e-postayla yenisini ekliyordu. Veritabanı unique kısıt hatası verdi. Oysa kodun sırası tertemizdi.

  1. Bayt: Önce eski hesabı sildim, sonra aynı e-postayla yenisini ekledim. Sıra kusursuz.

  2. Sen: Ve?

  3. Bayt: Veritabanı aynı e-posta iki kez var diye bağırdı!

  4. Bayt: Kodu yazdığın sıra ile SQL'in gittiği sıra aynı mı? Önce tahmin et.

save() bir not, SQL değil

save() ve persist() değişikliği persistence context’teki bir kuyruğa koyar. Veritabanına giden SQL, flushPersistence context'teki bekleyen değişiklikleri SQL olarak veritabanına göndermek. Commit değildir: transaction açık kalır ve geri alınabilir.Sözlükte gör → anında çalışır.

Varsayılan FlushModeFlush'ın ne zaman otomatik yapılacağı. AUTO (varsayılan) commit'ten ve etkilenecek sorgulardan önce flush eder; COMMIT yalnızca commit'te.Sözlükte gör → AUTO, commit’ten önce ve sonucu etkilenecek bir sorgudan önce flush eder. Böylece kod, aynı transaction’da kendi yazdığını görür. Flush commit değildir: transaction açık kalır ve geri alınabilir.

Kafam karıştı, daha basit anlat

save() postaya mektup bırakmaktır, mektubun yola çıkması değil. Mektuplar flush anında toplu hâlde yola çıkar.

Hızlı kontrolBaşlangıç

Sequence ile id alan bir entity için orders.save(order) çağrıldı. O satırda veritabanına ne gider?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

flush ile commit arasındaki fark nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Varsayılan FlushMode AUTO'da, persist ettiğin bir siparişin ardından aynı transaction'da JPQL ile sipariş sayısını sorguluyorsun. Sonuç yeni siparişi içerir mi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Zarflar hangi sırayla gider?

Aynı transaction'da önce accounts.delete(eski), sonra accounts.save(new Account(aynı e-posta)). Commit'te hangi SQL önce gider? Cevabı göster

INSERT. Flush, işlemleri koddaki sırayla değil türüne göre çalıştırır: önce insert’ler, en son delete’ler. Yeni satır eklenirken eski satır hâlâ duruyordur ve unique kısıt patlar.

Sen notu yazarsın, sekreter zarfları kendi sırasıyla postalar.
Adım adım oku
  1. Kod önce eski hesabı siliyor, sonra aynı e-postayla yenisini ekliyor.
  2. İkisi de flush anına kadar bekler; flush'ta Hibernate SQL'leri kendi düzenine göre dizer.
  3. INSERT'ler DELETE'lerden önce gider; yeni hesap eklenirken eskisi hâlâ durduğu için unique kısıtı patlar.
  4. Silmeden sonra açıkça flush() çağırmak DELETE'i hemen gönderir; INSERT ondan sonra gider.

Bu sırayı action queueHibernate'in flush'ta çalıştıracağı işlemleri tuttuğu kuyruk. İşlemleri koddaki sırayla değil türüne göre çalıştırır: insert, update, koleksiyonlar, en son delete.Sözlükte gör → belirler. Sıranın önemli olduğu yerde araya bir flush() koyarsın; çoğu zaman daha iyisi, silip eklemek yerine satırı güncellemektir.

Kafam karıştı, daha basit anlat

Postacı mektupları senin yazdığın sırayla değil, türüne göre dağıtır: önce eklemeler, en son silmeler. Sıra önemliyse araya kendin bir flush koy.

Hızlı kontrolOrta

Kullanıcı e-postasını değiştirmek için eski hesabı silip aynı e-postayla yenisini oluşturan kod unique kısıt hatası veriyor. Hatalı satır hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Hatalı satıra dokun, sonra kontrol et.

AccountService.java
Java 21UTF-8LF

FlushMode AUTO'da her işlemi, flush'ı tetikleyip tetiklemediğine göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Flush tetikler

Bekleyen değişiklikler SQL olarak gider.

    Flush tetiklemez

    SQL gitmez ya da yalnızca okuma yapılır.

      Kendin gör

      SQL ne zaman gidiyor?

      Tohum 739720

      Oynat ya da adımla.

      Hız
      Adım 0

      Şu an ne oldu?

      Aynı e-postayla sil ve ekle

      save() ve persist() SQL göndermez; değişikliği kuyruğa koyar. SQL, flush anında ve Hibernate'in seçtiği sırayla gider.

      Görevler0/3

      • Önce silip sonra eklediğin hâlde unique kısıtı patlataçık

        İpucu

        Varsayılan ayarlar yeter.

      • Aynı transaction'da persist ettiğin siparişi sayımda kaçıraçık

        İpucu

        FlushMode'a bak.

      • Kısıt hatasını catch bloğunda yakalaaçık

        İpucu

        Hatanın hangi satırda fırlayacağını belirle.

      Olay günlüğü (0)

      Henüz olay yok. Oynat veya adımla.

      1. Varsayılanla oynat. Sil ve ekle: INSERT önce gitti, unique kısıt patladı.
      2. “Doğru yerde flush et”i aç. DELETE önce gitti.
      3. Senaryoyu “try/catch içinde save” yap, flush’ı kapat. Hata commit’te fırladı, catch’e hiç girilmedi.
      4. Flush’ı aç. saveAndFlush, hatayı catch bloğunun içine taşıdı.
      5. “persist, sonra JPQL sayım” seç ve FlushMode’u COMMIT yap. Sayım yeni siparişi görmedi.
      Hızlı kontrolOrta

      try { orders.save(order); } catch (DataIntegrityViolationException e) { … } yazdın, ama duplicate sipariş numarasında catch bloğuna hiç girilmiyor ve istemci 500 alıyor. Neden?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Tuzaklar

      Her save’ten sonra flush. Sorunları görünür kılar ama batching’i öldürür ve her flush’ta dirty checking çalıştırır. Flush’ı yalnızca sıranın ya da hata yerinin önemli olduğu yerde kullan.

      readOnly metotta yazmak. Spring, readOnly transaction’da Hibernate’in flush’ını kapatır. Değişiklik hatasız kaybolur.

      Döngüde sorgu. Her JPQL sorgusundan önce otomatik flush çalışır. Büyüyen bir persistence context’te bu, her iterasyonu biraz daha pahalı yapar.

      Kafam karıştı, daha basit anlat

      Her mektuptan sonra postaneye koşma. Flush’ı yalnızca sıranın gerçekten önemli olduğu yerde elle çağır.

      Hızlı kontrolİleri

      @Transactional(readOnly = true) bir metotta entity'nin bir alanını değiştirdin. Spring + Hibernate'te ne olur?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aşağıdaki örnek bir bankanın kayıtlı alıcı ve kredi sözleşmesi servislerinden. Flush yalnızca gereken üç yerde, bilinçli kullanılıyor: sıranın önemli olduğu yerde, hatanın belirli bir satırda yakalanması gereken yerde ve döngüdeki gereksiz otomatik flush’ı kapatmak için.

      Derinleş · Kayıtlı alıcılar ve kredi sözleşmeleri: flush'ı bilerek kullanmak 6 dosya · ~106 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      src/main/java/com/bank/payee/ PayeeService.java Aynı IBAN'lı kayıtlı alıcıyı sil ve ekle: aradaki flush DELETE'i INSERT'ten önce gönderir. Daha iyisi güncellemek.

      src/main/java/com/bank/payee/PayeeService.java
      // A customer's saved payees: unique (customer_id, iban).
      @Service
      class PayeeService {
      private final PayeeRepository payees;
      PayeeService(PayeeRepository payees) {
      this.payees = payees;
      }
      // Preferred: one UPDATE, no ordering question at all.
      @Transactional
      public void rename(long payeeId, String nickname) {
      payees.findById(payeeId).orElseThrow().rename(nickname);
      }
      // When a delete-then-insert is truly needed (the payee changed hands and must
      // lose its history, e.g. an account closed and reopened under a new owner):
      @Transactional
      public long replace(long oldId, long customerId, String iban, String name) {
      payees.deleteById(oldId);
      payees.flush(); // DELETE goes now; flush order would put INSERT first
      return payees.save(new Payee(customerId, iban, name)).getId();
      }
      }

      src/main/java/com/bank/loan/ LoanContracts.java Sözleşme numarası çakışması save satırında değil flush'ta doğar. saveAndFlush onu burada doğurur ve anlamlı bir iş hatasına çevirir.

      src/main/java/com/bank/loan/LoanContracts.java
      @Service
      class LoanContracts {
      private final LoanContractRepository contracts;
      LoanContracts(LoanContractRepository contracts) {
      this.contracts = contracts;
      }
      @Transactional
      public LoanContract open(String contractNo, long customerId, BigDecimal principal) {
      try {
      // Flush here so a duplicate contract number fails on THIS line, not at commit.
      return contracts.saveAndFlush(LoanContract.open(contractNo, customerId, principal));
      } catch (DataIntegrityViolationException ex) {
      // The transaction is now rollback-only; do not keep using it.
      // Translate and rethrow: the caller sees a domain error, the TX rolls back.
      throw new DuplicateContractNumberException(contractNo, ex);
      }
      }
      }

      src/main/java/com/bank/loan/ LoanContractRepository.java Döngüdeki salt okunur sorgu her seferinde otomatik flush tetiklemesin diye flush modu COMMIT.

      src/main/java/com/bank/loan/LoanContractRepository.java
      interface LoanContractRepository extends JpaRepository<LoanContract, Long> {
      // Called inside the loop that restructures a customer's loans, which modifies
      // other entities. With AUTO, every call would flush and dirty-check the whole
      // persistence context first. This query does not depend on those pending
      // changes, so it may skip the flush.
      @QueryHints(@QueryHint(name = "org.hibernate.flushMode", value = "COMMIT"))
      @Query("select count(c) from LoanContract c where c.customerId = :customerId and c.status = 'ACTIVE'")
      long countActiveFor(long customerId);
      }

      src/main/java/com/bank/web/ LoanErrors.java Hata cevabı: transaction zaten geri alınıyor; istemciye anlaşılır bir 409.

      src/main/java/com/bank/web/LoanErrors.java
      @RestControllerAdvice
      class LoanErrors {
      @ExceptionHandler(DuplicateContractNumberException.class)
      ProblemDetail duplicate(DuplicateContractNumberException ex) {
      ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT,
      "Bu sözleşme numarası zaten kullanılıyor: " + ex.contractNo());
      problem.setTitle("Sözleşme numarası çakışması");
      return problem;
      }
      }

      src/main/resources/ application.yml Gerçekte ne gittiğini sırasıyla görmek için SQL ve parametre log'u.

      src/main/resources/application.yml
      logging:
      level:
      org.hibernate.SQL: DEBUG # every statement, in the order it is sent
      org.hibernate.orm.jdbc.bind: TRACE # with its parameter values: never in production,
      # it would write IBANs and amounts into the logs

      src/test/java/com/bank/payee/ PayeeReplaceTest.java Test: flush'sız sürüm unique kısıtla patlar, flush'lı sürüm geçer.

      src/test/java/com/bank/payee/PayeeReplaceTest.java
      @DataJpaTest
      @Import(PayeeService.class)
      class PayeeReplaceTest {
      private static final String IBAN = "TR330006100519786457841326";
      @Autowired PayeeService service;
      @Autowired PayeeRepository payees;
      @Autowired TestEntityManager em;
      @Test
      void deleteThenInsertWithoutFlushHitsTheUniqueConstraint() {
      long id = em.persistAndGetId(new Payee(7L, IBAN, "Ev sahibi"), Long.class);
      em.flush();
      payees.deleteById(id);
      payees.save(new Payee(7L, IBAN, "Yeni ev sahibi"));
      // Flush order: INSERT before DELETE -> the old row still holds (7, IBAN).
      assertThatThrownBy(() -> em.flush())
      .isInstanceOf(org.hibernate.exception.ConstraintViolationException.class);
      }
      @Test
      void replaceFlushesInBetween() {
      long id = em.persistAndGetId(new Payee(8L, IBAN, "Ev sahibi"), Long.class);
      em.flush();
      assertThatCode(() -> {
      service.replace(id, 8L, IBAN, "Yeni ev sahibi");
      em.flush();
      }).doesNotThrowAnyException();
      }
      }

      Kendini sına

      Şimşek turu1/5

      save() çağrısı SQL'i hemen veritabanına gönderir.

      Soru 1/2İleri

      Hibernate flush'ta işlemleri hangi sırayla çalıştırır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 save ve persist SQL göndermez. Değişiklikler flush anında gider: commit'te, etkilenecek bir sorgudan önce ya da açık bir flush çağrısında.
      2. 2 Flush'ta Hibernate işlemleri türüne göre sıralar: önce eklemeler, sonra güncellemeler, en son silmeler. Aynı benzersiz değeri silip yeniden eklemek için aradaki flush'ı kendin yaparsın.
      3. 3 Kısıt hatası SQL'in çalıştığı anda çıkar. save'i saran bir try/catch onu göremez; belli bir satırda yakalamak için saveAndFlush gerekir.
      Sonraki kapı İlişkinin iki tarafını da ayarladın ama yabancı anahtar null kaldı. Kimin sözü geçiyor? Cascade ve İlişkinin Sahibi — Kalem Gerçekten Eklendi mi? · 9 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı

      Bu dersin üstüne kurulanlar

      Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.