W jaki sposób JPA orphanRemoval = true różni się od klauzuli ON DELETE CASCADE DML


184

Jestem trochę zdezorientowany co do orphanRemovalatrybutu JPA 2.0 .

Myślę, że widzę, że jest to potrzebne, gdy używam narzędzi do generowania DB mojego dostawcy JPA do stworzenia bazowego DDL bazy danych, aby mieć ON DELETE CASCADEkonkretną relację.

Jeśli jednak DB istnieje i ma już ON DELETE CASCADErelację, czy to nie wystarczy, aby odpowiednio skasować usunięcie? Co robi orphanRemovaldodatkowo?

Twoje zdrowie

Odpowiedzi:


292

orphanRemovalnie ma z tym nic wspólnego ON DELETE CASCADE.

orphanRemovaljest całkowicie specyficzne dla ORM . Oznacza encję „potomną”, która ma zostać usunięta, gdy nie jest już przywoływana z encji „macierzystej”, np. Gdy usuwa się encję potomną z odpowiedniego zbioru encji macierzystej.

ON DELETE CASCADEjest specyficzna dla bazy danych , usuwa wiersz „podrzędny” w bazie danych po usunięciu wiersza „nadrzędnego”.


3
Czy to oznacza, że ​​mają one bezpieczny efekt, ale odpowiedzialny jest za to inny system?
Anonimowy

101
Anon, nie ma tego samego efektu. PRZY USUWANIU KASKAD mówi DB, aby usunąć wszystkie rekordy podrzędne, gdy element nadrzędny zostanie usunięty. To znaczy, jeśli usunę FAKTURĘ, a następnie usuń wszystkie POZYCJE na tej FAKTURZE. OrphanRemoval mówi ORM, że jeśli usunę obiekt Item z kolekcji Przedmiotów należących do obiektu Faktury (w operacji pamięci), a następnie „zapisz” Fakturę, usunięty Element powinien zostać usunięty z bazowej DB.
garyKeorkunian

2
Jeśli użyjesz relacji jednokierunkowej, sierota zostanie usunięta automatycznie, nawet jeśli nie ustawisz sierotyRemoval = true
Tim

98

Przykład wzięty tutaj :

Kiedy Employeeobiekt encji jest usuwany, operacja usuwania jest kaskadowana z referencyjnym Addressobiektem encji. Pod tym względem orphanRemoval=truei cascade=CascadeType.REMOVEsą identyczne, a jeśli orphanRemoval=truejest określony, CascadeType.REMOVEjest zbędny.

Różnica między tymi dwoma ustawieniami polega na reakcji na rozłączenie relacji. Na przykład, na przykład podczas ustawiania pola adresu na nulllub na inny Addressobiekt.

  • Jeśli orphanRemoval=true jest określony, odłączone Addresswystąpienie jest automatycznie usuwane. Jest to przydatne do czyszczenia zależnych obiektów (np. Address), Które nie powinny istnieć bez odniesienia do obiektu właściciela (np Employee.).

  • Jeśli tylko cascade=CascadeType.REMOVEjest określony, nie są podejmowane żadne automatyczne działania, ponieważ rozłączenie relacji nie jest operacją usuwania.

Aby uniknąć wiszących odniesień w wyniku usunięcia osieroconych, ta funkcja powinna być włączona tylko dla pól, które przechowują prywatne niepodzielne obiekty zależne.

Mam nadzieję, że to wyjaśni.


Po przeczytaniu Twojej odpowiedzi zdaję sobie sprawę, że dokładna różnica między nimi obojgami i mój problem został rozwiązany. Utknąłem w usuwaniu encji podrzędnych z bazy danych, jeśli są one odłączone (usunięte) od zdefiniowanej kolekcji w encji nadrzędnej. Dla tego samego zadałem pytanie „ stackoverflow.com/questions/15526440/… ”. Wystarczy dodać mój komentarz, aby połączyć oba pytania.
Narendra Verma

@forhas proszę przejść przez pytanie stackoverflow.com/questions/58185249/…
GokulRaj KN

46

W chwili usunięcia encji podrzędnej z kolekcji usuniesz również encję podrzędną z bazy danych. orphanRemoval oznacza również, że nie możesz zmienić rodziców; jeśli istnieje dział, który ma pracowników, po usunięciu tego pracownika, aby umieścić go w innym oddziale, przypadkowo usuniesz tego pracownika z bazy danych przy opróżnianiu / zatwierdzaniu (w zależności od tego, co nastąpi wcześniej). Morale polega na ustawieniu sierociRemovala na wartość prawdy, o ile masz pewność, że dzieci tego rodzica nie będą migrować do innego rodzica przez cały czas ich istnienia. Włączenie sierocińcaRemoval automatycznie dodaje również USUŃ do listy kaskad.


