Cascade remove no JPA

As ações de cascade em mapeamentos de entidades são facilitadores que nos ajudam no dia a dia de desenvolvimento quando utilizamos JPA, uma dessas ações facilitadoras é o cascade REMOVE, que aciona mecanismos de deleção quando removido objetos relacionados, ou seja, quando um objeto é deletado e ele possui mapeamento de cascade remove com outro objetos objeto relacionado também é aplicado a remoção no objeto relacionado, como no exemplo abaixo:

A entidade customer possui um relacionamento de um para um com address.

@Entity
public class Customer {

    @NotNull
    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    private Address address;
}

Quando chamarmos o delete em customer o address também será deletado devido o cascade ALL (é assumido todos os tipos de cascade:PERSIST,MERGE, DELETE, REFRESH e DETACH) no mapeamento.

customerRepository.delete(customerId);

Assim, será gerado o seguinte SQL:

Hibernate: delete from address where id=?
Hibernate: delete from customer where id=?

Porém, devemos tomar cuidado quando utilizamos essa anotação em mapeamentos *ToMany, ou seja, com lista de objetos, porque ele vai executar uma instrução SQL para cada registro da lista como demonstrado no exemplo abaixo:

Por exemplo, uma Bill possui uma lista de BillItems, que é um mapeamento OneToMany configurado com cascade ALL.

@Entity
public class Bill {

    @NotNull(message = "Bill items is required")
    @Size(min = 1, message = "The Bill needs at least one item")
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "bill")
    private Lis<BillItem> items = new ArrayList<>();
}

Devido esse mapeamento, quando removemos uma Bill o JPA sabe que os BillItem também devem ser removidos para manter a integridade dos dados no banco de dados.

billRepository.delete(id);

Porém, quando olhamos o SQL gerado por essa ação, nos deparamos com vários “deletes” sendo executados, isso ocorre, porque o JPA aplica uma instrução de delete para cada item da lista, como o exemplo abaixo a Bill possuía 10 BillItems então foram executados 10 deletes na base, com isso, um mapeamento que nos traz uma grande facilidade pode nos prejudicar em termos de performance, pois conforme a quantidade de registros relacionados, as operações no banco de dados podem ocasionar um gargalo de performance devido a quantidade de instruções.

Hibernate: delete from bill_item where id=?
Hibernate: delete from bill_item where id=?
Hibernate: delete from bill_item where id=?
Hibernate: delete from bill_item where id=?
Hibernate: delete from bill_item where id=?
Hibernate: delete from bill_item where id=?
Hibernate: delete from bill_item where id=?
Hibernate: delete from bill_item where id=?
Hibernate: delete from bill_item where id=?
Hibernate: delete from bill_item where id=?
Hibernate: delete from bill where id=?

Uma das alternativas para evitar esse gargalo de performance é deixa essa ação no próprio banco de dados, ou seja, não deixar a cargo do JPA realizar essa operação em cascata.

Para isso, existe a anotação @OnDelete do Hibernate, onde caso o generate ddl estiver ativo no projeto ele gera o “cascade delete” na constraint no banco de dados.

    @NotNull(message = "Bill items is required")
    @Size(min = 1, message = "The Bill needs at least one item")
    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "bill")
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Lis<BillItem> items = new ArrayList<>();

Obs: Caso o generate dll não estiver ativo, essa anotação não terá nenhum efeito, porém para que o mapeamento reflita exatamente como está o modelo de dados, eu particularmente acho válido deixar essa anotação no mapeamento, pois fica com mais clareza que tem uma ação de deleção sendo aplicada nesse relacionamento.

Com isso, como é uma alteração a nível de banco de dados deve ser feito um script para remover a constraint atual do relacionamento e criar uma nova com a instrução de “delete cascade” como demonstrado abaixo.

ALTER TABLE  bill_item
 DROP CONSTRAINT bill_item_bill_id_fk;

ALTER TABLE  bill_item
ADD CONSTRAINT bill_item_bill_id_fk FOREIGN KEY (bill_id) REFERENCES bill(id) ON DELETE CASCADE;

Obs: O exemplo acima é com a sintaxe do PostrgresSQL.

Após aplicado o “ON DELETE CASCADE” no relacionamento, quando executado um delete em bill via JPA, só será impresso um delete em bill, pois a deleção dos billItems fica a cargo do banco de dados e não da aplicação, dessa forma o banco de dados sabe a forma mais performática de executar essa instrução e evita o gargalo de performance na aplicação.

billRepository.delete(id);

Hibernate: delete from bill where id=?

Uma outra alternativa em termos de performance seria não utilizar os recursos de cascade do JPA ou do banco de dados e implementar a remoção em duas ações como no exemplo abaixo:

@Transactional
public void delete(Long id) {
    billItemRepository.deleteByBillItem(id);
    billRepository.delete(id);
}
Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.