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ąć.

bert
źródło

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:

cletus
źródło
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.

Louis Jacomet
źródło
wpis blogu - link jest uszkodzony.
Steffi
1
A magia maszyny powrotnej nas ratuje: web.archive.org/web/20120225040254/http://javablog.co.uk/2009/…
Louis Jacomet
43

Możesz spróbować tego:

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

Maciel Bombonato
źródło
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.

bert
źródło
4

Tutaj kaskada, w kontekście usuwania, oznacza, że ​​dzieci są usuwane, jeśli usuniesz rodzica. Nie stowarzyszenie. Jeśli używasz Hibernacji jako dostawcy JPA, możesz to zrobić za pomocą specyficznej kaskady hibernacji .

Chandra Patni
źródło
4

Możesz spróbować tego:

@OneToOne(cascade = CascadeType.REFRESH) 

lub

@OneToMany(cascade = CascadeType.REFRESH)
Amit Ghorpade
źródło
3
@Entity 
class Employee {
     @OneToOne(orphanRemoval=true)
     private Address address;
}

Zobacz tutaj .

Chang
źródło