İçeriğe geç

Cascade ve İlişkinin Sahibi — Kalem Gerçekten Eklendi mi?

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

Önce şunu oku: Flush ve SQL Sırası — save() Satırında Neden Hiçbir Şey Olmuyor?

30 saniyede özet

Sipariş ile kalemleri iki yönden birbirini tanır, ama veritabanına yalnızca biri yazar: kalemdeki @ManyToOne. Öbür taraf sadece bir aynadır. Cascade kaydetmeyi taşır, orphanRemoval da listeden çıkanı siler.

Siparişe yeni kalem eklenen ekranda hata yoktu, ama kalem veritabanına hiç yazılmadı. Kalemi silen ekranda da hata yoktu, ama orada da tuhaf bir şey oluyordu.

  1. Bayt: Siparişe kalem ekledim, ekranda görünüyordu. Veritabanına hiç yazılmamış!

  2. Sen: Kalemi listeye eklemedin mi?

  3. Bayt: Ekledim! order.getLines().add(kalem). Başka ne gerekiyordu ki?

  4. Bayt: Bir çocuğun hangi sınıfta olduğunu liste mi belirler, kayıt formu mu?

İlişkiyi kim yazar?

Çift yönlü bir ilişkinin iki ucu vardır: Order.lines ve OrderLine.order. Yabancı anahtar order_lines tablosundadır ve onu yazan uç owning sideÇift yönlü bir ilişkide yabancı anahtarı yazan taraf; genellikle @ManyToOne. mappedBy ile işaretli taraf yalnızca bir aynadır; onu değiştirmek SQL üretmez.Sözlükte gör →, yani @ManyToOne tarafıdır.

mappedBy = "order" ile işaretli koleksiyon bir aynadır. Onu değiştirmek bellekteki nesneyi değiştirir; FK’ye dair hiçbir SQL üretmez.

Order.java
public void addLine(OrderLine line) {
lines.add(line);
line.setOrder(this);
}
Kafam karıştı, daha basit anlat

İlişkinin iki ucu var ama kalemi yalnızca biri tutar. Veritabanına yazan uç kalemin tarafıdır. mappedBy olan taraf ise yalnızca bir aynadır: aynadaki görüntüyü değiştirmek asıl şeyi değiştirmez.

Hızlı kontrolBaşlangıç

Order'da @OneToMany(mappedBy = "order") List<OrderLine> lines, OrderLine'da @ManyToOne Order order var. order_id sütununu hangi alan belirler?

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

cascade = ALL açık. Kod yalnızca order.getLines().add(newLine) yapıyor, newLine.setOrder(order) çağırmıyor. order_id NOT NULL. Ne olur?

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

Birlikte kaydet, birlikte sil

cascadeBir entity'ye yapılan işlemin (persist, merge, remove…) ilişkideki entity'lere de uygulanması. Yalnızca parçası olduğu bütünle birlikte yaşayan çocuklar için kullanılır.Sözlükte gör →, ebeveyne yapılan işlemi çocuklara taşır: sipariş persist edilince yeni kalemler de persist edilir. Cascade yoksa yeni kalem ancak kendisi persist edilirse kaydedilir.

orphanRemoval kapalı. order.getLines().remove(kalem) çağrıldı ve commit edildi. Kalem veritabanından silinir mi? Cevabı göster

Hayır. mappedBy tarafındaki koleksiyondan çıkarmak SQL üretmez. Kalem veritabanında durur ve sipariş yeniden yüklenince geri gelir.

Liste bir aynadır; resmi kayıt formdadır.
Adım adım oku
  1. Kalemi yalnızca Order.lines listesine eklemek, sınıf listesine isim yazmaktır: veritabanında kayıt oluşmaz.
  2. OrderLine.order alanı, yani kayıt formu doldurulunca yabancı anahtar yazılır ve kalem kaydedilir.
  3. orphanRemoval kapalıyken kalemi listeden çıkarmak kaydı silmez; kalem veritabanında durmaya devam eder.
  4. orphanRemoval açıkken listeden çıkan kalem sahipsiz kabul edilir ve silinir.

