CodeGym /Kurse /Spring Test /Cascade und orphan removal bei Beziehungen des Artikels

Cascade und orphan removal bei Beziehungen des Artikels

Spring Test
Level 17, Lektion 1
Verfügbar

1. Die Rolle von cascade und orphanRemoval

Die Tatsache der Beziehung haben wir bereits überprüft: Der Fremdschlüssel ist an der richtigen Stelle gelandet, und der Round-Trip durch die Datenbank hat dies bestätigt. Aber der Entitäten-Graph ist nicht nur in der Statik interessant. Wir müssen auch verstehen, was passiert, wenn die übergeordnete Entität beginnt, untergeordnete Objekte zu speichern, zu löschen und neu aufzubauen.

Wenn Sie zum ersten Mal cascade = ... und orphanRemoval = true sehen, möchten Sie vielleicht wie ein Junior ehrlich reagieren: „Oh, zwei weitere Parameter in der Annotation. Wahrscheinlich muss ich sie wie im Tutorial setzen und vergessen.“ Aber in einer realen Anwendung sind diese Einstellungen keine Kosmetik, sondern Verhalten, das darüber entscheidet, ob in Ihrer Datenbank „verwaiste“ Datensätze zurückbleiben und ob das Speichern/Löschen des Entitäten-Graphen vorhersagbar funktioniert.

In der ContentHub-Domäne ist das buchstäblich spürbar. Ein Artikel hat Anhänge (ArticleAttachment) – Metadaten hochgeladener Dateien. Wenn ein Redakteur einen Anhang löscht oder ersetzt, ist es wichtig, dass in der Anhangstabelle keine „Geister“ zurückbleiben: Datensätze zu Dateien, die es nicht mehr gibt, oder Datensätze, die zu keinem Artikel mehr gehören. Und umgekehrt: Beim Speichern eines neuen Artikels mit Anhängen möchten wir, dass die Anhänge tatsächlich gespeichert werden und nicht irgendwo zwischen persist(article) und der Realität der Datenbank verloren gehen.

Um es in einem Satz zu sagen: Heute lernen wir, zwei Dinge mit Tests zu beweisen: Operationen an der übergeordneten Entität (Article) werden korrekt auf die untergeordneten Entitäten (ArticleAttachment) übertragen, und das Entfernen eines Anhangs aus der Sammlung eines Artikels führt zu einem vorhersagbaren Effekt in der Datenbank, nicht nur in der Java-Liste.

2. Cascade vs. orphanRemoval: unterschiedliche Mechanismen

Beide Begriffe klingen, als ginge es um „automatisches Löschen aller möglichen Dinge“, aber es sind unterschiedliche Geschichten. cascade – das bedeutet, wie eine Operation von EntityManager entlang des Objektgraphen „weiterfließt“. orphanRemoval – das betrifft das Schicksal eines untergeordneten Datensatzes, wenn er nicht mehr Teil der Sammlung des übergeordneten Elements ist. Beides zu verwechseln ist, als würde man „Wasser fließt durch die Rohre“ und „Der Wasserhahn wurde von der Wand entfernt“ verwechseln.

Lassen Sie uns den Unterschied maximal praxisnah in Begriffen von „Was tue ich“ → „Was passiert“ festhalten.

Mechanismus Was Sie im Code tun Was JPA als Antwort tut Typische Bedeutung für ContentHub
cascade = PERSIST entityManager.persist(article) JPA führt automatisch persist auch für article.getAttachments() aus „Ich erstelle einen Artikel sofort mit Anhängen“
cascade = REMOVE entityManager.remove(article) JPA löscht auch die zum Artikel gehörenden Anhänge „Ich lösche den Artikel – die Anhänge sollen ebenfalls verschwinden“
orphanRemoval = true article.getAttachments().remove(x) JPA löscht den Datensatz ArticleAttachment aus der Datenbank „Ich habe einen Anhang aus dem Artikel entfernt – er soll nicht in der Tabelle bleiben“

Es ist sehr wichtig, die Intuition zu erfassen: cascade wird dadurch ausgelöst, dass Sie eine Operation am übergeordneten Element aufrufen (persist/remove/merge/...). orphanRemoval wird dadurch ausgelöst, dass Sie die Zusammensetzung der Sammlung des übergeordneten Elements ändern. Der Mechanismus ähnelt einem „intelligenten Putzmann“: Er sieht, dass ein Objekt nicht mehr zum übergeordneten Element gehört – also ist es ein „orphan“ und muss gelöscht werden.

