2011-10-19 19 views
22

Estoy tratando de eliminar un gran número de filas de MOTHER gracias a una consulta JPQL.JPA: ELIMINAR DONDE no se elimina a los niños y se lanza una excepción

La clase Mother se define como sigue:

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

Como se puede ver, la clase Mother tiene "hijos" y al ejecutar la siguiente consulta:

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

se produce una excepción:

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

Por supuesto, podría primero se Lectura de todos los objetos que quiero eliminar y recuperarlos en una lista antes de iterar a través de ella para eliminar todo el objeto recuperado, ¡pero el rendimiento de tal solución sería terrible!

Entonces, ¿hay una manera de tomar ventaja de la asignación anterior para eliminar todos los objetos y Mother todos los Child objetos asociados con ellos de manera eficiente y sin necesidad de escribir primero las consultas para todo los niños?

Respuesta

27

ELIMINAR (y INSERTAR) no se conecta en cascada a través de las relaciones en la consulta JPQL. Esto se explica claramente en la especificación:

Una operación de eliminación solo se aplica a las entidades de la clase especificada y sus subclases. No se conecta en cascada a las entidades relacionadas.

Afortunadamente persisten y eliminan a través del administrador de entidades do (cuando hay atributo de cascada definido).

Lo que puede hacer:

  • captar todas las instancias de la entidad madre que deben ser eliminados.
  • para cada uno de ellos llama a EntityManager.remove().

código es algo como esto:

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

Lamentablemente esto anula las ventajas de rendimiento de la mayor borrar y para implementar esta solución en otra cosa que una situación de juguete que se necesita para envolver el SelectQuery en una infraestructura de paginación que borra el entermanager por página para evitar quedarse sin memoria con un gran número de Madres. – NBW

+2

Me estremezco cada vez que veo las llamadas a la base de datos en bucles. ¡Es MUY lento, consume muchos recursos e ineficiente! ¡Siempre hay una mejor manera! :) – PAULUS

+4

En realidad, usualmente EntityManager.remove() todavía no llama a la base de datos, esto ocurre más tarde durante el tiempo de vaciado/confirmación. –

0

¿Ha intentado utilizar session.delete(), o equivalente EntityManager.remove()?

Cuando utiliza una instrucción de eliminación HQL para emitir una consulta, es posible que esté pasando por alto el mecanismo de cascada de Hibernate. Echar un vistazo a este número JIRA: HHH-368

Usted posiblemente ser capaz de lograr esto:

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

No estoy seguro en este momento si es necesario inicializar la colección con el fin de hacerla cascada correctamente

0

esto está relacionado y puede ofrecer una solución si estás usando Hibernate.

JPA CascadeType.ALL does not delete orphans

EDITAR

Desde Oracle es el que da el error que tal vez podría hacer uso de la cascada de Oracle eliminar para evitar esto. Sin embargo, esto podría tener resultados impredecibles: dado que JPA no se da cuenta de que está eliminando otros registros, esos objetos podrían permanecer en la memoria caché y usarse aunque se hayan eliminado. Esto solo se aplica si la implementación de JPA que está utilizando tiene un caché y está configurado para usarlo.

Aquí es información sobre el uso de eliminación en cascada en Oracle: http://www.techonthenet.com/oracle/foreign_keys/foreign_delete.php

+0

La anotación deleteOrphans de Hibernate no ayuda con eso. Probado. – DoubleMalt

0

debo decir que no estoy seguro de si 'eliminar' en una consulta no eliminará todas ellas relacionadas OneToMany entidades en su caso, como dice 'Mikko Maunu' . Yo diría que sí. El problema es (lo siento por no probarlo) que lo que hará JPA/Hibernate es simplemente ejecutar la 'eliminación real de SQL' y mientras esas instancias de Madre e Hijo no se gestionan en ese momento, no tiene forma de saber qué Instancias hijo para eliminar también. orphanRemoval es de gran ayuda, pero no en este caso. Me

1) tratar de añadir 'ir = FetchType.EAGER' en la relación uno a muchos (esto podría ser un problema de rendimiento demasiado)

2) si 1) no funciona, no todos de la madre/Niño buscando para dejar todo claro para la capa JPA, y simplemente ejecutar una consulta antes de la que usa (en la misma transacción, pero no estoy seguro si no necesita ejecutar 'em.flush' entre ellos)

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

(Las eliminaciones son a menudo una molestia con JPA/Hibernate y un ejemplo que uso para denunciar el uso de ORM, que es esencialmente una capa agregada en aplicaciones, para adelgazar gs 'más fácil'. Lo único bueno de esto es que los problemas/errores de ORM generalmente se descubren durante la fase de desarrollo. Mi dinero está siempre en MyBatis que es mucho más limpio en mi opinión)

ACTUALIZACIÓN:.

Mikko Maunu es correcto, mayor de eliminación en cascada JPQL no. Sin embargo, usar dos consultas como sugerí está bien.

Lo complicado es que el contexto de persistencia (todas las entidades administradas por EntityManager) no está sincronizado con lo que hace la eliminación masiva, por lo que (ambas consultas en el caso que sugiero) deben ejecutarse en una transacción separada.

ACTUALIZACIÓN 2: Si se utiliza quitar manual en lugar de borrar mayor, muchos proveedores JPA e Hibernate también proporcionar removeAll (...) método o algo similar (no API) en sus implementaciones EntityManager. Es más fácil de usar y podría ser más efectivo en cuanto a rendimiento.

En, por ejemplo, OpenJPA solo necesita lanzar su EM a OpenJPAEntityManager, mejor por OpenJPAPersistence.cast (em) .removeAll (...)

+0

No tiene que probarlo, ELIMINAR en JPQL no funciona en cascada. Está claramente deletreado en la especificación: "Una operación de eliminación solo se aplica a las entidades de la clase especificada y sus subclases. No se aplica en cascada a las entidades relacionadas". –

+0

tienes razón, editado. Gracias. – MarianP

0

Puede retransmitir en el RDBMS para eliminar esos Mother s utilizando la restricción de clave externa. Esto supone que su generateing el DDL de entidades:

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