Excluindo objetos com o Hibernate

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á.