Wie unterscheidet sich JPA orphanRemoval = true von der DML-Klausel ON DELETE CASCADE

184

Ich bin etwas verwirrt über das JPA 2.0- orphanRemovalAttribut.

Ich denke, ich kann sehen, dass dies erforderlich ist, wenn ich die DB-Generierungstools meines JPA-Anbieters verwende, um die zugrunde liegende Datenbank-DDL zu erstellen, die eine ON DELETE CASCADEBeziehung zu der jeweiligen Beziehung aufweist.

Wenn die Datenbank jedoch vorhanden ist und bereits eine ON DELETE CASCADEBeziehung besteht, reicht dies nicht aus, um die Löschung entsprechend zu kaskadieren? Was macht der orphanRemovalzusätzlich?

Prost

Markos Fragkakis
quelle

Antworten:

291

orphanRemovalhat nichts damit zu tun ON DELETE CASCADE.

orphanRemovalist eine ganz ORM-spezifische Sache . Es markiert die "untergeordnete" Entität, die entfernt werden soll, wenn nicht mehr auf die "übergeordnete" Entität verwiesen wird, z. B. wenn Sie die untergeordnete Entität aus der entsprechenden Sammlung der übergeordneten Entität entfernen.

ON DELETE CASCADEist eine datenbankspezifische Sache , löscht die "untergeordnete" Zeile in der Datenbank, wenn die "übergeordnete" Zeile gelöscht wird.

axtavt
quelle
3
Bedeutet dies, dass sie die sichere Wirkung haben, aber ein anderes System dafür verantwortlich ist?
Anonymoose
101
Anon, es hat nicht den gleichen Effekt. ON DELETE CASCADE weist die DB an, alle untergeordneten Datensätze zu löschen, wenn der übergeordnete Datensatz gelöscht wird. Wenn ich also die RECHNUNG lösche, lösche ich alle EINZELTEILE auf dieser RECHNUNG. OrphanRemoval teilt dem ORM mit, dass das entfernte Element aus der zugrunde liegenden Datenbank gelöscht werden soll, wenn ich ein Elementobjekt aus der Sammlung von Elementen entferne, die zu einem Rechnungsobjekt gehören (im Speicherbetrieb), und dann die Rechnung "speichere".
GaryKeorkunian
2
Wenn Sie eine unidirektionale Beziehung verwenden, wird die Waise automatisch entfernt, obwohl Sie orphanRemoval nicht auf true setzen
Tim
98

Ein Beispiel genommen Form hier :

Wenn ein EmployeeEntitätsobjekt entfernt wird, wird die Entfernungsoperation auf das referenzierte AddressEntitätsobjekt kaskadiert . In dieser Hinsicht orphanRemoval=trueund cascade=CascadeType.REMOVEsind identisch, und wenn orphanRemoval=trueangegeben, CascadeType.REMOVEist redundant.

Der Unterschied zwischen den beiden Einstellungen besteht in der Reaktion auf das Trennen einer Beziehung. Zum Beispiel, wenn Sie das Adressfeld auf nulloder auf ein anderes AddressObjekt setzen.

  • Wenn orphanRemoval=trueangegeben, wird die getrennte AddressInstanz automatisch entfernt. Dies ist nützlich, um abhängige Objekte (z. B. Address) zu bereinigen, die ohne einen Verweis von einem Eigentümerobjekt (z Employee. B. ) nicht existieren sollten .

  • Wenn nur cascade=CascadeType.REMOVEangegeben wird, wird keine automatische Aktion ausgeführt, da das Trennen einer Beziehung kein Entfernungsvorgang ist.

Um zu vermeiden, dass Referenzen durch das Entfernen verwaister Objekte hängen bleiben, sollte diese Funktion nur für Felder aktiviert werden, die private, nicht gemeinsam genutzte abhängige Objekte enthalten.

Ich hoffe das macht es klarer.

