JPA: molti-a-uno unidirezionale e cancellazione a cascata


95

Supponiamo che io abbia una relazione unidirezionale @ManyToOne come la seguente:

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

Se ho un genitore P e figli C 1 ... C n che fanno riferimento a P, esiste un modo pulito e carino in JPA per rimuovere automaticamente i figli C 1 ... C n quando P viene rimosso (cioè entityManager.remove(P))?

Quello che sto cercando è una funzionalità simile a ON DELETE CASCADESQL.


1
Anche se solo "Child" ha un riferimento a "Parent" (in questo modo il riferimento è unidirezionale) è problematico per te aggiungere l'elenco di "Child" con una mappatura "@OneToMany" e l'attributo "Cascade = ALL" a il genitore'? Presumo che JPA dovrebbe risolvere che anche il difficile solo 1 lato detiene il riferimento.
kv Dennis

1
@kvDennis, ci sono casi in cui non vuoi accoppiare strettamente i molti lati a un lato. Ad esempio, in configurazioni tipo ACL in cui le autorizzazioni di sicurezza sono "add-on" trasparenti
Bachi

Risposte:


73

Le relazioni in JPA sono sempre unidirezionali, a meno che non associ il genitore al bambino in entrambe le direzioni. Le operazioni REMOVE a cascata dal genitore al figlio richiederanno una relazione dal genitore al figlio (non solo l'opposto).

Dovrai quindi fare questo:

  • In alternativa, modifica la @ManyToOnerelazione unidirezionale in bidirezionale @ManyToOneo unidirezionale @OneToMany. È quindi possibile eseguire operazioni REMOVE a cascata in modo da EntityManager.removerimuovere il genitore e i figli. Puoi anche specificare orphanRemovalcome true, per eliminare qualsiasi figlio orfano quando l'entità figlio nella raccolta genitore è impostata su null, ovvero rimuovere il figlio quando non è presente nella collezione di alcun genitore.
  • Oppure, specifica il vincolo di chiave esterna nella tabella figlia come ON DELETE CASCADE. Sarà necessario richiamare EntityManager.clear()dopo la chiamata EntityManager.remove(parent)poiché il contesto di persistenza deve essere aggiornato: le entità figlio non dovrebbero esistere nel contesto di persistenza dopo che sono state eliminate nel database.

7
c'è un modo per fare No2 con un'annotazione JPA?
user2573153

3
Come faccio a No2 con i mapping xml di Hibernate?
arg20

92

Se utilizzi l'ibernazione come provider JPA, puoi utilizzare l'annotazione @OnDelete. Questa annotazione aggiungerà alla relazione il trigger ON DELETE CASCADE , che delega la cancellazione dei figli al database.

Esempio:

public class Parent {

        @Id
        private long id;

}


public class Child {

        @Id
        private long id;

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

Con questa soluzione è sufficiente un rapporto unidirezionale dal bambino al genitore per rimuovere automaticamente tutti i figli. Questa soluzione non necessita di ascoltatori, ecc. Anche una query come DELETE FROM Parent WHERE id = 1 rimuoverà i figli.


4
Non riesco a farlo funzionare in questo modo, esiste una versione specifica di ibernazione o altri esempi più dettagliati come questo?
Mardari

3
È difficile dire perché non funziona per te. Per farlo funzionare potrebbe essere necessario rigenerare lo schema o aggiungere manualmente l'eliminazione a cascata. L'annotazione @OnDelete sembra essere in circolazione da un po 'in quanto tale, non immagino che la versione sia un problema.
Thomas Hunziker

10
Grazie per la risposta. Nota rapida: il trigger a cascata del database verrà creato solo se è stata abilitata la generazione DDL tramite ibernazione. Altrimenti dovrai aggiungerlo in un altro modo (es. Liquibase) per consentire l'esecuzione di query ad hoc direttamente sul DB come "DELETE FROM Parent WHERE id = 1" esegue la rimozione a cascata.
mjj1409

1
questo non funziona quando l'associazione è @OneToOneQualche idea su come risolverlo @OneToOne?
stakowerflol

1
@ThomasHunziker questo non funzionerà per orphanRemoval giusto?
oxyt

13

Crea una relazione bidirezionale, come questa:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

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

8
pessima risposta, le relazioni bidirezionali sono terribili nell'APP perché operare su set per bambini di grandi dimensioni richiede un'incredibile quantità di tempo
Enerccio

1
C'è la prova che le relazioni bidirezionali sono lente?
shalama

@enerccio E se la relazione bidirezionale fosse uno-a-uno? Inoltre, mostra un articolo in cui si afferma che le relazioni bidirezionali sono lente? lento in cosa? recupero? cancellando? in aggiornamento?
saran3h

@ saran3h ogni operazione (aggiungi, rimuovi) caricherà tutti i figli, quindi è un enorme carico di dati che può essere inutile (come l'aggiunta di un valore non richiede il caricamento di tutti i figli dal database che è esattamente ciò che fa questa mappatura).
Enerccio

@Enerccio Penso che tutti usino il caricamento pigro sui join. Allora come è ancora un problema di prestazioni?
saran3h

1

Ho visto in @ManytoOne unidirezionale, l'eliminazione non funziona come previsto. Quando il genitore viene eliminato, idealmente anche il figlio dovrebbe essere eliminato, ma solo il genitore viene eliminato e il figlio NON viene eliminato e rimane orfano

Le tecnologie utilizzate sono Spring Boot / Spring Data JPA / Hibernate

Avvio Sprint: 2.1.2 RILASCIARE

Spring Data JPA / Hibernate viene utilizzato per eliminare la riga .eg

parentRepository.delete(parent)

ParentRepository estende il repository CRUD standard come mostrato di seguito ParentRepository extends CrudRepository<T, ID>

Di seguito sono riportate le mie classi di entità

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


}

Ho trovato la soluzione al motivo per cui l'eliminazione non funzionava. apparentemente hibernate NON stava usando il motore mysql -INNODB, hai bisogno del motore INNODB per mysql per generare il vincolo di chiave esterna. Utilizzando le seguenti proprietà in application.properties, rende spring boot / hibernate per utilizzare il motore mysql INNODB. Quindi il vincolo della chiave esterna funziona e quindi elimina anche la cascata
ranjesh

Le proprietà perse vengono utilizzate nel commento precedente. di seguito sono riportate le proprietà primaverili utilizzatespring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
ranjesh

Cordiali saluti, hai sbagliato "nel codice. Vediname= "parent"
alexander

0

Usa questo metodo per eliminare solo un lato

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

Data l'annotazione ha funzionato per me. Puoi provare

Per esempio :-

     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;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.