it-swarm.com.de

JPA: DELETE WHERE löscht keine untergeordneten Objekte und löst eine Ausnahme aus

Ich versuche, dank einer JPQL-Abfrage eine große Anzahl von Zeilen aus MOTHER zu löschen.

Die Mother-Klasse ist wie folgt definiert:

@Entity
@Table(name = "MOTHER")
public class Mother implements Serializable {

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "mother", 
               orphanRemoval = true)
    private List<Child> children;    
}

@Entity
@Table(name = "CHILD")
public class Child  implements Serializable {

    @ManyToOne
    @JoinColumn(name = "MOTHER_ID")
    private Mother mother;    
}

Wie Sie sehen, hat die Mother-Klasse "Kinder" und beim Ausführen der folgenden Abfrage:

String deleteQuery = "DELETE FROM MOTHER WHERE some_condition";
entityManager.createQuery(deleteQuery).executeUpdate();

eine Ausnahme wird ausgelöst:

ERROR - ORA-02292: integrity constraint <constraint name> violated - 
                   child record found

Natürlich könnte ich zuerst alle Objekte auswählen, die ich löschen möchte, und sie in einer Liste abrufen, bevor ich sie durchläuft, um das gesamte abgerufene Objekt zu löschen. Die Leistung einer solchen Lösung wäre jedoch schrecklich!

Gibt es eine Möglichkeit, das vorherige Mapping zu nutzen, um alle Mother-Objekte UND alle ihnen zugeordneten Child-Objekte effizient und ohne vorher die Abfragen für all die Kinder zu schreiben?

24
Jean Logeart

DELETE (und INSERT) kaskadieren nicht über Beziehungen in der JPQL-Abfrage. Dies ist eindeutig in der Spezifikation geschrieben:

Ein Löschvorgang gilt nur für Entitäten der angegebenen Klasse und von seine Unterklassen. Es kaskadiert nicht mit verwandten Entitäten.

Zum Glück persistieren und entfernen über Entity Manager do (wenn Kaskadenattribute definiert sind) 

Was du tun kannst:

  • holen Sie alle Mother-Entity-Instanzen, die entfernt werden sollen.
  • für jeden von ihnen rufen Sie EntityManager.remove () auf.

Code ist so ähnlich:

String selectQuery = "SELECT m FROM Mother m WHERE some_condition";  
List<Mother> mothersToRemove = entityManager.createQuery(selectQuery).getResultList();  
for (Mother m: mothersToRemove) {  
    em.remove(m);
}
31
Mikko Maunu

Haben Sie versucht, session.delete() oder gleichwertige EntityManager.remove () zu verwenden?

Wenn Sie eine HQL-Löschanweisung zum Ausgeben einer Abfrage verwenden, umgehen Sie möglicherweise den Kaskadierungsmechanismus von Hibernate. Schauen Sie sich diese JIRA-Ausgabe an: HHH-368

Möglicherweise können Sie dies erreichen durch: 

Mother mother = session.load(Mother.class, id);
// If it is a lazy association, 
//it might be necessary to load it in order to cascade properly
mother.getChildren(); 
session.delete(mother);

Ich bin mir momentan nicht sicher, ob es notwendig ist, die Sammlung zu initialisieren, damit sie richtig kaskadiert wird.

1
Xavi López

Dies ist verwandt und bietet möglicherweise eine Lösung, wenn Sie Hibernate verwenden.

JPA CascadeType.ALL löscht keine Waisenkinder

BEARBEITEN

Da es sich bei Oracle um den Fehler handelt, können Sie möglicherweise die Oracle-Kaskadenlöschung verwenden, um dieses Problem zu umgehen. Dies kann jedoch zu unvorhersehbaren Ergebnissen führen: Da JPA nicht erkennt, dass Sie andere Datensätze löschen, können diese Objekte im Cache verbleiben und verwendet werden, obwohl sie gelöscht wurden. Dies gilt nur, wenn die von Ihnen verwendete JPA-Implementierung über einen Cache verfügt und für deren Verwendung konfiguriert ist.

