Como o JPA orphanRemoval = true difere da cláusula ON DELETE CASCADE DML

184

Estou um pouco confuso sobre o orphanRemovalatributo JPA 2.0 .

Acho que percebo que é necessário quando uso as ferramentas de geração de banco de dados do meu provedor de JPA para criar o DDL do banco de dados subjacente para ter uma ON DELETE CASCADErelação específica.

No entanto, se o banco de dados existe e já possui uma ON DELETE CASCADErelação, isso não é suficiente para fazer a exclusão da exclusão em cascata? O que o orphanRemovalfaz além disso?

Felicidades

Markos Fragkakis
fonte

Respostas:

292

orphanRemovalnão tem nada a ver com ON DELETE CASCADE.

orphanRemovalé uma coisa totalmente específica para ORM . Marca a entidade "filho" a ser removida quando não é mais referenciada da entidade "pai", por exemplo, quando você remove a entidade filho da coleção correspondente da entidade pai.

ON DELETE CASCADEé uma coisa específica do banco de dados , ela exclui a linha "filho" no banco de dados quando a linha "pai" é excluída.

axtavt
fonte
3
Isso significa que eles têm o efeito seguro, mas um sistema diferente é responsável por fazer isso acontecer?
Anonymoose
101
Anon, não tem o mesmo efeito. ON DELETE CASCADE diz ao banco de dados para excluir todos os registros filhos quando o pai for excluído. Ou seja, se eu excluir a FATURA, exclua todos os ITENS nessa FATURA. OrphanRemoval informa ao ORM que se eu remover um objeto Item da coleção de Itens que pertencem a um objeto Fatura (na operação de memória) e "salvar" a Fatura, o Item removido deverá ser excluído do banco de dados subjacente.
garyKeorkunian
2
Se você usar um relacionamento unidirecional, o órfão será removido automaticamente, mesmo que você não defina orphanRemoval = true
Tim
98

Um exemplo tomado aqui :

Quando um Employeeobjeto de entidade é removido, a operação de remoção é conectada em cascata ao Addressobjeto de entidade referenciado . A este respeito, orphanRemoval=truee cascade=CascadeType.REMOVEsão idênticos, e se orphanRemoval=truefor especificado, CascadeType.REMOVEé redundante.

A diferença entre as duas configurações está na resposta à desconexão de um relacionamento. Por exemplo, como ao definir o campo de endereço para nullou para outro Addressobjeto.

  • Se orphanRemoval=truefor especificado, a Addressinstância desconectada será removida automaticamente. Isso é útil para limpar objetos dependentes (por exemplo Address) que não deveriam existir sem uma referência de um objeto proprietário (por exemplo Employee).

  • Se apenas cascade=CascadeType.REMOVEfor especificado, nenhuma ação automática será executada, pois desconectar um relacionamento não é uma operação de remoção.

Para evitar referências pendentes como resultado da remoção órfã, esse recurso deve ser ativado apenas para campos que contêm objetos dependentes particulares não compartilhados.

Espero que isso torne mais claro.

forhas
fonte
Depois de ler sua resposta, percebo a diferença exata entre eles e meu problema foi resolvido. Fiquei preso ao excluir as entidades filhas do banco de dados, se elas foram desconectadas (removidas) da coleção definida na entidade pai. Para o mesmo, fiz a pergunta ' stackoverflow.com/questions/15526440/… '. Apenas adicionando meu comentário para vincular as duas perguntas.
Narendra Verma
@forhas, por favor, faça a pergunta stackoverflow.com/questions/58185249/…
GokulRaj KN
46

No momento em que você remover uma entidade filha da coleção, você também removerá essa entidade filha do banco de dados. orphanRemoval também implica que você não pode mudar de pai; se houver um departamento que tenha funcionários, depois de removê-lo para colocá-lo em outro departamento, você inadvertidamente removerá esse funcionário do banco de dados em liberação / confirmação (o que ocorrer primeiro). O moral é definir orphanRemoval como true, desde que você tenha certeza de que os filhos desse pai não migrarão para um pai diferente durante toda a sua existência. A ativação do orphanRemoval também adiciona REMOVE automaticamente à lista em cascata.

