JPA OneToMany nie usuwa dziecka


158

Mam problem z prostym @OneToManymapowaniem między jednostką nadrzędną i podrzędną. Wszystko działa dobrze, tylko że rekordy podrzędne nie są usuwane, gdy usuwam je z kolekcji.

Rodzic:

@Entity
public class Parent {
    @Id
    @Column(name = "ID")
    private Long id;

    @OneToMany(cascade = {CascadeType.ALL}, mappedBy = "parent")
    private Set<Child> childs = new HashSet<Child>();

 ...
}

Dziecko:

@Entity
public class Child {
    @Id
    @Column(name = "ID")
    private Long id;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="PARENTID", nullable = false)
    private Parent parent;

  ...
}

Jeśli teraz usunę i dziecko z zestawu childs, nie zostanie ono usunięte z bazy danych. Próbowałem anulować child.parentodniesienie, ale to też nie zadziałało.

Encje są używane w aplikacji internetowej, usuwanie odbywa się jako część żądania Ajax. Nie mam listy usuniętych dzieci po naciśnięciu przycisku zapisywania, więc nie mogę ich niejawnie usunąć.

Odpowiedzi:


253

Zachowanie JPA jest poprawne (czyli zgodnie ze specyfikacją ): obiekty nie są usuwane tylko dlatego, że zostały usunięte z kolekcji OneToMany. Istnieją rozszerzenia specyficzne dla dostawców, które to robią, ale natywny JPA tego nie obsługuje.

Po części dzieje się tak dlatego, że JPA tak naprawdę nie wie, czy powinno usunąć coś usuniętego z kolekcji. W terminologii modelowania obiektów jest to różnica między kompozycją a „agregacją *”.

W kompozycji jednostka potomna nie istnieje bez rodzica. Klasycznym przykładem jest między House i Room. Usuń dom i pokoje.

Agregacja jest luźniejszym rodzajem asocjacji i jest typowa dla kursu i ucznia. Usuń Kurs, a Student nadal istnieje (prawdopodobnie na innych Kursach).

Musisz więc albo użyć rozszerzeń specyficznych dla dostawcy, aby wymusić to zachowanie (jeśli są dostępne), albo jawnie usunąć dziecko ORAZ usunąć je z kolekcji nadrzędnej.

Jestem swiadomy:


dzięki za miłe wyjaśnienie. tak jest, jak się obawiałem. (Zanim zapytałem, przeszukałem / przeczytałem, po prostu chciałem być zapisany). jakoś zaczynam żałować decyzji o bezpośrednim użyciu JPA API i nit Hibernate .. Spróbuję używać wskaźnika Chandra Patni i używam hibernacji typu kaskadowego delete_orphan.
bert

Mam podobne pytanie na ten temat. Czy byłoby miło spojrzeć na ten post tutaj, proszę? stackoverflow.com/questions/4569857/…
Thang Pham

77
W JPA 2.0 możesz teraz użyć opcji orphanRemoval = true
itsadok

2
Świetne wstępne wyjaśnienie i dobra rada dotycząca usunięcia sieroty. Nie miałem pojęcia, że ​​JPA nie uwzględniło tego typu usunięcia. Niuanse między tym, co wiem o Hibernate, a tym, co faktycznie robi JPA, mogą być szalone.
sma

Ładnie wyjaśnione, pokazując różnice między kompozycją a agregacją!
Felipe Leão

73

Oprócz odpowiedzi Cletusa, JPA 2.0 , obowiązujące od grudnia 2010 r., Wprowadza orphanRemovalatrybut do @OneToManyadnotacji. Aby uzyskać więcej informacji, zobacz ten wpis na blogu .

Zauważ, że ponieważ specyfikacja jest stosunkowo nowa, nie wszyscy dostawcy JPA 1 mają ostateczną implementację JPA 2. Na przykład wersja Hibernate 3.5.0-Beta-2 nie obsługuje jeszcze tego atrybutu.


wpis blogu - link jest uszkodzony.
Steffi


43

Możesz spróbować tego:

@OneToOne(orphanRemoval=true) or @OneToMany(orphanRemoval=true).


2
Dzięki. Ale pytanie wraca z WZP 1 razy. Ta opcja nie była dostępna wcześniej niż.
bert

9
ale dobrze wiedzieć, dla tych z nas, którzy szukają rozwiązania tego problemu w czasach JPA2 :)
alex440

20

Jak wyjaśniono, nie jest możliwe zrobienie tego, co chcę z JPA, więc zastosowałem adnotację hibernate.cascade, w której odpowiedni kod w klasie Parent wygląda teraz następująco:

@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, mappedBy = "parent")
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
            org.hibernate.annotations.CascadeType.DELETE,
            org.hibernate.annotations.CascadeType.MERGE,
            org.hibernate.annotations.CascadeType.PERSIST,
            org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
private Set<Child> childs = new HashSet<Child>();

Nie mogłem po prostu użyć „WSZYSTKIE”, ponieważ spowodowałoby to również usunięcie rodzica.



4

Możesz spróbować tego:

@OneToOne(cascade = CascadeType.REFRESH) 

lub

@OneToMany(cascade = CascadeType.REFRESH)

3
@Entity 
class Employee {
     @OneToOne(orphanRemoval=true)
     private Address address;
}

Zobacz tutaj .

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.