3
Dokładnie poprawne ... zwane także „prywatnymi” relacjami rodzic / dziecko.
HDave

Oznacza to, że jak tylko zadzwonię department.remove(emp);, pracownik zostanie usunięty z tabeli emp nawet bez połączeniacommit()
JavaTechnical

18

Równoważne mapowanie JPA dla DDL ON DELETE CASCADEto cascade=CascadeType.REMOVE. Usunięcie sieroty oznacza, że ​​jednostki zależne są usuwane, gdy związek z ich „macierzystą” jednostką zostaje zniszczony. Na przykład, jeśli dziecko jest usuwane z @OneToManyrelacji bez jawnego usuwania go w menedżerze encji.


1
cascade=CascadeType.REMOVENIE jest równoważne z ON DELETE CASCADE. On do remove w kodzie aplikacji i nie wpływa na DDL, inne wykonywane w DB. Zobacz stackoverflow.com/a/19696859/548473
Grigory Kislin

9

Różnica polega na:
- orphanRemoval = true: „Podrzędny” byt jest usuwany, gdy nie jest już przywoływany (jego rodzic nie może zostać usunięty).
- CascadeType.REMOVE: Podmiot „podrzędny” jest usuwany tylko po usunięciu jego „nadrzędny”.


6

Ponieważ jest to bardzo częste pytanie, napisałem ten artykuł , na którym opiera się ta odpowiedź.

Przejścia stanu encji

JPA tłumaczy przejścia stanu encji na instrukcje SQL, takie jak INSERT, UPDATE lub DELETE.

Przejścia stanu encji JPA

Kiedy jesteś persistbytem, ​​planujesz wykonanie instrukcji INSERT po jej EntityManageropróżnieniu, automatycznie lub ręcznie.

gdy jesteś removebytem, ​​planujesz instrukcję DELETE, która zostanie wykonana po opróżnieniu kontekstu trwałości.

Kaskadowe przejścia stanu encji

Dla wygody JPA pozwala propagować przejścia stanu encji z encji nadrzędnych na encje podrzędne.

Jeśli więc masz Postjednostkę nadrzędną , która jest @OneToManypowiązana z PostCommentjednostką podrzędną:

Posty i podmioty PostComment

commentsKolekcja w Postjednostce są odwzorowywane w sposób następujący:

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

CascadeType.ALL

Ten cascadeatrybut informuje dostawcę JPA, aby przekazał przejście stanu encji z Postencji nadrzędnej do wszystkich PostCommentencji zawartych w plikucomments kolekcji.

Jeśli więc usuniesz Postjednostkę:

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

entityManager.remove(post);

Dostawca JPA PostCommentnajpierw usunie encję, a kiedy wszystkie encje podrzędne zostaną usunięte, usunie również Postencję:

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

DELETE FROM post WHERE id = 1

Usunięcie sieroty

Po ustawieniu orphanRemovalatrybutu truedostawca JPA planuje removeoperację, gdy jednostka potomna zostanie usunięta z kolekcji.

W naszym przypadku

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);

Dostawca JPA usunie powiązany post_commentrekord, ponieważ PostCommentjednostka nie jest już przywoływana w commentskolekcji:

DELETE FROM post_comment WHERE id = 1

PRZY USUNIĘCIU KASKADA

ON DELETE CASCADEJest określona na poziomie FK:

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

Gdy to zrobisz, jeśli usuniesz postwiersz:

DELETE FROM post WHERE id = 1

Wszystkie powiązane post_commentjednostki są automatycznie usuwane przez silnik bazy danych. Może to być jednak bardzo niebezpieczna operacja, jeśli przez pomyłkę usuniesz element główny.

Wniosek

Zaletą JPA cascadei orphanRemovalopcji jest to, że możesz również skorzystać z optymistycznego blokowania, aby zapobiec utracie aktualizacji .

Jeśli korzystasz z mechanizmu kaskadowego JPA, nie musisz używać poziomu DDL ON DELETE CASCADE, co może być bardzo niebezpieczną operacją, jeśli usuniesz element główny, który ma wiele elementów potomnych na wielu poziomach.

Więcej informacji na ten temat można znaleźć w tym artykule .


