JPA: jednokierunkowe wiele do jednego i usuwanie kaskadowe

98

Powiedzmy, że mam relację jednokierunkową, @ManyToOne taką jak ta:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;
}

@Entity
public class Child implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne
    @JoinColumn
    private Parent parent;  
}

Jeśli mam rodzica P i dzieci C 1 ... C n odwołując się z powrotem do P, czy w JPA jest czysty i ładny sposób na automatyczne usuwanie dzieci C 1 ... C n po usunięciu P (tj. entityManager.remove(P))?

To, czego szukam, to funkcjonalność podobna do ON DELETE CASCADESQL.

perp
źródło
1
Nawet jeśli tylko „Dziecko” ma odniesienie do „Nadrzędnego” (w ten sposób odniesienie jest jednokierunkowe), czy dodanie listy „Dziecko” z mapowaniem „@OneToMany” i atrybutem „Cascade = ALL” do rodzic'? Zakładam, że JPA powinno rozwiązać, że nawet trudna tylko 1 strona ma odniesienie.
kvDennis
1
@kvDennis, są przypadki, w których nie chcesz ściśle łączyć wielu stron z jedną stroną. Np. W konfiguracjach podobnych do ACL, w których uprawnienia bezpieczeństwa są przezroczyste „dodatek”
Bachi

Odpowiedzi:

73

Relacje w JPA są zawsze jednokierunkowe, chyba że skojarzysz rodzica z dzieckiem w obu kierunkach. Kaskadowe operacje USUŃ od rodzica do dziecka będą wymagały relacji między rodzicem a dzieckiem (nie tylko odwrotnie).

Dlatego musisz to zrobić:

  • Albo zmień @ManyToOnerelację jednokierunkową na dwukierunkową @ManyToOnelub jednokierunkową @OneToMany. Następnie możesz kaskadowo wykonać operacje USUŃ, aby EntityManager.removeusunąć rodzica i dzieci. Możesz również określić orphanRemovaljako true, aby usunąć osierocone elementy potomne, gdy element podrzędny w kolekcji nadrzędnej ma wartość null, tj. Usunąć dziecko, gdy nie ma go w żadnej kolekcji nadrzędnej.
  • Lub określ ograniczenie klucza obcego w tabeli podrzędnej jako ON DELETE CASCADE. Będziesz musiał wywołać EntityManager.clear()po wywołaniu, EntityManager.remove(parent)ponieważ kontekst trwałości musi zostać odświeżony - jednostki podrzędne nie powinny istnieć w kontekście trwałości po ich usunięciu z bazy danych.
Vineet Reynolds
źródło
7
czy jest sposób na zrobienie No2 z adnotacją JPA?
user2573153
3
Jak zrobić No2 z odwzorowaniami Hibernate xml?
arg20
94

Jeśli używasz hibernacji jako dostawcy JPA, możesz użyć adnotacji @OnDelete. Ta adnotacja doda do relacji wyzwalacz ON DELETE CASCADE , który deleguje usuwanie elementów podrzędnych do bazy danych.

Przykład:

public class Parent {

        @Id
        private long id;

}


public class Child {

        @Id
        private long id;

        @ManyToOne
        @OnDelete(action = OnDeleteAction.CASCADE)
        private Parent parent;
}

Dzięki takiemu rozwiązaniu wystarczy jednokierunkowa relacja między dzieckiem a rodzicem, aby automatycznie usunąć wszystkie dzieci. To rozwiązanie nie wymaga żadnych detektorów itp. Również zapytanie takie jak DELETE FROM Parent WHERE id = 1 usunie elementy podrzędne.

Thomas Hunziker
źródło
4
Nie mogę sprawić, by działało w ten sposób, czy istnieje jakaś konkretna wersja hibernacji lub inny bardziej szczegółowy przykład, taki jak ten?
Mardari
3
Trudno powiedzieć, dlaczego to nie działa. Aby to działało, może być konieczne ponowne wygenerowanie schematu lub ręczne dodanie usuwania kaskadowego. Adnotacja @OnDelete wydaje się być dostępna od jakiegoś czasu, jako taka nie zgadłbym, że wersja jest problemem.
Thomas Hunziker
11
Dziękuję za odpowiedź. Uwaga: wyzwalacz kaskady bazy danych zostanie utworzony tylko wtedy, gdy włączono generowanie DDL przez hibernację. W przeciwnym razie będziesz musiał dodać to w inny sposób (np. Liquibase), aby umożliwić wykonywanie zapytań ad hoc bezpośrednio względem bazy danych, np. „DELETE FROM Parent WHERE id = 1”, wykonujących usuwanie kaskadowe.
mjj1409
1
to nie działa, gdy stowarzyszenie ma @OneToOnejakieś pomysły, jak to rozwiązać @OneToOne?
stakowerflol
1
@ThomasHunziker to nie będzie działać w przypadku sierotyRemoval, prawda?
oxyt
14

Utwórz relację dwukierunkową, taką jak ta:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}
tekumara
źródło
9
zła odpowiedź, dwukierunkowe relacje są okropne w JPA, ponieważ operowanie na dużych zestawach dziecięcych zajmuje niesamowitą ilość czasu
Enerccio
1
Czy istnieje dowód na to, że relacje dwukierunkowe są powolne?
shalama
@enerccio A co, jeśli relacja dwukierunkowa to jeden do jednego? Pokaż także artykuł, w którym stwierdza się, że relacje dwukierunkowe są powolne? powolny w czym? aport? usuwanie? aktualizowanie?
saran3h
@ saran3h każda operacja (dodawanie, usuwanie) ładuje wszystkie elementy podrzędne, więc jest to ogromne ładowanie danych, które może być bezużyteczne (np. dodawanie wartości nie wymaga ładowania wszystkich elementów potomnych z bazy danych, co jest dokładnie tym, co robi to mapowanie).
Enerccio
@Enerccio Myślę, że wszyscy używają leniwego ładowania na złączach. Więc dlaczego nadal jest to problem z wydajnością?
saran3h
1

Widziałem w jednokierunkowym @ManytoOne, usuwanie nie działa zgodnie z oczekiwaniami. Gdy usuwany jest rodzic, najlepiej byłoby również usunąć dziecko, ale tylko rodzic jest usuwany, a dziecko NIE jest usuwane i pozostaje osierocone

Zastosowane technologie to Spring Boot / Spring Data JPA / Hibernate

Sprint Boot: 2.1.2.RELEASE

Spring Data JPA / Hibernate służy do usuwania wiersza .eg

parentRepository.delete(parent)

ParentRepository rozszerza standardowe repozytorium CRUD, jak pokazano poniżej ParentRepository extends CrudRepository<T, ID>

Oto moja klasa encji

@Entity(name = “child”)
public class Child  {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne( fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = “parent_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Parent parent;
}

@Entity(name = “parent”)
public class Parent {

    @Id
    @GeneratedValue
    private long id;

    @Column(nullable = false, length = 50)
    private String firstName;


}
ranjesh
źródło
Znalazłem rozwiązanie, dlaczego usuwanie nie działa. najwyraźniej hibernacja NIE korzystała z silnika mysql -INNODB, potrzebujesz silnika INNODB dla mysql, aby wygenerować ograniczenie klucza obcego. Użycie poniższych właściwości w application.properties powoduje, że podczas rozruchu wiosennego / hibernacji używany jest silnik mysql INNODB. Tak więc ograniczenie klucza obcego działa, a zatem również usuwa kaskadę
ranjesh
Brakujące właściwości użyte we wcześniejszym komentarzu. Poniżej przedstawiono zastosowane właściwości sprężynyspring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
ranjesh
FYI, masz zły "kod. Zobaczname= "parent"
aleksander
0

W ten sposób usuń tylko jedną stronę

    @ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
//  @JoinColumn(name = "qid")
    @JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = @ForeignKey(name = "qid"), nullable = false)
    // @JsonIgnore
    @JsonBackReference
    private QueueGroup queueGroup;
Shubham
źródło
-1

@Cascade (org.hibernate.annotations.CascadeType.DELETE_ORPHAN)

Podana adnotacja zadziałała dla mnie. Można spróbować

Na przykład :-

     public class Parent{
            @Id
            @GeneratedValue(strategy=GenerationType.AUTO)
            @Column(name="cct_id")
            private Integer cct_id;
            @OneToMany(cascade=CascadeType.REMOVE, fetch=FetchType.EAGER,mappedBy="clinicalCareTeam", orphanRemoval=true)
            @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
            private List<Child> childs;
        }
            public class Child{
            @ManyToOne(fetch=FetchType.EAGER)
            @JoinColumn(name="cct_id")
            private Parent parent;
    }
Swarit Agarwal
źródło
-1

Nie musisz używać asocjacji dwukierunkowej zamiast swojego kodu, wystarczy dodać CascaType.Remove jako właściwość do adnotacji ManyToOne, a następnie użyć @OnDelete (action = OnDeleteAction.CASCADE), to działa dobrze dla mnie.

Chedy
źródło