Forhas
quelle
Nachdem ich Ihre Antwort gelesen habe, stelle ich fest, dass der genaue Unterschied zwischen beiden und meinem Problem behoben ist. Ich habe mich beim Löschen der untergeordneten Entitäten aus der Datenbank festgefahren, wenn diese von der definierten Sammlung in der übergeordneten Entität getrennt (entfernt) wurden. Ebenso stellte ich die Frage ' stackoverflow.com/questions/15526440/… '. Füge einfach meinen Kommentar hinzu, um beide Fragen zu verknüpfen.
Narendra Verma
@forhas bitte gehen Sie durch die Frage stackoverflow.com/questions/58185249/…
GokulRaj KN
46

Sobald Sie eine untergeordnete Entität aus der Sammlung entfernen, entfernen Sie diese untergeordnete Entität ebenfalls aus der Datenbank. orphanRemoval bedeutet auch, dass Sie die Eltern nicht wechseln können. Wenn es eine Abteilung mit Mitarbeitern gibt, haben Sie diesen Mitarbeiter beim Löschen / Festschreiben (je nachdem, was zuerst eintritt) versehentlich aus der Datenbank entfernt, sobald Sie diesen Mitarbeiter entfernt haben, um ihn in eine andere Abteilung zu verschieben. Die Moral besteht darin, orphanRemoval auf true zu setzen, solange Sie sicher sind, dass Kinder dieses Elternteils während ihrer gesamten Existenz nicht zu einem anderen Elternteil migrieren. Durch Aktivieren von orphanRemoval wird REMOVE automatisch zur Kaskadenliste hinzugefügt.

Onur
quelle
3
Genau richtig ... wird auch als "private" Eltern-Kind-Beziehung bezeichnet.
HDave
Das heißt, sobald ich department.remove(emp);anrufe, wird dieser Mitarbeiter aus der emp-Tabelle gelöscht, ohne überhaupt commit()
anzurufen
18

Die entsprechende JPA-Zuordnung für die DDL ON DELETE CASCADElautet cascade=CascadeType.REMOVE. Verwaiste Entfernung bedeutet, dass abhängige Entitäten entfernt werden, wenn die Beziehung zu ihrer "übergeordneten" Entität zerstört wird. Zum Beispiel, wenn ein Kind aus einer @OneToManyBeziehung entfernt wird, ohne es explizit im Entitätsmanager zu entfernen.

Heri
quelle
1
cascade=CascadeType.REMOVEist NICHT gleichbedeutend mit ON DELETE CASCADE. Ein wird im Anwendungscode entfernt und wirkt sich nicht auf DDL aus, andere werden in DB ausgeführt. Siehe stackoverflow.com/a/19696859/548473
Grigory Kislin
9

Der Unterschied ist:
- orphanRemoval = true: Die Entität "Child" wird entfernt, wenn nicht mehr darauf verwiesen wird (das übergeordnete Element wird möglicherweise nicht entfernt).
- CascadeType.REMOVE: Die Entität "Child" wird nur entfernt, wenn die Entität "Parent" entfernt wird.

user3572554
quelle
6

Da dies eine sehr häufige Frage ist, habe ich diesen Artikel geschrieben , auf dem diese Antwort basiert.

Übergänge des Entitätsstatus

JPA übersetzt Entitätsstatusübergänge in SQL-Anweisungen wie INSERT, UPDATE oder DELETE.

Übergänge des JPA-Entitätsstatus

Wenn Sie persisteine Entität sind, planen Sie, dass die INSERT-Anweisung ausgeführt wird, wenn sie EntityManagerautomatisch oder manuell gelöscht wird.

Wenn Sie removeeine Entität sind, planen Sie die DELETE-Anweisung, die ausgeführt wird, wenn der Persistenzkontext gelöscht wird.

Übergänge von kaskadierenden Entitätszuständen

Mit JPA können Sie Entitätsstatusübergänge von übergeordneten Entitäten zu untergeordneten Entitäten weitergeben.

Wenn Sie also eine übergeordnete PostEntität haben, die mit der untergeordneten Entität @OneToManyverknüpft istPostComment :

Post- und PostComment-Entitäten

Die commentsSammlung in der PostEntität wird wie folgt zugeordnet:

@OneToMany(
    mappedBy = "post", 
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();

CascadeType.ALL

Das cascadeAttribut weist den JPA-Anbieter an, den Entitätsstatusübergang von der übergeordneten PostEntität an alle PostCommentin der commentsAuflistung enthaltenen Entitäten zu übergeben .

Wenn Sie also die PostEntität entfernen :

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

entityManager.remove(post);

Der JPA-Anbieter entfernt zuerst die PostCommentEntität. Wenn alle untergeordneten Entitäten gelöscht werden, wird auch die PostEntität gelöscht :

DELETE FROM post_comment WHERE id = 1
DELETE FROM post_comment WHERE id = 2

DELETE FROM post WHERE id = 1

Waisenentfernung

Wenn Sie das orphanRemovalAttribut auf festlegen true, plant der JPA-Anbieter einen removeVorgang, wenn die untergeordnete Entität aus der Sammlung entfernt wird.

In unserem Fall also

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

PostComment postComment = post.getComments().get(0);
assertEquals(1L, postComment.getId());

post.getComments().remove(postComment);

Der JPA-Anbieter wird den zugehörigen post_commentDatensatz entfernen, da PostCommentin der commentsSammlung nicht mehr auf die Entität verwiesen wird :

DELETE FROM post_comment WHERE id = 1

AUF KASKADE LÖSCHEN

Das ON DELETE CASCADEist auf FK-Ebene definiert:

ALTER TABLE post_comment 
ADD CONSTRAINT fk_post_comment_post_id 
FOREIGN KEY (post_id) REFERENCES post 
ON DELETE CASCADE;

Wenn Sie dies tun, löschen Sie eine postZeile:

DELETE FROM post WHERE id = 1

Alle zugeordneten post_commentEntitäten werden vom Datenbankmodul automatisch entfernt. Dies kann jedoch eine sehr gefährliche Operation sein, wenn Sie versehentlich eine Stammentität löschen.

Fazit

Der Vorteil von JPA cascadeund orphanRemovalOptionen besteht darin, dass Sie auch von einer optimistischen Sperrung profitieren können , um verlorene Updates zu vermeiden .

Wenn Sie den JPA-Kaskadenmechanismus verwenden, müssen Sie keine DDL-Ebene verwenden. Dies ON DELETE CASCADEkann eine sehr gefährliche Operation sein, wenn Sie eine Stammentität entfernen, die viele untergeordnete Entitäten auf mehreren Ebenen enthält.

Vlad Mihalcea
quelle
Also in Orphan Removal Teil Ihrer Antwort: post.getComments (). Remove (postComment); funktioniert in bidirektionalem OneToMany-Mapping nur aufgrund der Persist-Kaskade. Ohne Kaskadierung und ohne Entfernen auf der ManyToOne-Seite, wie in Ihrem Beispiel, würde das Entfernen der Verbindung zwischen zwei Entitäten in der Datenbank nicht beibehalten?
Aurelije
Die Entfernung von Waisen ist von nicht betroffen CascadeType. Es ist ein komplementärer Mechanismus. Jetzt verwechseln Sie das Entfernen mit dem Fortbestehen. Beim Entfernen von Waisen geht es darum, nicht referenzierte Zuordnungen zu löschen, während beim Fortbestehen neue Entitäten gespeichert werden. Sie müssen den in der Antwort angegebenen Links folgen, um diese Konzepte besser zu verstehen.
Vlad Mihalcea
Ich verstehe eines nicht: Wie wird die Entfernung von Waisen in die bidirektionale Zuordnung einfließen, wenn wir niemals die Verbindung auf der M-Seite entfernen? Ich denke, dass das Entfernen von PostComment aus der Liste von Post, ohne PostComment.post auf null zu setzen, nicht zum Entfernen der Verbindung zwischen diesen beiden Entitäten in DB führt. Aus diesem Grund denke ich, dass die Entfernung von Waisen nicht zum Tragen kommt. In der relationalen Welt ist PostComment keine Waise. Ich werde es testen, wenn ich etwas Freizeit habe.
Aurelije
1
Ich habe diese beiden Beispiele in mein GitHub-Repository für Java-Persistenz mit hoher Leistung aufgenommen, die zeigen, wie alles funktioniert. Sie müssen die untergeordnete Seite nicht synchronisieren, wie Sie es normalerweise tun müssen, um Entitäten direkt zu entfernen. Das Entfernen von Waisen funktioniert jedoch nur, wenn eine Kaskadierung hinzugefügt wird. Dies scheint jedoch eine Einschränkung des Ruhezustands zu sein, keine JPA-Spezifikation.
Vlad Mihalcea
5

@GaryK Antwort ist absolut großartig, ich habe eine Stunde lang nach einer Erklärung suchen orphanRemoval = truevs CascadeType.REMOVEund es hat mir geholfen , zu verstehen.

Zusammenfassend: Funktioniert orphanRemoval = trueidentisch mit CascadeType.REMOVE NUR WENN wir object ( entityManager.delete(object)) löschen und möchten, dass auch die untergeordneten Objekte entfernt werden.

In einer völlig anderen Situation führt das Abrufen von Daten wie List<Child> childs = object.getChilds()und das anschließende Entfernen von child ( entityManager.remove(childs.get(0)) using orphanRemoval=truedazu, dass die entsprechende Entität childs.get(0)aus der Datenbank gelöscht wird.

pzeszko
quelle
1
Sie haben einen Tippfehler in Ihrem zweiten Absatz: Es gibt keine Methode wie entityManager.delete (obj); Es ist entityManager.remove (obj).
JL_SO
3

Das Entfernen von Waisen hat im folgenden Szenario den gleichen Effekt wie ON DELETE CASCADE: - Nehmen wir an, wir haben eine einfache Beziehung zwischen der Schülerentität und einer Leitfadenentität, bei der viele Schüler demselben Leitfaden zugeordnet werden können, und in der Datenbank haben wir eine Fremdschlüsselbeziehung zwischen Student- und Guide-Tabelle, sodass die Student-Tabelle id_guide als FK hat.

    @Entity
    @Table(name = "student", catalog = "helloworld")
    public class Student implements java.io.Serializable {
     @Id
     @GeneratedValue(strategy = IDENTITY)
     @Column(name = "id")
     private Integer id;

    @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
    @JoinColumn(name = "id_guide")
    private Guide guide;

// Die übergeordnete Entität

    @Entity
    @Table(name = "guide", catalog = "helloworld")
    public class Guide implements java.io.Serializable {

/**
 * 
 */
private static final long serialVersionUID = 9017118664546491038L;

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "name", length = 45)
private String name;

@Column(name = "salary", length = 45)
private String salary;


 @OneToMany(mappedBy = "guide", orphanRemoval=true) 
 private Set<Student> students = new  HashSet<Student>(0);

In diesem Szenario ist die Beziehung so, dass die Schülerentität der Eigentümer der Beziehung ist, und als solche müssen wir die Schülerentität speichern, um den gesamten Objektgraphen beizubehalten, z

    Guide guide = new Guide("John", "$1500");
    Student s1 = new Student(guide, "Roy","ECE");
    Student s2 = new Student(guide, "Nick", "ECE");
    em.persist(s1);
    em.persist(s2);

Hier ordnen wir dieselbe Anleitung zwei verschiedenen Schülerobjekten zu. Da CASCADE.PERSIST verwendet wird, wird das Objektdiagramm wie folgt in der Datenbanktabelle gespeichert (in meinem Fall MySQL).

STUDENTentabelle: -

ID Name Dept Id_Guide

1 Roy ECE 1

2 Nick ECE 1

GUIDE Tabelle: -

ID NAME Gehalt

1 John $ 1500

und nun, wenn ich einen der Schüler entfernen möchte, mit

      Student student1 = em.find(Student.class,1);
      em.remove(student1);

und wenn ein Schülerdatensatz entfernt wird, sollte auch der entsprechende Führungsdatensatz entfernt werden. Hier kommt das CASCADE.REMOVE-Attribut in der Schülerentität ins Bild und bewirkt, dass der Schüler mit der Kennung 1 sowie das entsprechende Leitobjekt (Kennung) entfernt werden 1). In diesem Beispiel gibt es jedoch ein weiteres Schülerobjekt, das demselben Leitfadendatensatz zugeordnet ist. Wenn wir das Attribut orphanRemoval = true in der Leitfadenentität nicht verwenden, funktioniert der obige Entfernungscode nicht.

kunal
quelle