O JPA OneToMany não exclui filho

158

Eu tenho um problema com um @OneToManymapeamento simples entre um pai e uma entidade filho. Tudo funciona bem, apenas os registros filho não são excluídos quando os removo da coleção.

O pai:

@Entity
public class Parent {
    @Id
    @Column(name = "ID")
    private Long id;

    @OneToMany(cascade = {CascadeType.ALL}, mappedBy = "parent")
    private Set<Child> childs = new HashSet<Child>();

 ...
}

A criança:

@Entity
public class Child {
    @Id
    @Column(name = "ID")
    private Long id;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="PARENTID", nullable = false)
    private Parent parent;

  ...
}

Se agora eu excluir e filho do conjunto de filhos, ele não será excluído do banco de dados. Tentei anular a child.parentreferência, mas isso também não funcionou.

As entidades são usadas em um aplicativo Web, a exclusão acontece como parte de uma solicitação do Ajax. Não tenho uma lista de filhos excluídos quando o botão Salvar é pressionado, portanto não posso excluí-los implicitamente.

bert
fonte

Respostas:

252

O comportamento da JPA está correto (ou seja , de acordo com a especificação ): os objetos não são excluídos simplesmente porque você os removeu de uma coleção OneToMany. Existem extensões específicas do fornecedor que fazem isso, mas a JPA nativa não a atende.

Em parte, isso ocorre porque o JPA realmente não sabe se deve excluir algo removido da coleção. Em termos de modelagem de objetos, essa é a diferença entre composição e "agregação *.

Na composição , a entidade filha não existe sem o pai. Um exemplo clássico é entre casa e quarto. Exclua a casa e os quartos também.

A agregação é um tipo de associação mais flexível e é tipificada por Curso e Aluno. Excluir o curso e o aluno ainda existe (provavelmente em outros cursos).

Portanto, você precisa usar extensões específicas do fornecedor para forçar esse comportamento (se disponível) ou excluir explicitamente o filho E removê-lo da coleção dos pais.

Estou ciente de:

cleto
fonte
obrigado a boa explicação. então é como eu temia. (eu fiz algumas pesquisas / leituras antes de perguntar, apenas querendo salvar). de alguma forma começo a lamentar a decisão de usar a API JPA e nit Hibernate diretamente. Vou tentar o ponteiro Chandra Patni e usar o tipo de cascata hibernate delete_orphan.
bert
Eu tenho um tipo de pergunta semelhante sobre isso. Seria gentil dar uma olhada neste post aqui, por favor? stackoverflow.com/questions/4569857/…
Thang Pham 01/01
77
Com JPA 2.0, agora você pode usar a opção orphanRemoval = true
itsadok
2
Ótima explicação inicial e bons conselhos sobre a remoção de órfãos. Não fazia ideia que o JPA não era responsável por esse tipo de remoção. As nuances entre o que eu sei sobre o Hibernate e o que o JPA realmente faz podem ser enlouquecedoras.
sma
Bem explicado, expondo as diferenças entre Composição e Agregação!
Felipe Leão
73

Além da resposta do cletus, o JPA 2.0 , final desde dezembro de 2010, introduz um orphanRemovalatributo nas @OneToManyanotações. Para mais detalhes, consulte esta entrada do blog .

Observe que, como a especificação é relativamente nova, nem todos os provedores de JPA 1 têm uma implementação final de JPA 2. Por exemplo, a versão do Hibernate 3.5.0-Beta-2 ainda não suporta esse atributo.

Louis Jacomet
fonte
entrada do blog - o link está quebrado.
Steffi
1
E a magia da máquina wayback
Louis Jacomet
43

Você pode tentar isso:

@OneToOne(orphanRemoval=true) or @OneToMany(orphanRemoval=true).

Maciel Bombonato
fonte
2
Obrigado. Mas a questão voltou da JPA 1 vezes. E esta opção não estava disponível de volta a.
bert
9
bom saber, no entanto, para aqueles de nós que procurar uma solução para isso nos tempos JPA2 :)
alex440
20

Como explicado, não é possível fazer o que eu quero com o JPA, então empreguei a anotação hibernate.cascade, com isso, o código relevante na classe Parent agora fica assim:

@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, mappedBy = "parent")
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
            org.hibernate.annotations.CascadeType.DELETE,
            org.hibernate.annotations.CascadeType.MERGE,
            org.hibernate.annotations.CascadeType.PERSIST,
            org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
private Set<Child> childs = new HashSet<Child>();

Eu não poderia usar simplesmente 'ALL', pois isso também excluiria o pai.

bert
fonte
4

Aqui, em cascata, no contexto de remoção, significa que os filhos serão removidos se você remover o pai. Não é a associação. Se você estiver usando o Hibernate como seu provedor de JPA, poderá fazê-lo usando a cascata específica do hibernate .

Chandra Patni
fonte
4

Você pode tentar isso:

@OneToOne(cascade = CascadeType.REFRESH) 

ou

@OneToMany(cascade = CascadeType.REFRESH)
Amit Ghorpade
fonte
3
@Entity 
class Employee {
     @OneToOne(orphanRemoval=true)
     private Address address;
}

Veja aqui .

Chang
fonte