Onur
fonte
3
Exatamente correto ... também chamado de relacionamento pai / filho "privado".
usar o seguinte comando
Isso significa que, logo que eu chamo department.remove(emp);que o empregado será excluída da tabela emp, mesmo sem chamarcommit()
JavaTechnical
18

O mapeamento JPA equivalente para o DDL ON DELETE CASCADEé cascade=CascadeType.REMOVE. Remoção órfã significa que as entidades dependentes são removidas quando o relacionamento com a entidade "pai" é destruído. Por exemplo, se um filho for removido de um @OneToManyrelacionamento sem removê-lo explicitamente no gerenciador de entidades.

Heri
fonte
1
cascade=CascadeType.REMOVENÃO é equivalente a ON DELETE CASCADE. Ao remover no código do aplicativo e não afeta no DDL, outro executado no DB. Veja stackoverflow.com/a/19696859/548473
Grigory Kislin
9

A diferença é:
- orphanRemoval = true: a entidade "Filho" é removida quando não é mais referenciada (seu pai não pode ser removido).
- CascadeType.REMOVE: A entidade "Filho" é removida apenas quando o "Pai" é removido.

user3572554
fonte
6

Como essa é uma pergunta muito comum, escrevi este artigo , no qual essa resposta se baseia.

Transições de estado da entidade

O JPA converte transições de estado da entidade para instruções SQL, como INSERT, UPDATE ou DELETE.

Transições de estado da entidade JPA

Quando você é persistuma entidade, está agendando a instrução INSERT para ser executada quando EntityManagerfor liberada, automática ou manualmente.

quando você é removeuma entidade, está agendando a instrução DELETE, que será executada quando o contexto de persistência for liberado.

Transições de estado da entidade em cascata

Por conveniência, o JPA permite propagar transições de estado de entidade de entidades pai para filho filho.

Portanto, se você tem uma Postentidade pai que tem uma @OneToManyassociação com a PostCommententidade filho:

Entidades Post e PostComment

A commentscoleção na Postentidade é mapeada da seguinte maneira:

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

CascadeType.ALL

O cascadeatributo informa ao provedor JPA para passar a transição do estado da entidade da entidade pai Postpara todas as PostCommententidades contidas na commentscoleção.

Portanto, se você remover a Postentidade:

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

entityManager.remove(post);

O provedor JPA removerá a PostCommententidade primeiro e, quando todas as entidades filhas forem excluídas, a Postentidade também será excluída :

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

DELETE FROM post WHERE id = 1

Remoção de órfãos

Quando você define o orphanRemovalatributo como true, o provedor JPA agendará uma removeoperação quando a entidade filha for removida da coleção.

Então, no nosso 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);

O provedor JPA removerá o post_commentregistro associado, pois a PostCommententidade não é mais referenciada na commentscoleção:

DELETE FROM post_comment WHERE id = 1

EM EXCLUIR CASCADE

O ON DELETE CASCADEé definido no nível FK:

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

Depois de fazer isso, se você excluir uma postlinha:

DELETE FROM post WHERE id = 1

Todas as post_commententidades associadas são removidas automaticamente pelo mecanismo de banco de dados. No entanto, isso pode ser uma operação muito perigosa se você excluir uma entidade raiz por engano.

Conclusão

A vantagem do JPA cascadee das orphanRemovalopções é que você também pode se beneficiar do bloqueio otimista para evitar atualizações perdidas .

Se você usar o mecanismo de cascata JPA, não precisará usar o nível DDL ON DELETE CASCADE, o que pode ser uma operação muito perigosa se você remover uma entidade raiz que possui muitas entidades filhas em vários níveis.

Para mais detalhes sobre este tópico, consulte este artigo .

Vlad Mihalcea
fonte
Portanto, em Remoção Órfã, parte da sua resposta: post.getComments (). Remove (postComment); funcionará no mapeamento bidirecional OneToMany apenas devido à cascata Persist. Sem cascata e sem remoção no ManyToOne, como no seu exemplo, a remoção da conexão entre duas entidades não seria mantida no DB?
aurelije 27/03
A remoção de órfãos não é afetada por CascadeType. É um mecanismo complementar. Agora, você está confundindo remoção com persistência. A remoção de órfão é sobre a exclusão de associações não referenciadas, enquanto a persistência é sobre como salvar novas entidades. Você precisa seguir os links fornecidos na resposta para entender melhor esses conceitos.
Vlad Mihalcea 27/03
Não entendo uma coisa: como a remoção de órfãos será ativada no mapeamento bidirecional se nunca removermos a conexão no lado M? Eu acho que remover o PostComment da lista do Post sem definir PostComment.post como null não resultará na remoção da conexão entre essas duas entidades no DB. É por isso que eu acho que a remoção de órfãos não vai começar, no mundo das relações lá PostComment não é órfão. Vou testá-lo quando tiver tempo livre.
aurelije 27/03
1
Adicionei esses dois exemplos no meu repositório Java Persistence GitHub de alto desempenho, que demonstram como tudo funciona. Você não precisa sincronizar o lado filho, como costuma fazer para remover as entidades diretamente. No entanto, a remoção de órfão funciona apenas se a cascata for adicionada, mas isso parece ser uma limitação do Hibernate, não uma especificação da JPA.
Vlad Mihalcea 27/03
5

Resposta @ GaryK é absolutamente ótimo, eu passei uma hora procurando uma explicação orphanRemoval = truevs CascadeType.REMOVEe isso me ajudou a entender.

Resumindo: orphanRemoval = truefunciona idêntico como CascadeType.REMOVE SOMENTE SE estamos excluindo object ( entityManager.delete(object)) e queremos que os objetos childs sejam removidos também.

Em situações completamente diferentes, quando buscamos alguns dados como List<Child> childs = object.getChilds()e removemos um child ( entityManager.remove(childs.get(0)) usando orphanRemoval=true, a entidade correspondente childs.get(0)será excluída do banco de dados.

pzeszko
fonte
1
Você tem um erro de digitação no seu segundo parágrafo: não existe um método como entityManager.delete (obj); é entityManager.remove (obj).
JL_SO 12/09
3

A remoção de órfãos tem o mesmo efeito que ON DELETE CASCADE no seguinte cenário: - Digamos que tenhamos um relacionamento simples de muitos para um entre a entidade estudantil e uma entidade guia, em que muitos alunos podem ser mapeados para o mesmo guia e, no banco de dados, temos um relação de chave estrangeira entre a tabela Student e Guide, de forma que a tabela student tenha id_guide como 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;

// A entidade pai

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

Nesse cenário, o relacionamento é tal que a entidade estudantil é o proprietário do relacionamento e, como tal, precisamos salvar a entidade estudantil para manter o gráfico inteiro do objeto, por exemplo

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

Aqui estamos mapeando o mesmo guia com dois objetos de alunos diferentes e, como o CASCADE.PERSIST é usado, o gráfico de objetos será salvo como abaixo na tabela do banco de dados (MySql no meu caso)

Tabela do ALUNO: -

ID Nome Departamento Id_Guide

1 Roy ECE 1

2 Nick ECE 1

Tabela GUIA: -

ID NAME Salário

1 João $ 1500

e agora, se eu quiser remover um dos alunos, usando

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

e quando um registro de aluno é removido, o registro de guia correspondente também deve ser removido, é aí que o atributo CASCADE.REMOVE na entidade Student entra em cena e o que faz é: remove o aluno com o identificador 1, bem como o objeto de guia correspondente (identificador 1) Mas neste exemplo, há mais um objeto de aluno que é mapeado para o mesmo registro de guia e, a menos que usemos o atributo orphanRemoval = true na Entidade de guia, o código de remoção acima não funcionará.

kunal
fonte