Um das zu festigen, hier ein kleines Verhaltensdiagramm. Ja, es ist fast ein Comic, aber das Gehirn merkt es sich besser:

flowchart TD
    %% Parent entity
    A[Article] -->|has many| B[ArticleAttachment]
    %% EntityManager operations trigger cascade from parent to children
    EM[EntityManager] -->|persist Article| A
    A -->|cascade persist| B
    %% Orphan removal triggers when you change the composition of the parent's collection
    A -->|remove attachment from list| OR[orphanRemoval]
    OR -->|DELETE in DB| B

Das Diagramm sagt nicht, dass „es immer so sein muss“. Es sagt, dass es so sein wird, wenn Sie die entsprechenden Einstellungen gesetzt haben. Und unsere Aufgabe ist es, Tests zu schreiben, die dieses Verhalten bestätigen, damit eine spätere Änderung der Annotation nicht zu einer stillen Regression wird.

3. Mapping in ContentHub: Wo ist Cascade sinnvoll?

Wir sprechen jetzt über die Beziehung Article → ArticleAttachment, und hier ist Cascade oft wirklich nützlich. Anhänge leben in unserer Domäne als Teil des Artikels: Für sich allein ergeben sie keinen Sinn. Das ist ähnlich wie „Ordner und Dateien darin“: Eine Datei ohne Ordner sollte in unserem Szenario nicht einfach auf dem Schreibtisch der Datenbank herumliegen.

Beispiel eines vereinfachten Mappings (die Idee ist wichtiger als die Details der Felder):

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;

@Entity
class Article {

    // Collection on inverse side (mappedBy) – reflection of the relationship.
    // The real foreign key is usually on the owning side (in ArticleAttachment.article).
    @OneToMany(mappedBy = "article", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<ArticleAttachment> attachments = new ArrayList<>(); // To avoid NPE when adding attachments
}

Hier verwenden wir CascadeType.ALL, weil es eine lehrreiche und anschauliche Variante ist. In der Produktion werden Sie die Kaskade oft auf das einschränken, was wirklich benötigt wird (z. B. PERSIST und REMOVE), um zu vermeiden, dass versehentlich Dinge „durchsickern“, die nicht sollten. Aber zunächst ist es wichtig, die Mechanik zu verstehen und zu lernen, sie zu testen.

Bei der Beziehung Article → Category ist die Situation jedoch anders. Kategorien sind ein Nachschlagewerk, sie sind gemeinsam genutzt, werden durch Migrationen befüllt, und eine Kategorie gehört zu vielen Artikeln. Daher ist eine Kaskade auf die Kategorie eine gefährliche Sache. Wenn Sie aus Gewohnheit cascade = REMOVE bei @ManyToOne Category category setzen, dann könnte „Artikel löschen“ plötzlich zu „Kategorie löschen“ werden, und dann ... nun ja, dann wird es lustig, aber nur die ersten fünf Minuten, bis Sie die Produktionslogs öffnen.

In unserer Domäne kann man das sehr menschlich formulieren: Ein Anhang ist ein „Teil des Artikels“, eine Kategorie ist ein „Nachschlagewerk für Artikel“. Cascade und orphan removal sind in der Regel für „Teile“ sinnvoll und in der Regel nicht sinnvoll für „Nachschlagewerke“.

4. Methoden addAttachment / removeAttachment als Absicherung

Wenn Sie eine bidirektionale Beziehung haben (Article enthält eine Sammlung von Anhängen, und ArticleAttachment enthält einen Verweis auf den Artikel), gibt es eine unangenehme Kleinigkeit: JPA speichert den Fremdschlüssel auf der owning side. In unserem Fall ist die owning side fast immer ArticleAttachment.article (d. h. @ManyToOne auf der Seite des Anhangs), weil sich dort @JoinColumn befindet und dort physisch der Fremdschlüssel in der Tabelle liegt.

Das führt zu einem klassischen Bug: Ein Entwickler hat einen Anhang zu article.getAttachments().add(attachment) hinzugefügt, aber attachment.setArticle(article) nicht gesetzt. Im Speicher sieht die Liste schön aus, Tests ohne flush/clear „bestehen“ manchmal, aber in der Datenbank wird der Fremdschlüssel nicht gesetzt, und der Datensatz wird entweder nicht eingefügt, oder er wird mit NULL eingefügt (wenn Sie es aus irgendeinem Grund erlaubt haben), oder es kommt zu einer Constraint-Verletzung.

Deshalb erstellen wir in der Praxis fast immer Wrapper-Methoden, die beide Seiten synchronisieren:

public void addAttachment(ArticleAttachment attachment) {
    // Add to the collection (inverse side)
    attachments.add(attachment);

    // Absolutely synchronize the owning side, otherwise the foreign key in the DB won't be set
    attachment.setArticle(this);
}

public void removeAttachment(ArticleAttachment attachment) {
    // Remove from the collection (otherwise orphanRemoval might not work)
    attachments.remove(attachment);

    // Break the owning side – this is the key signal for JPA that the relationship is actually broken
    attachment.setArticle(null);
}

Es sieht aus wie „zwei überflüssige Methoden“, aber das ist genau der Fall, in dem das „Überflüssige“ Stunden von Ermittlungen spart. Und übrigens verbessern diese Methoden die Testbarkeit: Im Test rufen Sie article.addAttachment(...) auf und sind sicher, dass der Fremdschlüssel korrekt ist und orphan removal wie erwartet funktioniert.

Ein weiterer wichtiger Gedanke: orphanRemoval reagiert darauf, dass die untergeordnete Entität nicht mehr Teil der Sammlung ist. Aber bei einer bidirektionalen Beziehung bedeutet „nicht mehr Teil sein“ nicht nur das remove aus der Liste, sondern auch das Aufbrechen der owning side. Deshalb macht ein guter removeAttachment beides, damit JPA kein „widersprüchliches Weltbild“ erhält.

5. Test für cascade: Anhänge ebenfalls in der DB

Jetzt verwandeln wir die Annotation in beobachtbares Verhalten. Wir wollen einen Test, der beweist: Wenn ich einen Article erstelle, dort neue (noch nicht gespeicherte) ArticleAttachment hinzufüge und dann nur den Artikel speichere, sind die Anhänge trotzdem in der Datenbank gelandet. Das ist der Effekt von cascade persist.

Das Skelett eines Tests in @DataJpaTest sieht normalerweise so aus:

import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.beans.factory.annotation.Autowired;

@DataJpaTest
class ArticleAttachmentCascadeDataJpaTest {

