Excluindo objetos com o Hibernate
*1. Visão geral *
Como uma estrutura ORM com todos os recursos, o Hibernate é responsável pelo gerenciamento do ciclo de vida de objetos persistentes (entidades), incluindo operações CRUD, como read, save, update e delete.
Neste artigo, exploramos várias* maneiras pelas quais os objetos podem ser excluídos de um banco de dados usando o Hibernate *e explicamos problemas e armadilhas comuns que podem ocorrer.
Usamos o JPA e apenas recuamos e usamos a API nativa do Hibernate para os recursos que não são padronizados no JPA.
===* 2. Diferentes maneiras de excluir objetos *
Os objetos podem ser excluídos nos seguintes cenários:
-
Usando EntityManager.remove
-
Quando uma exclusão é em cascata de outras instâncias da entidade
-
Quando um orphanRemoval é aplicado
-
Executando uma instrução delete JPQL
-
Executando consultas nativas *Aplicando uma técnica de exclusão suave (filtrando entidades com exclusão suave por uma condição em uma cláusula _ @ Where_)
No restante do artigo, analisamos esses pontos em detalhes.
===* 3. Exclusão usando o Entity Manager *
A exclusão com o EntityManager é a maneira mais direta de remover uma instância da entidade:
Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();
foo = entityManager.find(Foo.class, foo.getId());
assertThat(foo, notNullValue());
entityManager.remove(foo);
flushAndClear();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
Nos exemplos deste artigo, usamos um método auxiliar para liberar e limpar o contexto de persistência quando necessário:
void flushAndClear() {
entityManager.flush();
entityManager.clear();
}
Após chamar o método EntityManager.remove, a instância fornecida faz a transição para o estado removed e a exclusão associada do banco de dados ocorre na próxima descarga.
Observe que* a instância excluída será persistida se uma operação PERSIST for aplicada a ela *. Um erro comum é ignorar que uma operação PERSIST foi aplicada a uma instância removida (geralmente, porque é cascata de outra instância no momento da liberação), porque a seção 3.2.2 do https://download.oracle. com/otndocs/jcp/persistence-2_1-fr-eval-spec/index.html [especificação JPA] exige que essa instância seja persistida novamente nesse caso.
Ilustramos isso definindo uma associação _ @ ManyToOne_ de Foo para Bar:
@Entity
public class Foo {
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Bar bar;
//other mappings, getters and setters
}
Quando excluímos uma instância Bar referenciada por uma instância Foo que também é carregada no contexto de persistência, a instância Bar não será removida do banco de dados:
Bar bar = new Bar("bar");
Foo foo = new Foo("foo");
foo.setBar(bar);
entityManager.persist(foo);
flushAndClear();
foo = entityManager.find(Foo.class, foo.getId());
bar = entityManager.find(Bar.class, bar.getId());
entityManager.remove(bar);
flushAndClear();
bar = entityManager.find(Bar.class, bar.getId());
assertThat(bar, notNullValue());
foo = entityManager.find(Foo.class, foo.getId());
foo.setBar(null);
entityManager.remove(bar);
flushAndClear();
assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());
Se a Bar removida for referenciada por um Foo, a operação PERSIST será conectada em cascata de Foo a Bar porque a associação está marcada com cascade = CascadeType.ALL e a exclusão não é programada. Para verificar se isso está acontecendo, podemos ativar o nível do log de rastreamento para o pacote org.hibernate e procurar entradas como un-scheduling entity deletion.
*4. Exclusão em cascata *
A exclusão pode ser conectada em cascata a entidades filhas quando os pais são removidos:
Bar bar = new Bar("bar");
Foo foo = new Foo("foo");
foo.setBar(bar);
entityManager.persist(foo);
flushAndClear();
foo = entityManager.find(Foo.class, foo.getId());
entityManager.remove(foo);
flushAndClear();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());
Aqui bar é removido porque a remoção é conectada em cascata a foo, pois a associação é declarada em cascata todas as operações do ciclo de vida de Foo a Bar.
Observe que* quase sempre é um erro colocar em cascata a operação REMOVE em uma associação _ @ ManyToMany_ *, porque isso desencadeia a remoção de instâncias filho que podem estar associadas a outras instâncias pai. Isso também se aplica a CascadeType.ALL, pois significa que todas as operações devem ser conectadas em cascata, incluindo a operação REMOVE.
*5. Remoção de Órfãos *
A diretiva orphanRemoval declara que as instâncias de entidade associadas devem ser removidas quando são desassociadas do pai ou equivalentemente quando o pai é removido.
Mostramos isso definindo essa associação de Bar a _Baz: _
@Entity
public class Bar {
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Baz> bazList = new ArrayList<>();
//other mappings, getters and setters
}
Em seguida, uma instância Baz é excluída automaticamente quando removida da lista de uma instância Bar pai:
Bar bar = new Bar("bar");
Baz baz = new Baz("baz");
bar.getBazList().add(baz);
entityManager.persist(bar);
flushAndClear();
bar = entityManager.find(Bar.class, bar.getId());
baz = bar.getBazList().get(0);
bar.getBazList().remove(baz);
flushAndClear();
assertThat(entityManager.find(Baz.class, baz.getId()), nullValue());
*A semântica da operação _orphanRemoval_ é completamente semelhante a uma operação _REMOVE_ aplicada diretamente às instâncias filho afetadas* , o que significa que a operação _REMOVE_ é mais em cascata para os filhos aninhados. Como conseqüência, você deve garantir que nenhuma outra instância faça referência às removidas (caso contrário, elas serão persistidas).
*6. Exclusão usando uma instrução JPQL *
O Hibernate suporta operações de exclusão no estilo DML:
Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();
entityManager.createQuery("delete from Foo where id = :id")
.setParameter("id", foo.getId())
.executeUpdate();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
É importante observar que* as instruções JPQL no estilo DML não afetam o estado nem o ciclo de vida das instâncias da entidade que já estão carregadas no contexto de persistência *; portanto, é recomendável que elas sejam executadas antes do carregamento das entidades afetadas.
*7. Exclusão usando consultas nativas *
Às vezes, precisamos voltar a consultas nativas para obter algo que não é suportado pelo Hibernate ou é específico para um fornecedor de banco de dados. Também podemos excluir dados no banco de dados com consultas nativas:
Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();
entityManager.createNativeQuery("delete from FOO where ID = :id")
.setParameter("id", foo.getId())
.executeUpdate();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
A mesma recomendação se aplica a consultas nativas e a instruções no estilo JPA DML, ou seja, as consultas nativas não afetam o estado nem o ciclo de vida das instâncias da entidade que são carregadas no contexto de persistência antes da execução das consultas.
===* 8. Exclusão suave *
Frequentemente, não é desejável remover dados de um banco de dados devido a propósitos de auditoria e manutenção do histórico. Em tais situações, podemos aplicar uma técnica chamada exclusões suaves. Basicamente, apenas marcamos uma linha como removida e a filtramos ao recuperar dados.
Para evitar muitas condições redundantes nas cláusulas where em todas as consultas que leem entidades de exclusão programável, o Hibernate fornece a anotação _ @ Where_ que pode ser colocada em uma entidade e que contém um fragmento SQL que é adicionado automaticamente às consultas SQL geradas para essa entidade.
Para demonstrar isso, adicionamos a anotação _ @ Where_ e uma coluna denominada DELETED à entidade Foo:
@Entity
@Where(clause = "DELETED = 0")
public class Foo {
//other mappings
@Column(name = "DELETED")
private Integer deleted = 0;
//getters and setters
public void setDeleted() {
this.deleted = 1;
}
}
O teste a seguir confirma que tudo funciona como esperado:
Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();
foo = entityManager.find(Foo.class, foo.getId());
foo.setDeleted();
flushAndClear();
assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
===* 9. Conclusão*
Neste artigo, examinamos diferentes maneiras pelas quais os dados podem ser excluídos com o Hibernate. Explicamos conceitos básicos e algumas práticas recomendadas. Também demonstramos como as exclusões virtuais podem ser facilmente implementadas com o Hibernate.
A implementação deste tutorial para excluir objetos com o Hibernate está disponível over no Github. Este é um projeto baseado em Maven, portanto, deve ser fácil importar e executar como está.