orphanRemovalKoleksiyondan çıkarılan ya da ebeveyniyle bağı koparılan çocuğun veritabanından silinmesi. cascade REMOVE'dan farklıdır: o, ebeveyn silinince çalışır.Sözlükte gör → = true, koleksiyondan çıkan çocuğu siler. cascade = REMOVE ise yalnızca ebeveyn silinince çalışır.

Kafam karıştı, daha basit anlat

Cascade, “siparişe ne yaparsan kalemlerine de yap” demektir. Kalemi listeden çıkarmak ise onu silmez; silinmesini istiyorsan orphanRemoval gerekir.

Hızlı kontrolOrta

orphanRemoval yok. order.getLines().remove(line) çağrıldı, transaction commit edildi. Veritabanında ne olur?

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

cascade = REMOVE ile orphanRemoval = true arasındaki fark nedir?

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

Kendin gör

Kalem gerçekten eklendi mi?

Tohum 947319

Oynat ya da adımla.

Hız
Adım 0

Şu an ne oldu?

Order.lines tarafı mappedBy: yalnızca bir ayna

Yabancı anahtarı OrderLine.order yazar. Koleksiyonu değiştirmek, sahip tarafı değiştirmek değildir.

Görevler0/3

  • Yeni kalemi order_id = NULL ile kaydetmeye çalışaçık

    İpucu

    Cascade açık, ama yalnızca koleksiyona ekle.

  • Koleksiyondan çıkardığın kalem veritabanında kalsınaçık

    İpucu

    orphanRemoval kapalı, sahip tarafa dokunma.

  • Ekle ve çıkar; bellek ile veritabanı aynı kalsınaçık

    İpucu

    Yardımcı metot, cascade ve orphanRemoval.

Olay günlüğü (0)

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

  1. Varsayılanla oynat. Cascade ALL, yalnızca koleksiyon: yeni kalem order_id NULL ile patladı, eski kalem hayalet kaldı.
  2. “İki tarafı da güncelle”yi aç. Ekleme düzeldi; çıkarma şimdi NULL’a güncellemeye çalıştı.
  3. orphanRemoval’ı aç. İkisi de doğru.
  4. cascade’i “Yok” yap. Yeni kalem hiç kaydedilmedi.
Hızlı kontrolOrta

Bir kalemi silen kod, ürün kataloğundan da ürünü siliyor. Hatalı satır hangisi?

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

Hatalı satıra dokun, sonra kontrol et.

OrderLine.java
Java 21UTF-8LF

Her ilişkiyi cascade ve orphanRemoval açısından ayır.

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

Sınıflandırılmamış

cascade ALL + orphanRemoval