Hier finden Sie Informationen zur Verwendung der Kaskadenlöschung in Oracle: http://www.techonthenet.com/Oracle/foreign_keys/foreign_delete.php

0
Sarel Botha

Sie können das RDBMS weiterleiten, um diese Mothers mit einer Fremdschlüsseleinschränkung zu löschen. Dies setzt voraus, dass Sie Ihre DDL aus Entitäten generieren:

@Entity
@Table(name = "CHILD")
public class Child  implements Serializable {

    @ManyToOne
    @JoinColumn(name = "MOTHER_ID", foreignKey = @ForeignKey(foreignKeyDefinition =
        "FOREIGN KEY(MOTHER_ID) REFERENCES MOTHER(ID) ON DELETE CASCADE",
        value = ConstraintMode.CONSTRAINT))
    private Mother mother;    
}
0
rzymek

Ich muss sagen, dass ich nicht sicher bin, ob "delete" in einer Abfrage nicht alle verwandten onetomany-Entitäten in Ihrem Fall entfernt, wie "MikKo Maunu" sagt. Ich würde sagen, es würde ... Das Problem ist (tut mir leid, dass ich es nicht ausprobiert habe), dass JPA/Hibernate nur die "echte SQL-Löschung" ausführen soll und diese Mutter-Kind-Instanzen derzeit nicht verwaltet werden , es hat keine Möglichkeit zu wissen, welche Child-Instanzen zu entfernen sind. OrphanRemoval ist eine große Hilfe, aber in diesem Fall nicht. Ich würde es tun

1) Versuchen Sie, 'fetch = FetchType.EAGER' in die Onetomany-Relation einzufügen (dies kann auch ein Leistungsproblem sein)

2) Wenn 1) nicht funktioniert, führen Sie nicht alle Mutter-/Kind-Abrufe aus, um alles für die JPA-Ebene klar zu machen, und führen Sie einfach eine Abfrage vor der von Ihnen verwendeten aus (in derselben Transaktion, aber ich bin nicht sicher, ob Sie dies nicht benötigen 'em.flush' zwischen ihnen laufen lassen) 

DELETE FROM Child c WHERE c.mother <the condition>

(Löschen ist oft ein Ärgernis bei JPA/Hibernate. Ein Beispiel, das ich benutze, um die Verwendung von ORM, die im Wesentlichen eine zusätzliche Ebene in Apps ist, zu verurteilen, um die Dinge "einfacher" zu machen.) Nur das Gute daran ist, dass ORM Probleme/Fehler gibt werden normalerweise in der Entwicklungsphase entdeckt. Mein Geld ist immer bei MyBatis, was meiner Meinung nach viel sauberer ist.)

UPDATE:

Mikko Maunu hat recht, Massenlöschung in JPQL läuft nicht in Kaskade. Zwei Abfragen zu verwenden, wie ich vorgeschlagen habe, ist in Ordnung.

Eine heikle Sache ist, dass der Persistenzkontext (alle von EntityManager verwalteten Entitäten) nicht mit dem Massenlöschvorgang synchronisiert wird. Daher sollten beide Abfragen in dem von mir vorgeschlagenen Fall in einer separaten Transaktion ausgeführt werden.

UPDATE 2: Bei der Verwendung des manuellen Entfernens anstelle des Massenlöschens stellen viele JPA-Anbieter und Hibernate auch die Methode removeAll (...) oder etwas Ähnliches (keine API) für ihre EntityManager-Implementierungen bereit. Es ist einfacher zu verwenden und kann in Bezug auf die Leistung effektiver sein.

In z. OpenJPA müssen Sie nur Ihre EM in OpenJPAEntityManager umwandeln, am besten mit OpenJPAPersistence.cast (em) .removeAll (...)

0
MarianP