    @Autowired TestEntityManager entityManager; // Praktischer EntityManager für Tests (persist/flush/clear)
}

Wir fügen Repositories hinzu, weil wir den DB-Zustand nach clear() bequem überprüfen müssen:

import org.springframework.beans.factory.annotation.Autowired;

@Autowired ArticleRepository articleRepository; // Überprüft Löschen/Vorhandensein von Artikeln bei Bedarf
@Autowired ArticleAttachmentRepository attachmentRepository; // Überprüft, dass Anhänge wirklich in der Tabelle liegen

Und jetzt das eigentliche Szenario. Versuchen Sie, es so „schlank“ wie möglich zu halten: eine Kategorie, ein Artikel, ein Anhang. Je weniger Dekoration, desto leichter zu verstehen, warum der Test fehlgeschlagen ist.

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

@Test
void persistArticle_shouldPersistAttachments_viaCascade() {
    // Kategorie separat speichern: Kaskade auf Nachschlagewerke nehmen wir nicht an
    Category java = entityManager.persist(new Category("java", "Java"));

    // Artikel verweist auf die bereits existierende Kategorie
    Article article = new Article("JPA basics", java);

    // Anhang über die Helper-Methode hinzufügen, damit owning side (attachment.article) sicher gefüllt ist
    article.addAttachment(new ArticleAttachment("guide.pdf"));

    // Nur den übergeordneten persistieren – der Anhang sollte per Kaskade gespeichert werden
    entityManager.persistAndFlush(article);

    // Persistence Context leeren, um anschließend wirklich aus der DB zu lesen, nicht aus dem L1-Cache
    entityManager.clear();

    // Hauptaussage: Der Datensatz des Anhangs ist tatsächlich in der Tabelle gelandet
    assertThat(attachmentRepository.count()).isEqualTo(1L);
}

Was ist hier wichtig zu beachten, speziell als Autor von Tests:

Wir speichern die Kategorie separat, weil wir für die Kategorie keine Kaskade annehmen. Das ist ein normales Modell: Ein Artikel verweist auf eine bereits existierende Kategorie.

Wir fügen den Anhang über addAttachment hinzu, damit die owning side (attachment.article) sicher gefüllt ist.

Wir verwenden persistAndFlush, weil JPA ohne flush den INSERT bis zum Ende der Transaktion verzögern könnte, und Sie würden nicht die Datenbank überprüfen, sondern die Pläne der Datenbank fürs Leben.

Wir machen clear(), damit nachfolgende Prüfungen nicht den Zustand aus dem Persistence Context „ablesen“. Nach clear() wird attachmentRepository.count() definitiv zur Datenbank gehen.

Wenn im Mapping cascade = PERSIST (oder ALL) entfernt wird, erhalten Sie, je nachdem wie das Speichern organisiert ist, entweder eine Ausnahme wegen einer transienten Entität, oder der Anhang wird einfach nicht gespeichert. Und das ist kein „schlechter Test“ – das ist ein Test, der ehrlich zeigt, dass sich das Verhalten geändert hat.

6. Test für orphanRemoval: Datensatz aus der DB verschwunden

Orphan removal – das ist wie eine „harte Reinigung“ der Datenbank: Wenn ein Objekt nicht mehr Teil des Graphen ist, wird es gelöscht. Aber damit das nicht nur Worte bleiben, brauchen wir einen Test: Artikel mit zwei Anhängen erstellen, einen entfernen, flush/clear ausführen und prüfen, dass der Datensatz in der Anhangstabelle verschwunden ist.

Damit der Test kurz bleibt und nicht zu einem 80-zeiligen Thriller wird, ist es praktisch, eine kleine Helper-Methode innerhalb der Testklasse zu haben, die einen Artikel mit Anhängen erstellt. Das ist keine „verborgene Magie“, wenn sie ehrlich und transparent ist.

Hier ist zum Beispiel ein sehr kompakter Helper:

private Article persistArticleWithTwoAttachments(Category category) {
    // Artikel in der angegebenen Kategorie erstellen
    Article article = new Article("A", category);

    // 2 Anhänge hinzufügen, damit beim Entfernen eines davon etwas „verwaist“ werden kann
    article.addAttachment(new ArticleAttachment("first.txt"));
    article.addAttachment(new ArticleAttachment("second.txt"));

    // Speichern und sofort flush, damit die Anhänge garantiert eine ID erhalten
    return entityManager.persistAndFlush(article);
}

Und jetzt der Haupttest. Beachten Sie: Wir müssen die id des zu entfernenden Anhangs vor dem Entfernen nehmen, denn nach dem Entfernen existiert das Objekt im Speicher noch, aber in der Datenbank sollte es bereits verschwunden sein.

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

@Test
void removeAttachment_shouldDeleteRow_viaOrphanRemoval() {
    // Nachschlagewerk (Kategorie) lebt separat, ohne Kaskade
    Category java = entityManager.persist(new Category("java", "Java"));

    // Artikel mit 2 Anhängen erstellen (beide sollten nach persistAndFlush in der DB sein)
    Article article = persistArticleWithTwoAttachments(java);

    // ID vorher merken: Nach orphan removal sollte der Datensatz aus der Tabelle verschwinden
    Long removedId = article.getAttachments().getFirst().getId();

    // Anhang mit der korrekten Methode entfernen, die beide Seiten der Beziehung trennt
    article.removeAttachment(article.getAttachments().getFirst());

    // Persistence Context mit der DB synchronisieren (sonst könnte DELETE verzögert werden)
    entityManager.flush();

    // Kontext leeren, damit findById zur DB geht und nicht das managed-Objekt zurückgibt
    entityManager.clear();

    // Genau die Datenbank prüfen: Zeile mit removedId sollte nicht mehr existieren
    assertThat(attachmentRepository.findById(removedId)).isEmpty();
}

Der Sinn des Tests besteht genau darin, den Effekt auf Datenbankebene zu beweisen. Die Prüfung von article.getAttachments().size() wäre eine Prüfung der Java-Collection, nicht des Persistence-Verhaltens. Ja, es ist wichtig, dass die Collection kleiner geworden ist. Aber für die Qualität der Data-Layer ist es viel wichtiger, dass die Zeile in der Tabelle tatsächlich verschwunden ist.

Wenn orphanRemoval = true entfernt wird, hängt das Ergebnis davon ab, wie genau Sie die Beziehung trennen und was das aktuelle Mapping erlaubt. In unserer Variante macht removeAttachment() ein attachment.setArticle(null), und article_id halten wir als non-nullable, so dass Sie bei flush eher eine Constraint-Verletzung erhalten als eine „still verbleibende“ Zeile. Wenn der FK NULL erlauben würde oder die owning side gar nicht getrennt würde, könnte der Datensatz als verwaist in der DB bleiben. Und genau hier wird der Wert von orphanRemoval besonders deutlich: Er verwandelt ein zweideutiges Schicksal der untergeordneten Zeile in ein vorhersagbares Löschen.

Und noch eine praktische Randbemerkung: orphan removal löscht den Datensatz, aber nicht die Datei aus dem Dateisystem. In ContentHub wird das Aufgabe einer anderen Schicht sein (storage/service), und der Data-Layer-Test beweist nur die Korrektheit der DB-Metadaten. Das ist normal: Ein Test – eine Schicht, sonst erhalten Sie eine Schichttorte aus Zufälligkeiten.

7. Cascade REMOVE beim Löschen eines Artikels

Das Entfernen eines Anhangs aus der Sammlung ist ein Szenario. Ein weiteres lebensnahes Szenario: Der Artikel wird vollständig gelöscht (oder zumindest kann das in manchen Projekten so sein; in ContentHub haben wir häufiger eine Archivierung, aber der Mechanismus der Kaskade ist dennoch wichtig). Wenn ein Artikel gelöscht wird, ist es logisch zu erwarten, dass die Anhänge mitgelöscht werden. Hier ist cascade = REMOVE (oder ALL) nützlich.

Aber gleichzeitig möchte man auf keinen Fall, dass das Löschen eines Artikels die Kategorie löscht. Die Kategorie ist ein Nachschlagewerk. Daher ist es gut, den Test für cascade remove so zu schreiben, dass er beide Seiten prüft: Die Anhänge sind verschwunden, die Kategorie ist geblieben.

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

@Test
void deleteArticle_shouldDeleteAttachments_butNotCategory() {
    // Nachschlagewerk (Kategorie) erstellen (kein Kaskaden-Löschen von der Artikelseite)
    Category java = entityManager.persist(new Category("java", "Java"));

    // Artikel mit Anhängen erstellen
    Article article = persistArticleWithTwoAttachments(java);

    // ID der Kategorie merken, um zu prüfen, dass sie nicht versehentlich gelöscht wurde
    Long categoryId = java.getId();

    // Artikel löschen: Erwartet, dass Anhänge per Kaskade verschwinden, die Kategorie bleibt
    articleRepository.delete(article);

    // DELETE zwangsweise an die DB senden
    entityManager.flush();

    // Cache leeren, damit die folgenden Prüfungen wirklich die Datenbank lesen
    entityManager.clear();

    // Anhänge sollen verschwunden sein
    assertThat(attachmentRepository.count()).isZero();

    // Kategorie soll noch vorhanden sein (sonst wurde irgendwo eine überflüssige Kaskade aktiviert)
    assertThat(entityManager.find(Category.class, categoryId)).isNotNull();
}

Dieser Test ist nützlich als „Versicherung gegen versehentliche Annotationen“. Wenn jemand eine Kaskade auf die Kategorie hinzufügt, könnte der Test plötzlich rot werden, und Sie erkennen die Ursache, bevor das „Kategorie-Nachschlagewerk“ unerwartet in der Produktion verschwindet. Das ist eine sehr angenehme Art von Regression: besser durch einen Test als durch Benutzer abfangen.

8. Nuancen: remove aus der Liste und DB-Zustand

Manchmal scheint es, dass JPA „erraten“ sollte, was Sie wollten. Aber JPA ist kein Gedankenleser. Es schaut ehrlich auf die managed-Objekte und ihren Zustand im Persistence Context und darauf, was genau sich geändert hat. Wenn Sie ein Objekt aus der Sammlung entfernt haben, die owning side aber immer noch auf das übergeordnete Element zeigt, könnte JPA entscheiden, dass die Beziehung nicht getrennt ist. Wenn Sie keinen flush gemacht haben, hat JPA möglicherweise noch gar nichts an die Datenbank gesendet. Wenn Sie keinen clear gemacht haben, könnte Ihr nachfolgender findById das Objekt aus dem L1-Cache zurückgeben.

Aus diesem Grund gibt es bei Data-Tests eine eiserne Disziplin: Sie ändern den Graphen – machen Sie flush(); Sie wollen genau die Datenbank prüfen – machen Sie clear(); Sie prüfen über das Repository oder über entityManager.find nach dem Leeren. Das ist kein „Ritual“, sondern eine Möglichkeit, „glückliche Zufälle“ aus dem Test zu entfernen.

Besonders sollte man die Natur von @DataJpaTest im Kopf behalten: Der Test arbeitet meist innerhalb einer Transaktion und rollt nach Abschluss zurück. Daher ist das Löschen im Test nicht „für immer“ – und das ist gut. Aber flush ist trotzdem obligatorisch, wenn Sie Constraint-Verletzungen und echte DELETE/INSERT innerhalb des aktuellen Tests sehen möchten.

9. Typische Fehler bei cascade und orphanRemoval

Fehler Nr. 1: CascadeType.ALL setzen, „weil es im Beispiel so war“, und sich dann über Nebeneffekte wundern.
ALL umfasst nicht nur PERSIST und REMOVE, sondern auch MERGE und andere Operationen. Im Lehrcode ist das praktisch, aber in einem echten Projekt kann es zu unerwarteten Aktualisierungen untergeordneter Entitäten beim save() des übergeordneten Elements führen. Die richtige Gewohnheit ist, zuerst zu formulieren, welches Verhalten benötigt wird, und dann die minimale Menge an Kaskaden für dieses Verhalten auszuwählen.

Fehler Nr. 2: REMOVE auf Nachschlagewerke und allgemeine Entitäten kaskadieren.
Für die Beziehung Article → Category ist eine Löschkaskade fast immer schädlich. Eine Kategorie „gehört“ nicht zu einem Artikel, sie wird von vielen Artikeln verwendet. Wenn die Kaskade eingeschaltet ist, könnte das Löschen eines Artikels theoretisch die Kategorie löschen (oder versuchen zu löschen und einen Fehler wegen des Fremdschlüssels erhalten). Ein Test, der prüft, dass „die Kategorie geblieben ist“, ist eine hervorragende Absicherung dagegen.

Fehler Nr. 3: Nur die inverse Seite der Beziehung (die Sammlung) ändern, die owning side (Fremdschlüssel) vergessen.
Bei mappedBy = "article" ist die Sammlung Article.attachments eine Spiegelung der Beziehung, aber der Fremdschlüssel wird in ArticleAttachment.article gespeichert. Wenn Sie article.getAttachments().add(a) machen, aber nicht a.setArticle(article), dann wird in der Datenbank entweder keine Beziehung erscheinen, oder es kommt zu einer Constraint-Verletzung. Genau deshalb sind die Methoden addAttachment/removeAttachment keine Marotte, sondern eine Möglichkeit, sich eine Ermittlung zu ersparen, warum „meine Attachments nicht verknüpft wurden“.

Fehler Nr. 4: orphan removal anhand der Größe der Java-Collection prüfen, nicht die Datenbank.
attachments.size() kann 1 sein, weil Sie das Objekt aus der Liste entfernt haben. Aber der Datensatz in der Tabelle könnte noch vorhanden sein. In einem Data-Layer-Test ist der Hauptbeweis eine Abfrage an die Datenbank nach flush() und clear(): z. B. attachmentRepository.findById(id) sollte empty zurückgeben.

Fehler Nr. 5: flush vergessen und erwarten, dass das Löschen „bereits stattgefunden hat“.
JPA ist nicht verpflichtet, sofort SQL für jede Ihrer Aktionen auszuführen. Es sammelt Änderungen und synchronisiert sie mit der Datenbank bei flush oder am Ende der Transaktion. Wenn Sie einen Anhang entfernt haben und sofort das Repository prüfen, kann der Test merkwürdige Ergebnisse liefern, besonders wenn der Persistence Context noch alte Objekte enthält. flush bringt den Moment der Wahrheit etwas näher und macht den Test ehrlich.

1
Aufgabe
Spring Test, Level 17, Lektion 1
Gesperrt
Kaskadierendes Speichern von Artikelanhängen
Kaskadierendes Speichern von Artikelanhängen
1
Aufgabe
Spring Test, Level 17, Lektion 1
Gesperrt
Verwaiste Anhänge entfernen
Verwaiste Anhänge entfernen
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION