web-dev-qa-db-fra.com

JPA: DELETE WHERE ne supprime pas les enfants et lève une exception

J'essaie de supprimer un grand nombre de lignes de MOTHER grâce à une requête JPQL.

La classe Mother est définie comme suit:

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

Comme vous pouvez le constater, la classe Mother a "enfants" et lors de l'exécution de la requête suivante:

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

une exception est levée:

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

Bien sûr, je pouvais d'abord sélectionner tous les objets que je voulais supprimer et les récupérer dans une liste avant de parcourir cette liste pour supprimer tous les objets récupérés, mais les performances d'une telle solution seraient terribles!

Existe-t-il donc un moyen de tirer parti du mappage précédent pour supprimer efficacement tous les objets Mother ET tous les objets Child qui leur sont associés, sans écrire préalablement les requêtes pour tous les enfants?

24
Jean Logeart

DELETE (et INSERT) ne sont pas mis en cascade via des relations dans une requête JPQL. Ceci est clairement écrit dans la spécification:

Une opération de suppression ne s'applique qu'aux entités de la classe spécifiée et à ses sous-classes. Il ne cascade pas aux entités liées.

Heureusement, persistez et la suppression via le gestionnaire d’entités s’effectue (lorsqu’un attribut en cascade est défini). 

Ce que tu peux faire:

  • récupère toutes les instances d'entités mères à supprimer.
  • pour chacun d’eux, appelez EntityManager.remove ().

Le code ressemble à ceci:

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

Avez-vous essayé d'utiliser session.delete() ou un équivalent EntityManager.remove () ?

Lorsque vous utilisez une instruction delete HQL pour émettre une requête, vous pouvez éventuellement contourner le mécanisme en cascade d'Hibernate. Jetez un coup d'œil à ce numéro de JIRA: HHH-368

Vous pourrez éventuellement y arriver en: 

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

Je ne suis pas sûr pour le moment s'il est nécessaire d'initialiser la collection pour la faire passer en cascade correctement.

1
Xavi López

Ceci est lié et peut offrir une solution si vous utilisez Hibernate.

JPA CascadeType.ALL ne supprime pas les orphelins

MODIFIER

Puisque c'est Oracle qui vous donne l’erreur, vous pouvez peut-être utiliser la suppression en cascade d’Oracle pour résoudre ce problème. Cependant, cela pourrait avoir des résultats imprévisibles: puisque JPA ne réalise pas que vous supprimez d'autres enregistrements, ces objets peuvent rester dans le cache et être utilisés même s'ils ont été supprimés. Cela ne s'applique que si l'implémentation de JPA que vous utilisez possède un cache et est configurée pour l'utiliser.

Voici des informations sur l’utilisation de la suppression en cascade dans Oracle: http://www.techonthenet.com/Oracle/foreign_keys/foreign_delete.php

0
Sarel Botha

Vous pouvez utiliser le SGBDR pour supprimer ces Mothers en utilisant la contrainte de clé étrangère . Cela suppose que vous générez votre DDL à partir d'entités:

@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

Je dois dire que je ne suis pas sûr que "supprimer" dans une requête ne supprime pas toutes les entités associées de votre cas, comme le dit "MikKo Maunu". Je dirais que ce serait ... Le problème est (désolé de ne pas l'avoir essayé) que JPA/Hibernate va simplement exécuter la "suppression SQL réelle" et, bien que ces instances Mère et Enfant ne soient pas gérées à ce moment-là , il n’ya aucun moyen de savoir quelles instances de Child supprimer aussi . orphanRemoval est d’une grande aide, mais pas dans ce cas .

1) essayez d’ajouter 'fetch = FetchType.EAGER' dans la relation onetomany (cela pourrait aussi être un problème de performances)

2) si 1) ne fonctionne pas, ne faites pas tous les extractions mère/enfant pour rendre tout ce qui est clair pour la couche JPA, et lancez simplement une requête avant celle que vous utilisez (dans la même transaction, mais je ne suis pas sûr si vous n'en avez pas besoin exécuter 'em.flush' entre eux) 

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

(Les suppressions sont souvent une nuisance pour JPA/Hibernate et un exemple que j’utilise pour dénoncer l’utilisation de ORM, qui est essentiellement une couche ajoutée dans les applications, afin de faciliter les choses. Le seul point positif, c’est que les problèmes/bogues liés à ORM sont généralement découverts au cours de la phase de développement. Mon argent est toujours sur MyBatis qui est beaucoup plus propre à mon avis.)

METTRE À JOUR:

Mikko Maunu a raison, la suppression en bloc dans JPQL ne cascade pas. Utiliser deux requêtes comme je le suggère est bien.

Ce qui est délicat, c'est que le contexte de persistance (toutes les entités gérées par EntityManager) n'est pas synchronisé avec ce que supprime en bloc, de sorte qu'il doit (les deux requêtes dans le cas que je suggère) être exécuté dans une transaction séparée.

UPDATE 2: .__ Si vous utilisez la suppression manuelle au lieu de la suppression en bloc, de nombreux fournisseurs JPA et Hibernate fournissent également une méthode removeAll (...) ou quelque chose de similaire (non-API) sur leurs implémentations EntityManager. Il est plus simple à utiliser et pourrait être plus efficace en ce qui concerne les performances.

Dans par exemple OpenJPA vous devez seulement transtyper votre ME à OpenJPAEntityManager, de préférence par OpenJPAPersistence.cast (em) .removeAll (...)

0
MarianP