Çocuk yalnızca ebeveyniyle var.

    Cascade yok

    İki taraf bağımsız yaşar ya da paylaşılır.

      Tuzaklar

      Paylaşılan entity’ye cascade. Kalemden ürüne cascade = ALL, bir kalemi silerken ürünü katalogdan siler. Cascade yalnızca ebeveynden, onunla birlikte ölen çocuğa doğru kurulur.

      Koleksiyonu yenisiyle değiştirmek. orphanRemoval’lı bir koleksiyona setLines(new ArrayList<>()) hata verir. Mevcut koleksiyonu clear() edip doldur ya da setter yazma.

      Büyük koleksiyonda cascade REMOVE. Hibernate her çocuğu yükler ve tek tek siler. Binlerce çocuk için bilinçli bir toplu silme gerekir.

      Kafam karıştı, daha basit anlat

      Cascade’i yalnızca ebeveynden, onunla birlikte yaşayıp ölen çocuğa kur. Ürün kataloğu gibi başkalarıyla paylaşılan şeylere asla kurma.

      Hızlı kontrolİleri

      orphanRemoval = true olan bir ilişkide order.setLines(new ArrayList<>(dtoLines)) yazıldı. Hibernate hata veriyor. Neden?

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

      Aşağıdaki örnek bir bankanın kurumsal müşterilere sunduğu toplu ödeme talimatından; aggregate doğru eşleniyor: iki tarafı birlikte güncelleyen metotlar, yalnızca aggregate içinde cascade, koleksiyonu değiştirmeden yenileme ve bunları veritabanından yeniden okuyarak doğrulayan test.

      Derinleş · Toplu ödeme talimatı aggregate'i: sahip taraf, cascade ve orphanRemoval 5 dosya · ~127 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      src/main/java/com/bank/bulk/ BulkPayment.java Ebeveyn: koleksiyon mappedBy, cascade ve orphanRemoval yalnızca kendi satırları için. Setter yok; değişiklik yardımcı metotlarla ve yalnızca taslakken.

      src/main/java/com/bank/bulk/BulkPayment.java
      // A company's supplier payment run: one debit account, many payment lines,
      // approved as a whole by two signatories before it is sent.
      @Entity
      @Table(name = "bulk_payments")
      public class BulkPayment {
      @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
      private Long id;
      @Enumerated(EnumType.STRING)
      private BulkStatus status = BulkStatus.DRAFT;
      // Lines live and die with the run: cascade + orphanRemoval.
      @OneToMany(mappedBy = "bulkPayment", cascade = CascadeType.ALL, orphanRemoval = true)
      private List<PaymentLine> lines = new ArrayList<>();
      public void addLine(Payee payee, BigDecimal amount) {
      requireDraft();
      lines.add(new PaymentLine(this, payee, amount)); // owning side set in the constructor
      }
      public void removeLine(long lineId) {
      requireDraft();
      lines.removeIf(line -> line.getId() != null && line.getId() == lineId); // orphanRemoval deletes it
      }
      public void replaceLines(List<LineSpec> specs, Function<Long, Payee> payees) {
      requireDraft();
      lines.clear(); // same collection instance, never a new list
      specs.forEach(spec -> addLine(payees.apply(spec.payeeId()), spec.amount()));
      }
      private void requireDraft() {
      // Once signed, the lines are what the signatories approved: no edits.
      if (status != BulkStatus.DRAFT) throw new IllegalStateException("bulk payment is " + status);
      }
      // Read-only view: outside code cannot add a line without the owning side.
      public List<PaymentLine> getLines() {
      return Collections.unmodifiableList(lines);
      }
      public Long getId() { return id; }
      }

      src/main/java/com/bank/bulk/ PaymentLine.java Sahip taraf: bulk_payment_id'yi bu alan yazar. Kayıtlı alıcıya cascade yok; alıcı başka talimatlarda da kullanılır.

      src/main/java/com/bank/bulk/PaymentLine.java
      @Entity
      @Table(name = "payment_lines")
      public class PaymentLine {
      @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
      private Long id;
      // Owning side: this field decides bulk_payment_id.
      @ManyToOne(fetch = FetchType.LAZY, optional = false)
      @JoinColumn(name = "bulk_payment_id")
      private BulkPayment bulkPayment;
      // Shared data: no cascade. Removing a line must never delete a saved payee.
      @ManyToOne(fetch = FetchType.LAZY, optional = false)
      private Payee payee;
      private BigDecimal amount;
      protected PaymentLine() {}
      PaymentLine(BulkPayment bulkPayment, Payee payee, BigDecimal amount) {
      this.bulkPayment = bulkPayment;
      this.payee = payee;
      this.amount = amount;
      }
      public Long getId() { return id; }
      public BigDecimal getAmount() { return amount; }
      }

      src/main/java/com/bank/bulk/ BulkPaymentEditing.java Satırları toptan yenilemek: aynı koleksiyonu temizle ve doldur, yenisini atama.

      src/main/java/com/bank/bulk/BulkPaymentEditing.java
      @Service
      class BulkPaymentEditing {
      private final BulkPaymentRepository bulkPayments;
      private final PayeeRepository payees;
      BulkPaymentEditing(BulkPaymentRepository bulkPayments, PayeeRepository payees) {
      this.bulkPayments = bulkPayments;
      this.payees = payees;
      }
      // The accountant re-uploads the corrected supplier list before signing.
      @Transactional
      public void replaceLines(long bulkId, List<LineSpec> specs) {
      BulkPayment bulk = bulkPayments.findById(bulkId).orElseThrow();
      // getReferenceById: a proxy, no SELECT per payee, just its id for the FK.
      bulk.replaceLines(specs, payees::getReferenceById);
      // No save(): orphanRemoval deletes the old lines, cascade inserts the new ones at commit.
      }
      }

      src/main/resources/db/migration/ V9__payment_lines.sql Şema: bulk_payment_id NOT NULL; sahip taraf unutulursa hata hemen görünür.

      src/main/resources/db/migration/V9__payment_lines.sql
      CREATE TABLE payment_lines (
      id BIGINT PRIMARY KEY,
      bulk_payment_id BIGINT NOT NULL REFERENCES bulk_payments (id), -- a forgotten owning side fails here
      payee_id BIGINT NOT NULL REFERENCES payees (id),
      amount NUMERIC(15, 2) NOT NULL CHECK (amount > 0)
      );
      CREATE INDEX payment_lines_bulk_payment_id ON payment_lines (bulk_payment_id);

      src/test/java/com/bank/bulk/ PaymentLinesMappingTest.java Test: değiştir, flush ve clear, yeniden yükle. Bellekteki nesneye güvenmez.

      src/test/java/com/bank/bulk/PaymentLinesMappingTest.java
      @DataJpaTest
      class PaymentLinesMappingTest {
      @Autowired TestEntityManager em;
      @Test
      void addAndRemoveReachTheDatabase() {
      Payee supplier = em.persist(new Payee(7L, "TR330006100519786457841326", "Kırtasiye A.Ş."));
      BulkPayment bulk = new BulkPayment();
      bulk.addLine(supplier, new BigDecimal("1200.00"));
      bulk.addLine(supplier, new BigDecimal("350.50"));
      em.persist(bulk);
      em.flush();
      em.clear(); // forget everything in memory
      BulkPayment reloaded = em.find(BulkPayment.class, bulk.getId());
      assertThat(reloaded.getLines()).hasSize(2); // cascade inserted both, with bulk_payment_id
      long first = reloaded.getLines().get(0).getId();
      reloaded.removeLine(first);
      em.flush();
      em.clear();
      assertThat(em.find(PaymentLine.class, first)).isNull(); // orphanRemoval deleted it
      assertThat(em.find(Payee.class, supplier.getId())).isNotNull(); // the payee survived
      }
      }

      Kendini sına

      Şimşek turu1/5

      Çift yönlü bir ilişkide yabancı anahtarı @ManyToOne tarafı yazar.

      Soru 1/2İleri

      Tek yönlü @OneToMany List<OrderLine> lines (mappedBy ve @JoinColumn olmadan) yazdın. Şema oluşturulunca ne görürsün?

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

      Aklında kalacak üç şey

      1. 1 Çift yönlü ilişkide order_id sütununu OrderLine.order yazar. Yalnızca listeye eklemek o sütunu boş bırakır; iki tarafı birlikte güncelleyen bir yardımcı metot gerekir.
      2. 2 mappedBy tarafındaki listeden bir şey çıkarmak SQL üretmez. Çocuğun silinmesi için orphanRemoval = true gerekir.
      3. 3 Cascade yalnızca ebeveyniyle birlikte yaşayıp ölen çocuklar içindir. Ürün ya da müşteri gibi paylaşılan kayıtlara cascade REMOVE koymak veri siler.
      Sonraki kapı Kaydettiğin nesne bir kümenin içinde kayboldu. equals ve hashCode'u id'ye göre yazmıştın, değil mi? Entity equals ve hashCode — Aynı Sipariş mi? · 8 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı