JPA: unidirektionales Viele-zu-Eins- und kaskadierendes Löschen


94

Angenommen, ich habe eine unidirektionale @ManyToOne Beziehung wie die folgende:

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

Wenn ich ein Elternteil P und Kinder C 1 ... C n habe, die auf P zurückgreifen, gibt es in JPA eine saubere und hübsche Möglichkeit, die Kinder C 1 ... C n automatisch zu entfernen, wenn P entfernt wird (dh entityManager.remove(P))?

Was ich suche, ist eine Funktionalität ähnlich wie ON DELETE CASCADEin SQL.


1
Selbst wenn nur 'Child' einen Verweis auf 'Parent' hat (auf diese Weise ist die Referenzierung unidirektional), ist es für Sie problematisch, die Liste von 'Child' mit einem '@OneToMany'-Mapping und dem Attribut' Cascade = ALL 'hinzuzufügen das Elternteil'? Ich gehe davon aus, dass JPA das Problem lösen sollte, dass selbst eine harte Seite nur die Referenz enthält.
kvDennis

1
@kvDennis, es gibt Fälle, in denen Sie die Vielseitigkeit nicht eng mit der einen Seite verbinden möchten. ZB in ACL-ähnlichen Setups, in denen Sicherheitsberechtigungen transparent sind "Add-On"
Bachi

Antworten:


73

Beziehungen in JPA sind immer unidirektional, es sei denn, Sie ordnen den Elternteil dem Kind in beide Richtungen zu. Das Kaskadieren von REMOVE-Operationen vom Elternteil zum Kind erfordert eine Beziehung vom Elternteil zum Kind (nicht nur das Gegenteil).

Sie müssen daher Folgendes tun:

  • Ändern Sie entweder die unidirektionale @ManyToOneBeziehung in eine bidirektionale @ManyToOneoder eine unidirektionale @OneToMany. Sie können dann REMOVE-Vorgänge kaskadieren, EntityManager.removeum die übergeordneten und untergeordneten Elemente zu entfernen. Sie können auch orphanRemovaltrue angeben , um verwaiste untergeordnete Elemente zu löschen, wenn die untergeordnete Entität in der übergeordneten Auflistung auf null gesetzt ist, dh das untergeordnete Element zu entfernen, wenn es in der übergeordneten Auflistung nicht vorhanden ist.
  • Oder geben Sie die Fremdschlüsseleinschränkung in der untergeordneten Tabelle als an ON DELETE CASCADE. Sie müssen nach dem Aufruf EntityManager.clear()aufrufen, EntityManager.remove(parent)da der Persistenzkontext aktualisiert werden muss. Die untergeordneten Entitäten dürfen nach dem Löschen in der Datenbank nicht im Persistenzkontext vorhanden sein.

7
Gibt es eine Möglichkeit, No2 mit einer JPA-Annotation auszuführen?
user2573153

3
Wie mache ich No2 mit Hibernate-XML-Zuordnungen?
arg20

92

Wenn Sie den Ruhezustand als JPA-Anbieter verwenden, können Sie die Anmerkung @OnDelete verwenden. Diese Anmerkung fügt der Beziehung den Auslöser ON DELETE CASCADE hinzu , der das Löschen der untergeordneten Elemente an die Datenbank delegiert.

Beispiel:

public class Parent {

        @Id
        private long id;

}


public class Child {

        @Id
        private long id;

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

Mit dieser Lösung reicht eine unidirektionale Beziehung vom Kind zum Elternteil aus, um alle Kinder automatisch zu entfernen. Diese Lösung benötigt keine Listener usw. Auch eine Abfrage wie DELETE FROM Parent WHERE id = 1 entfernt die untergeordneten Elemente .


4
Ich kann es nicht so machen. Gibt es eine bestimmte Version des Ruhezustands oder ein anderes detaillierteres Beispiel wie dieses?
Mardari

3
Es ist schwer zu sagen, warum es bei Ihnen nicht funktioniert. Damit dies funktioniert, müssen Sie möglicherweise das Schema neu generieren oder die Kaskadenlöschung manuell hinzufügen. Die Annotation @OnDelete scheint eine Weile zu existieren, daher würde ich nicht vermuten, dass die Version ein Problem darstellt.
Thomas Hunziker

10
Danke für die Antwort. Kurzer Hinweis: Der Datenbankkaskaden-Trigger wird nur erstellt, wenn Sie die DDL-Generierung über den Ruhezustand aktiviert haben. Andernfalls müssen Sie es auf eine andere Weise hinzufügen (z. B. Liquibase), damit Ad-hoc-Abfragen direkt für die Datenbank ausgeführt werden können, z. B. 'DELETE FROM Parent WHERE id = 1', um die Kaskadenentfernung durchzuführen.
mjj1409

1
Dies funktioniert nicht, wenn der Verein ist. @OneToOneIrgendwelche Ideen, wie man es lösen kann @OneToOne?
Stakowerflol

1
@ThomasHunziker das funktioniert nicht für orphanRemoval oder?
Oxyt

13

Erstellen Sie eine bidirektionale Beziehung wie folgt:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}

8
schlechte Antwort, bidirektionale Beziehungen sind in JPA schrecklich, weil die Operation an großen Kindersets unglaublich viel Zeit in
Anspruch

1
Gibt es Beweise dafür, dass bidirektionale Beziehungen langsam sind?
Shalama

@enerccio Was ist, wenn die bidirektionale Beziehung eins zu eins ist? Zeigen Sie auch einen Artikel, der besagt, dass bidirektionale Beziehungen langsam sind. langsam in was? abrufen? löschen? Aktualisierung?
saran3h

@ saran3h Bei jeder Operation (Hinzufügen, Entfernen) werden alle untergeordneten Elemente geladen, sodass eine enorme Datenlast unbrauchbar sein kann (wie beim Hinzufügen eines Werts müssen nicht alle untergeordneten Elemente aus der Datenbank geladen werden, was genau das ist, was diese Zuordnung bewirkt).
Enerccio

@Enerccio Ich denke, jeder verwendet Lazy Loading für Joins. Wie ist es also immer noch ein Leistungsproblem?
saran3h

1

Ich habe in unidirektionalem @ManytoOne gesehen, Löschen funktioniert nicht wie erwartet. Wenn ein Elternteil gelöscht wird, sollte im Idealfall auch ein Kind gelöscht werden, aber nur das Elternteil wird gelöscht und das Kind wird NICHT gelöscht und als Waise belassen

Die verwendete Technologie ist Spring Boot / Spring Data JPA / Hibernate

Sprint Boot: 2.1.2.RELEASE

Spring Data JPA / Hibernate wird zum Löschen der Zeile .eg verwendet

parentRepository.delete(parent)

ParentRepository erweitert das Standard-CRUD-Repository wie unten gezeigt ParentRepository extends CrudRepository<T, ID>

Im Folgenden sind meine Entitätsklassen aufgeführt

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


}

Ich fand die Lösung, warum das Löschen nicht funktionierte. Anscheinend verwendete der Ruhezustand NICHT die MySQL-Engine -INNODB. Sie benötigen die Engine INNODB, damit MySQL die Fremdschlüsseleinschränkung generiert. Wenn Sie die folgenden Eigenschaften in application.properties verwenden, wird Spring Boot / Hibernate verwendet, um die MySQL-Engine INNODB zu verwenden. So funktioniert die Fremdschlüsseleinschränkung und damit auch die Kaskade
ranjesh

Verpasste Eigenschaften werden in früheren Kommentaren verwendet. Es folgen die Eigenschaften der verwendeten spring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
Federn

Zu Ihrer Information, Sie haben einen falschen "Code. Siehename= "parent"
Alexander

0

Verwenden Sie diese Methode, um nur eine Seite zu löschen

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

-1

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

Die gegebene Anmerkung hat bei mir funktioniert. Kann es versuchen

Zum Beispiel :-

     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;
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.