W części dotyczącej usuwania sierocych odpowiedzi: post.getComments (). Remove (postComment); będzie działać w mapowaniu dwukierunkowym OneToMany tylko ze względu na kaskadę Persist. Bez kaskadowania i braku usuwania po stronie ManyToOne, jak w twoim przykładzie, usunięcie połączenia między 2 jednostkami nie byłoby utrzymywane w DB?
aurelije

Nie ma to wpływu na usunięcie sieroty CascadeType. To mechanizm uzupełniający. Teraz mylicie usuwanie z utrzymywaniem się. Usuwanie Sierot polega na usuwaniu niepowiązanych powiązań, a utrwalanie polega na zapisywaniu nowych encji. Aby uzyskać lepsze zrozumienie tych pojęć, należy skorzystać z łączy podanych w odpowiedzi.
Vlad Mihalcea

Nie rozumiem jednej rzeczy: jak rozpocznie się usuwanie sierot w mapowaniu dwukierunkowym, jeśli nigdy nie usuniemy połączenia po stronie M. Myślę, że usunięcie PostComment z listy Post bez ustawienia PostComment.post na null nie spowoduje usunięcia połączenia między tymi 2 jednostkami w DB. Dlatego myślę, że usuwanie sierot nie rozpocznie się, w relacyjnym świecie PostComment nie jest sierotą. Przetestuję to, kiedy będę miał trochę wolnego czasu.
aurelije

1
Dodałem te dwa przykłady do mojego repozytorium GitHub o wysokiej wydajności Java Persistence, które pokazują, jak to wszystko działa. Nie musisz synchronizować strony podrzędnej, jak zwykle musisz zrobić, aby bezpośrednio usunąć jednostki. Jednak usuwanie sierocych działa tylko wtedy, gdy dodano kaskadę, ale wydaje się, że jest to ograniczenie Hibernacji, a nie specyfikacja JPA.
Vlad Mihalcea

5

@GaryK odpowiedź jest absolutnie świetna, spędziłem godzinę szukając wyjaśnienia orphanRemoval = truevs CascadeType.REMOVEi pomogło mi to zrozumieć.

Podsumowując: orphanRemoval = truedziała identycznie jak CascadeType.REMOVE TYLKO JEŚLIentityManager.delete(object) usuwamy obiekt ( ) i chcemy również usunąć obiekty potomne.

W zupełnie innej sytuacji, gdy pobieramy niektóre dane, takie jak, List<Child> childs = object.getChilds()a następnie usuwamy child ( entityManager.remove(childs.get(0)), użycie orphanRemoval=truespowoduje, że odpowiadająca mu encja childs.get(0)zostanie usunięta z bazy danych.


1
W drugim akapicie masz literówkę: Nie ma takiej metody jak entityManager.delete (obj); jest podmiotManager.remove (obj).
JL_SO,

3

usuwanie osieroconych ma taki sam efekt jak w przypadku USUŃ KASKADĘ w następującym scenariuszu: - Załóżmy, że mamy prostą relację wiele do jednego między jednostką studencką a jednostką prowadzącą, w której wielu studentów można zmapować na ten sam przewodnik, aw bazie danych mamy relacja klucza obcego między tabelą Studenta a Przewodnikiem, dzięki czemu tabela studenta ma identyfikator id_guide jako FK.

    @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;

// Jednostka nadrzędna

    @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);

W tym scenariuszu relacja polega na tym, że jednostka studencka jest właścicielem relacji i jako taka musimy zapisać jednostkę studencką, aby zachować cały wykres obiektu, np.

    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);

Tutaj mapujemy ten sam przewodnik z dwoma różnymi obiektami studenckimi, a ponieważ używany jest CASCADE.PERSIST, wykres obiektów zostanie zapisany jak poniżej w tabeli bazy danych (w moim przypadku MySql)

Stół STUDENT: -

ID Nazwa Dział Id_Guide

1 Roy ECE 1

2 Nick ECE 1

Tabela GUIDE: -

NAZWA ID Wynagrodzenie

1 John 1500 USD

a teraz, jeśli chcę usunąć jednego z uczniów, używając

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

a po usunięciu rekordu studenta należy również usunąć odpowiedni rekord przewodnika, to tutaj pojawia się atrybut CASCADE.REMOVE w jednostce Studenta i to, co robi, usuwa ucznia o identyfikatorze 1 oraz odpowiedni obiekt przewodnika (identyfikator 1). Ale w tym przykładzie jest jeszcze jeden obiekt ucznia, który jest odwzorowany na ten sam rekord przewodnika i jeśli nie użyjemy atrybutu sierotaRemoval = true w elemencie przewodnika, powyższy kod usuwania nie będzie działał.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.