JPA: muitos-para-um unidirecional e exclusão em cascata

95

Digamos que eu tenha um relacionamento unidirecional @ManyToOne como o seguinte:

@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 eu tiver um pai P e filhos C 1 ... C n referenciando P, existe uma maneira limpa e bonita em JPA de remover automaticamente os filhos C 1 ... C n quando P é removido (isto é entityManager.remove(P))?

O que procuro é uma funcionalidade semelhante ao ON DELETE CASCADESQL.

criminoso
fonte
1
Mesmo que apenas 'Filho' tenha uma referência a 'Pai' (dessa forma, a referência é unidirecional) é problemático para você adicionar a Lista de 'Filho' com um mapeamento '@OneToMany' e atributo 'Cascata = ALL' para o 'pai'? Presumo que o JPA deve resolver que mesmo difícil, apenas um lado mantém a referência.
kvDennis
1
@kvDennis, há casos em que você não deseja acoplar os muitos lados a um lado. Por exemplo, em configurações do tipo ACL em que as permissões de segurança são transparentes "add-on"
Bachi

Respostas:

73

Os relacionamentos em JPA são sempre unidirecionais, a menos que você associe o pai ao filho em ambas as direções. As operações REMOVE em cascata dos pais para os filhos exigirão uma relação dos pais para os filhos (não apenas o oposto).

Portanto, você precisará fazer o seguinte:

  • Altere o @ManyToOnerelacionamento unidirecional para bidirecional @ManyToOneou unidirecional @OneToMany. Você pode então colocar em cascata as operações REMOVE para EntityManager.removeremover o pai e os filhos. Você também pode especificar orphanRemovalcomo verdadeiro, para excluir quaisquer filhos órfãos quando a entidade filha na coleção pai for definida como nula, ou seja, remover o filho quando não estiver presente em nenhuma coleção pai.
  • Ou especifique a restrição de chave estrangeira na tabela filha como ON DELETE CASCADE. Você precisará invocar EntityManager.clear()após a chamada, EntityManager.remove(parent)pois o contexto de persistência precisa ser atualizado - as entidades filhas não deveriam existir no contexto de persistência depois de terem sido excluídas do banco de dados.
Vineet Reynolds
fonte
7
existe uma maneira de fazer No2 com uma anotação JPA?
user2573153
3
Como faço para o No2 com mapeamentos xml do Hibernate?
arg20
92

Se você estiver usando o hibernate como seu provedor JPA, poderá usar a anotação @OnDelete. Esta anotação adicionará à relação a trigger ON DELETE CASCADE , que delega a exclusão dos filhos ao banco de dados.

Exemplo:

public class Parent {

        @Id
        private long id;

}


public class Child {

        @Id
        private long id;

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

Com esta solução, um relacionamento unidirecional do filho para o pai é suficiente para remover automaticamente todos os filhos. Esta solução não precisa de nenhum ouvinte, etc. Além disso, uma consulta como DELETE FROM Parent WHERE id = 1 removerá os filhos.

Thomas Hunziker
fonte
4
Não consigo fazer funcionar assim. Existe alguma versão específica do hibernate ou outro exemplo mais detalhado como este?
Mardari
3
É difícil dizer por que não está funcionando para você. Para fazer isso funcionar, você pode precisar gerar novamente o esquema ou adicionar a exclusão em cascata manualmente. A anotação @OnDelete parece estar por aí há algum tempo, portanto, não acho que a versão seja um problema.
Thomas Hunziker
10
Obrigado pela resposta. Observação rápida: o gatilho em cascata do banco de dados só será criado se você tiver habilitado a geração de DDL via hibernação. Caso contrário, você terá que adicioná-lo de outra forma (por exemplo, liquibase) para permitir que consultas ad hoc sejam executadas diretamente no banco de dados, como 'DELETE FROM Parent WHERE id = 1', execute a remoção em cascata.
mjj1409
1
isso não está funcionando quando a associação é @OneToOneAlguma idéia de como resolver isso @OneToOne?
stakowerflol
1
@ThomasHunziker isso não funcionará para o órfão, certo?
oxyt 05 de
13

Crie uma relação bidirecional, como esta:

@Entity
public class Parent implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
    private Set<Child> children;
}
tekumara
fonte
8
má resposta, os relacionamentos bidirecionais são terríveis no JPA porque operar em grandes conjuntos infantis leva uma quantidade incrível de tempo
Enerccio
1
Existe prova de que os relacionamentos bidirecionais são lentos?
shalama 01 de
@enerccio E se a relação bidirecional for um para um? Além disso, mostre um artigo que afirma que as relações bidirecionais são lentas. lento em quê? recuperando? excluindo? atualizando?
saran3h
@ saran3h cada operação (adicionar, remover) irá carregar todos os filhos, de modo que é uma grande carga de dados que pode ser inútil (como adicionar um valor não requer o carregamento de todos os filhos do banco de dados, que é exatamente o que esse mapeamento faz).
Enerccio
@Enerccio Acho que todos usam o carregamento lento nas junções. Então, como isso ainda é um problema de desempenho?
saran3h
1

Eu vi em @ManytoOne unidirecional, a exclusão não funciona como esperado. Quando o pai é excluído, o ideal é que o filho também seja excluído, mas apenas o pai é excluído e o filho NÃO é excluído e fica órfão

A tecnologia usada é Spring Boot / Spring Data JPA / Hibernate

Inicialização Sprint: 2.1.2.RELEASE

Spring Data JPA / Hibernate é usado para excluir a linha.

parentRepository.delete(parent)

ParentRepository estende o repositório CRUD padrão como mostrado abaixo ParentRepository extends CrudRepository<T, ID>

A seguir estão minha classe de entidade

@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
fonte
Eu encontrei a solução para o motivo pelo qual delete não estava funcionando. aparentemente o hibernate NÃO estava usando mysql Engine -INNODB, você precisa do engine INNODB para o mysql gerar restrição de chave estrangeira. Usar as seguintes propriedades em application.properties, faz com que o spring boot / hibernate use o mecanismo INNODB do mysql. Portanto, a restrição de chave estrangeira funciona e, portanto, também exclui cascade
ranjesh
Propriedades perdidas são usadas em comentários anteriores. a seguir estão as propriedades da mola usadasspring.jpa.hibernate.use-new-id-generator-mappings=true spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
ranjesh
FYI, você errou "no código. Vejaname= "parent"
alexander
0

Use esta forma para apagar apenas um 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
fonte
-1

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

A anotação dada funcionou para mim. Pode tentar

Por exemplo :-

     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
fonte