JPA: eliminación unidireccional de varios a uno y en cascada

95

Di que tengo un unidireccional @ManyToOne relación como la siguiente:

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

Si tengo un padre P e hijos C 1 ... C n haciendo referencia a P, ¿hay una forma limpia y bonita en JPA de eliminar automáticamente a los hijos C 1 ... C n cuando se elimina P (es decir entityManager.remove(P))?

Lo que estoy buscando es una funcionalidad similar a ON DELETE CASCADESQL.

perpetrador
fuente
1
Incluso si solo 'Niño' tiene una referencia a 'Padre' (de esa manera la referencia es unidireccional), ¿es problemático para usted agregar la Lista de 'Niño' con un mapeo '@OneToMany' y el atributo 'Cascade = ALL' a el padre'? Supongo que JPA debería resolver que incluso aunque solo 1 lado tiene la referencia.
kvDennis
1
@kvDennis, hay casos en los que no desea acoplar estrechamente el lado múltiple a un lado. Por ejemplo, en configuraciones similares a ACL donde los permisos de seguridad son un "complemento" transparente
Bachi

Respuestas:

73

Las relaciones en JPA son siempre unidireccionales, a menos que asocie al padre con el hijo en ambas direcciones. Las operaciones REMOVE en cascada del padre al hijo requerirán una relación del padre al hijo (no solo lo contrario).

Por lo tanto, deberá hacer esto:

  • O cambie la @ManyToOnerelación unidireccional a bidireccional @ManyToOneo unidireccional @OneToMany. A continuación, puede realizar operaciones REMOVE en cascada para EntityManager.removeeliminar el padre y los hijos. También puede especificar orphanRemovalcomo verdadero, para eliminar cualquier hijo huérfano cuando la entidad hijo en la colección principal se establece en nula, es decir, eliminar el hijo cuando no está presente en la colección principal.
  • O especifique la restricción de clave externa en la tabla secundaria como ON DELETE CASCADE. Deberá invocar EntityManager.clear()después de llamar, EntityManager.remove(parent)ya que el contexto de persistencia debe actualizarse; se supone que las entidades secundarias no deben existir en el contexto de persistencia después de que se hayan eliminado en la base de datos.
Vineet Reynolds
fuente
7
¿Hay alguna manera de hacer No2 con una anotación JPA?
user2573153
3
¿Cómo hago No2 con asignaciones xml de Hibernate?
arg20
92

Si está usando hibernate como su proveedor de JPA, puede usar la anotación @OnDelete. Esta anotación agregará a la relación el disparador ON DELETE CASCADE , que delega la eliminación de los hijos a la base de datos.

Ejemplo:

public class Parent {

        @Id
        private long id;

}


public class Child {

        @Id
        private long id;

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

Con esta solución, una relación unidireccional del niño al padre es suficiente para eliminar automáticamente a todos los niños. Esta solución no necesita oyentes, etc. Además, una consulta como DELETE FROM Parent WHERE id = 1 eliminará los hijos.

Thomas Hunziker
fuente
4
No puedo hacer que funcione de esta manera, ¿hay alguna versión específica de hibernación u otro ejemplo más detallado como este?
Mardari
3
Es difícil decir por qué no le está funcionando. Para que esto funcione, es posible que deba volver a generar el esquema o agregar la eliminación en cascada manualmente. La anotación @OnDelete parece haber existido por un tiempo, como tal, no adivinaría que la versión sea un problema.
Thomas Hunziker
10
Gracias por la respuesta. Nota rápida: el disparador en cascada de la base de datos solo se creará si ha habilitado la generación de DDL a través de hibernación. De lo contrario, tendrá que agregarlo de otra manera (por ejemplo, liquibase) para permitir que las consultas ad hoc se ejecuten directamente contra la base de datos, como 'ELIMINAR DEL Padre DONDE id = 1' realizar la eliminación en cascada.
mjj1409
1
esto no funciona cuando la asociación está ¿ @OneToOneAlguna idea de cómo solucionarlo @OneToOne?
stakowerflol
1
@ThomasHunziker esto no funcionará para orphanRemoval, ¿verdad?
oxyt
13

Crea una relación bidireccional, como esta:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}
tekumara
fuente
8
mala respuesta, las relaciones bidireccionales son terribles en JPA porque operar en conjuntos de niños grandes lleva una cantidad increíble de tiempo
Enerccio
1
¿Hay pruebas de que las relaciones bidireccionales sean lentas?
shalama
@enerccio ¿Qué pasa si la relación bidireccional es de uno a uno? Además, muestre un artículo que indique que las relaciones bidireccionales son lentas. lento en que? recuperando? borrar? actualizacion?
saran3h
@ saran3h cada operación (agregar, eliminar) cargará todos los niños, por lo que es una carga de datos enorme que puede ser inútil (como agregar un valor no requiere cargar todos los niños de la base de datos, que es exactamente lo que hace este mapeo).
Enerccio
@Enerccio Creo que todos usan la carga diferida en las uniones. Entonces, ¿cómo sigue siendo un problema de rendimiento?
saran3h
1

He visto en @ManytoOne unidireccional, eliminar no funciona como se esperaba. Cuando se elimina el padre, lo ideal es que el niño también se elimine, pero solo se elimina el padre y el niño NO se elimina y se deja como huérfano

Las tecnologías utilizadas son Spring Boot / Spring Data JPA / Hibernate

Arranque de Sprint: 2.1.2.RELEASE

Spring Data JPA / Hibernate se usa para eliminar la fila .eg

parentRepository.delete(parent)

ParentRepository extiende el repositorio CRUD estándar como se muestra a continuación ParentRepository extends CrudRepository<T, ID>

Los siguientes son mi clase de entidad

@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
fuente
Encontré la solución a la razón por la que eliminar no funcionaba. aparentemente hibernate NO estaba usando mysql Engine -INNODB, necesita el motor INNODB para que mysql genere una restricción de clave externa. Usando las siguientes propiedades en application.properties, hace que Spring Boot / Hibernate use el motor mysql INNODB. Entonces, la restricción de clave externa funciona y, por lo tanto, también elimina la cascada
ranjesh
Las propiedades perdidas se utilizan en el comentario anterior. A continuación se muestran las propiedades de primavera utilizadasspring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
ranjesh
Para su información, se equivocó "en el código. Vername= "parent"
alexander
0

Utilice esta forma para eliminar solo un lado

    @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
fuente
-1

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

La anotación dada funcionó para mí. Puede intentarlo

Por ejemplo :-

     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
fuente