In che modo JPA orphanRemoval = true differisce dalla clausola DML ON DELETE CASCADE


184

Sono un po 'confuso sull'attributo JPA 2.0 orphanRemoval.

Penso di poter vedere che è necessario quando uso gli strumenti di generazione DB del mio provider JPA per creare il database DDL sottostante per avere una ON DELETE CASCADErelazione specifica.

Tuttavia, se il DB esiste e ha già una ON DELETE CASCADErelazione, questo non è sufficiente per eliminare in modo appropriato la cancellazione? Cosa fa orphanRemovalin aggiunta?

Saluti

Risposte:


292

orphanRemovalnon ha niente a che fare con ON DELETE CASCADE.

orphanRemovalè una cosa completamente specifica per ORM . Contrassegna l'entità "figlio" da rimuovere quando non fa più riferimento all'entità "padre", ad esempio quando si rimuove l'entità figlio dalla raccolta corrispondente dell'entità padre.

ON DELETE CASCADEè una cosa specifica del database , elimina la riga "figlio" nel database quando viene eliminata la riga "padre".


3
Questo significa che hanno l'effetto sicuro, ma un sistema diverso è responsabile per farlo accadere?
Anonymoose,

101
Anon, non ha lo stesso effetto. ON DELETE CASCADE indica al DB di eliminare tutti i record figlio quando viene eliminato il genitore. Cioè se elimino la FATTURA, quindi elimino tutti gli ARTICOLI in quella FATTURA. OrphanRemoval dice all'ORM che se rimuovo un oggetto Item dalla raccolta di oggetti che appartengono a un oggetto Invoice (in operazione di memoria) e quindi "salvo" la fattura, l'elemento rimosso dovrebbe essere eliminato dal DB sottostante.
garyKeorkunian,

2
Se usi una relazione unidirezionale, l'orfano verrà rimosso automaticamente anche se non imposti orphanRemoval = true
Tim

98

Un esempio preso qui :

Quando un Employeeoggetto entità viene rimosso, l'operazione di rimozione viene messa in cascata Addresssull'oggetto entità referenziato . A questo proposito, orphanRemoval=truee cascade=CascadeType.REMOVEsono identici, e se orphanRemoval=trueè specificato, CascadeType.REMOVEè ridondante.

La differenza tra le due impostazioni sta nella risposta alla disconnessione di una relazione. Ad esempio, ad esempio quando si imposta il campo dell'indirizzo su nullo su un altro Addressoggetto.

  • Se orphanRemoval=trueviene specificato, l' Addressistanza disconnessa viene automaticamente rimossa. Ciò è utile per ripulire oggetti dipendenti (ad es. Address) Che non dovrebbero esistere senza un riferimento da un oggetto proprietario (ad es Employee.).

  • Se cascade=CascadeType.REMOVEviene specificato solo , non viene intrapresa alcuna azione automatica poiché la disconnessione di una relazione non è un'operazione di rimozione.

Per evitare riferimenti penzolanti a seguito della rimozione orfana, questa funzione deve essere abilitata solo per i campi che contengono oggetti dipendenti privati ​​non condivisi.

Spero che questo renda più chiaro.


Dopo aver letto la tua risposta, realizzo la differenza esatta tra entrambi e il mio problema è stato risolto. Mi sono bloccato nell'eliminazione delle entità figlio dal database, se queste sono disconnesse (rimosse) dalla raccolta definita nell'entità padre. Per lo stesso ho posto la domanda " stackoverflow.com/questions/15526440/… ". Aggiungo solo il mio commento per collegare entrambe le domande.
Narendra Verma,

@forhas prega di passare attraverso domanda stackoverflow.com/questions/58185249/...
GokulRaj KN

46

Nel momento in cui rimuovi un'entità figlio dalla raccolta, rimuoverai anche quell'entità figlio dal DB. orphanRemoval implica anche che non puoi cambiare i genitori; se c'è un reparto che ha dipendenti, una volta rimosso quell'impiegato per inserirlo in un altro reparto, sarà inavvertitamente rimosso quell'impiegato dal DB in flush / commit (che viene prima). Il morale è di impostare OrphanRemoval su true fintanto che si è certi che i figli di quel genitore non migreranno verso un altro genitore durante la loro esistenza. L'attivazione di orphanRemoval aggiunge automaticamente RIMUOVI all'elenco a cascata.


3
Esatto esatto ... chiamato anche relazione "privato" genitore / figlio.
HDave

Ciò significa che non appena chiamo department.remove(emp);quel dipendente verrà eliminato dalla tabella emp senza nemmeno chiamarecommit()
JavaTechnical

18

La mappatura JPA equivalente per DDL ON DELETE CASCADEè cascade=CascadeType.REMOVE. Rimozione orfana significa che le entità dipendenti vengono rimosse quando viene distrutta la relazione con la loro entità "genitore". Ad esempio, se un figlio viene rimosso da una @OneToManyrelazione senza rimuoverla esplicitamente nel gestore entità.


1
cascade=CascadeType.REMOVENON è equivalente a ON DELETE CASCADE. Su rimuovi nel codice dell'applicazione e non influisce su DDL, altri eseguiti nel DB. Vedere stackoverflow.com/a/19696859/548473
Grigory Kislin

9

La differenza è:
- orphanRemoval = true: l'entità "figlio" viene rimossa quando non è più referenziata (il suo genitore potrebbe non essere rimosso).
- CascadeType.REMOVE: l'entità "Child" viene rimossa solo quando viene rimosso il suo "Parent".


6

Poiché questa è una domanda molto comune, ho scritto questo articolo , su cui si basa questa risposta.

Transizioni dello stato delle entità

JPA traduce le transizioni dello stato dell'entità in istruzioni SQL, come INSERT, UPDATE o DELETE.

Transizioni di stato dell'entità JPA

Quando si è persistun'entità, si sta pianificando che l'istruzione INSERT venga eseguita quando EntityManagerviene scaricata, automaticamente o manualmente.

quando sei removeun'entità, stai pianificando l'istruzione DELETE, che verrà eseguita quando il Contesto di persistenza viene svuotato.

Transizioni di stato entità a cascata

Per comodità, JPA consente di propagare le transizioni di stato delle entità da entità padre a quella figlio.

Pertanto, se si dispone di Postun'entità padre che ha @OneToManyun'associazione con l' PostCommententità figlio:

Entità Post e PostComment

La commentsraccolta Postnell'entità è mappata come segue:

@OneToMany(
    mappedBy = "post", 
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();

CascadeType.ALL

L' cascadeattributo indica al provider JPA di passare la transizione dello stato dell'entità Postdall'entità padre a tutte le PostCommententità contenute incomments raccolta.

Quindi, se rimuovi l' Postentità:

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

entityManager.remove(post);

Il provider JPA rimuoverà PostCommentprima l' entità e quando tutte le entità figlio verranno eliminate, eliminerà anche l' Postentità:

DELETE FROM post_comment WHERE id = 1
DELETE FROM post_comment WHERE id = 2

DELETE FROM post WHERE id = 1

Rimozione orfana

Quando si imposta l' orphanRemovalattributo su true, il provider JPA pianificherà removeun'operazione quando l'entità figlio viene rimossa dalla raccolta.

Quindi, nel nostro caso,

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

PostComment postComment = post.getComments().get(0);
assertEquals(1L, postComment.getId());

post.getComments().remove(postComment);

Il provider JPA rimuoverà il post_commentrecord associato poiché l' PostCommententità non fa più riferimento nella commentsraccolta:

DELETE FROM post_comment WHERE id = 1

ALLA CANCELLAZIONE DELLA CASCATA

Il ON DELETE CASCADEè definito a livello FK:

ALTER TABLE post_comment 
ADD CONSTRAINT fk_post_comment_post_id 
FOREIGN KEY (post_id) REFERENCES post 
ON DELETE CASCADE;

Una volta che lo fai, se elimini una postriga:

DELETE FROM post WHERE id = 1

Tutte le post_commententità associate vengono rimosse automaticamente dal motore di database. Tuttavia, questa operazione può essere molto pericolosa se si elimina un'entità radice per errore.

Conclusione

Il vantaggio dell'APP cascadee delle orphanRemovalopzioni è che puoi anche trarre vantaggio dal blocco ottimistico per evitare la perdita di aggiornamenti .

Se si utilizza il meccanismo a cascata JPA, non è necessario utilizzare il livello DDL ON DELETE CASCADE, che può essere un'operazione molto pericolosa se si rimuove un'entità radice che ha molte entità figlio su più livelli.

Per maggiori dettagli su questo argomento, consulta questo articolo .


Quindi nella parte di rimozione orfana della tua risposta: post.getComments (). Remove (postComment); funzionerà nella mappatura bidirezionale OneToMany solo a causa della cascata Persist. Senza il collegamento in cascata e senza la rimozione sul lato ManyToOne, come nel tuo esempio, la rimozione della connessione tra 2 entità non verrebbe mantenuta nel DB?
aurelije,

La rimozione orfana non è influenzata da CascadeType. È un meccanismo complementare. Ora stai sbagliando la rimozione con persistente. La rimozione orfana riguarda l'eliminazione delle associazioni senza riferimento mentre la persistenza riguarda il salvataggio di nuove entità. È necessario seguire i collegamenti forniti nella risposta per comprendere meglio questi concetti.
Vlad Mihalcea,

Non capisco una cosa: come verrà avviata la rimozione orfana nella mappatura bidirezionale se non rimuoviamo mai la connessione sul lato M? Penso che la rimozione di PostComment dall'elenco Post senza impostare PostComment.post su null non comporterà la rimozione della connessione tra quelle 2 entità nel DB. Questo è il motivo per cui penso che la rimozione orfana non entrerà in gioco, nel mondo relazionale PostComment non è orfano. Lo proverò quando avrò del tempo libero.
aurelije,

1
Ho aggiunto questi due esempi nel mio repository GitHub Java Persistence ad alte prestazioni che dimostrano come funziona tutto. Non è necessario sincronizzare il lato figlio come di solito è necessario fare per rimuovere direttamente le entità. Tuttavia, la rimozione orfana funziona solo se viene aggiunto il collegamento in cascata, ma questa sembra essere una limitazione dell'ibernazione, non una specifica JPA.
Vlad Mihalcea,

5

@GaryK risposta è assolutamente grande, ho passato un'ora alla ricerca di una spiegazione orphanRemoval = truevs CascadeType.REMOVEe mi ha aiutato a capire.

Riassumendo: orphanRemoval = truefunziona come CascadeType.REMOVE SOLO SE stiamo cancellando object ( entityManager.delete(object)) e vogliamo che anche gli oggetti child vengano rimossi.

In siti completamente diversi, quando recuperiamo alcuni dati come List<Child> childs = object.getChilds()e quindi rimuoviamo un figlio ( entityManager.remove(childs.get(0)) usando orphanRemoval=true, quell'entità corrispondente childs.get(0)verrà eliminata dal database.


1
Hai un refuso nel secondo paragrafo: Non esiste un metodo come entityManager.delete (obj); è entityManager.remove (obj).
JL_SO,

3

la rimozione orfana ha lo stesso effetto di ON DELETE CASCADE nel seguente scenario: - Diciamo che abbiamo una semplice relazione molti-a-uno tra un'entità studentesca e un'entità guida, dove molti studenti possono essere mappati sulla stessa guida e nel database abbiamo un relazione chiave esterna tra la tabella Student e Guide in modo tale che la tabella student abbia id_guide come FK.

    @Entity
    @Table(name = "student", catalog = "helloworld")
    public class Student implements java.io.Serializable {
     @Id
     @GeneratedValue(strategy = IDENTITY)
     @Column(name = "id")
     private Integer id;

    @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
    @JoinColumn(name = "id_guide")
    private Guide guide;

// L'entità padre

    @Entity
    @Table(name = "guide", catalog = "helloworld")
    public class Guide implements java.io.Serializable {

/**
 * 
 */
private static final long serialVersionUID = 9017118664546491038L;

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "name", length = 45)
private String name;

@Column(name = "salary", length = 45)
private String salary;


 @OneToMany(mappedBy = "guide", orphanRemoval=true) 
 private Set<Student> students = new  HashSet<Student>(0);

In questo scenario, la relazione è tale che l'entità studente è il proprietario della relazione e come tale dobbiamo salvare l'entità studente per mantenere l'intero grafico dell'oggetto, ad es.

    Guide guide = new Guide("John", "$1500");
    Student s1 = new Student(guide, "Roy","ECE");
    Student s2 = new Student(guide, "Nick", "ECE");
    em.persist(s1);
    em.persist(s2);

Qui stiamo mappando la stessa guida con due diversi oggetti studente e poiché viene utilizzato CASCADE.PERSIST, il grafico degli oggetti verrà salvato come di seguito nella tabella del database (MySql nel mio caso)

Tavolo STUDENT: -

ID nome Dipartimento Id_Guide

1 Roy ECE 1

2 Nick ECE 1

Tabella GUIDA: -

ID NOME Stipendio

1 Giovanni $ 1500

e ora se voglio rimuovere uno degli studenti, usando

      Student student1 = em.find(Student.class,1);
      em.remove(student1);

e quando viene rimosso un record dello studente, deve essere rimosso anche il record della guida corrispondente, è qui che viene visualizzato l'attributo CASCADE.REMOVE nell'entità Studente e ciò che fa; rimuove lo studente con l'identificatore 1 e l'oggetto guida corrispondente (identificatore 1). Ma in questo esempio, esiste un altro oggetto studente mappato allo stesso record della guida e, a meno che non utilizziamo l' attributo orphanRemoval = true nell'entità guida, il codice di rimozione sopra non funzionerà.

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.