Eliminazione di oggetti con Hibernate

1. Panoramica

In quanto framework ORM completo, Hibernate è responsabile della gestione del ciclo di vita degli oggetti persistenti (entità), comprese le operazioni CRUD come lettura , salvataggio , aggiornamento ed eliminazione .

In questo articolo, esploriamo vari modi in cui gli oggetti possono essere eliminati da un database utilizzando Hibernate e spieghiamo i problemi comuni e le insidie ​​che possono verificarsi.

Usiamo JPA e facciamo un passo indietro e utilizziamo l'API nativa di Hibernate per quelle funzionalità che non sono standardizzate in JPA.

2. Diversi modi per eliminare gli oggetti

Gli oggetti possono essere eliminati nei seguenti scenari:

  • Utilizzando EntityManager.remove
  • Quando un'eliminazione viene eseguita in cascata da altre istanze di entità
  • Quando viene applicato un orphanRemoval
  • Eseguendo un'istruzione JPQL di eliminazione
  • Eseguendo query native
  • Applicando una tecnica di eliminazione temporanea (filtrando le entità eliminate temporaneamente in base a una condizione in una clausola @Where )

Nel resto dell'articolo, esaminiamo questi punti in dettaglio.

3. Eliminazione tramite Entity Manager

L'eliminazione con EntityManager è il modo più semplice per rimuovere un'istanza di entità:

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()); 

Negli esempi in questo articolo utilizziamo un metodo di supporto per svuotare e cancellare il contesto di persistenza quando necessario:

void flushAndClear() { entityManager.flush(); entityManager.clear(); }

Dopo aver chiamato il metodo EntityManager.remove , l'istanza fornita passa allo stato rimosso e l'eliminazione associata dal database si verifica al successivo lavaggio.

Si noti che l' istanza eliminata viene nuovamente persistita se viene applicata un'operazione PERSIST . Un errore comune è ignorare che un'operazione PERSIST è stata applicata a un'istanza rimossa (di solito, perché è collegata a cascata da un'altra istanza al momento dello scaricamento), perché la sezione 3.2.2 della specifica JPA impone che tale istanza sia persistette ancora in tal caso.

Lo illustriamo definendo un'associazione @ManyToOne da Foo a Bar :

@Entity public class Foo { @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) private Bar bar; // other mappings, getters and setters }

Quando cancelliamo un'istanza Bar a cui fa riferimento un'istanza Foo che viene caricata anche nel contesto di persistenza, l' istanza Bar non verrà rimossa dal database:

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 la barra rimossa è referenziata da un Foo , l' operazione PERSIST viene eseguita in cascata da Foo a Bar perché l'associazione è contrassegnata con cascade = CascadeType.ALL e l'eliminazione non è pianificata. Per verificare che ciò avvenga, è possibile abilitare il livello di log di traccia per il pacchetto org.hibernate e cercare voci come l' eliminazione dell'entità annullata dalla pianificazione .

4. Eliminazione a cascata

L'eliminazione può essere eseguita in cascata alle entità figlio quando i genitori vengono rimossi:

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());

Qui la barra viene rimossa perché la rimozione viene eseguita a cascata da foo , poiché l'associazione viene dichiarata a cascata tutte le operazioni del ciclo di vita da Foo a Bar .

Si noti che si tratta quasi sempre di un bug a cascata REMOVE funzionamento in un @ManyToMany associazione , perché sarebbe innescare istanze secondarie rimozione che possono essere associati con altre istanze genitore. Questo vale anche per CascadeType.ALL , poiché significa che tutte le operazioni devono essere collegate a cascata, inclusa l' operazione REMOVE .

5. Rimozione degli orfani

La direttiva orphanRemoval dichiara che le istanze di entità associate devono essere rimosse quando vengono dissociate dal genitore, o in modo equivalente quando il genitore viene rimosso.

Lo dimostriamo definendo tale associazione da Bar a Baz:

@Entity public class Bar { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List bazList = new ArrayList(); // other mappings, getters and setters }

Quindi un'istanza Baz viene eliminata automaticamente quando viene rimossa dall'elenco di un'istanza Bar padre :

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());

The semantics of the orphanRemoval operation is completely similar to a REMOVE operation applied directly to the affected child instances, which means that the REMOVE operation is further cascaded to nested children. As a consequence, you have to ensure that no other instances reference the removed ones (otherwise they are re-persisted).

6. Deletion Using a JPQL Statement

Hibernate supports DML-style delete operations:

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());

It is important to note that DML-style JPQL statements affect neither the state nor life cycle of entity instances that are already loaded into the persistence context, so it is recommended that they are executed prior to loading the affected entities.

7. Deletion Using Native Queries

Sometimes we need to fall back to native queries to achieve something that is not supported by Hibernate or is specific to a database vendor. We may also delete data in the database with native queries:

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());

The same recommendation applies to native queries as for JPA DML-style statements, i.e. native queries affect neither the state nor life cycle of entity instances which are loaded into the persistence context prior to execution of the queries.

8. Soft Deletion

Often it is not desirable to remove data from a database because of auditing purposes and keeping history. In such situations, we may apply a technique called soft deletes. Basically, we just mark a row as removed and we filter it out when retrieving data.

In order to avoid lots of redundant conditions in where clauses in all the queries that read soft-deletable entities, Hibernate provides the @Where annotation which can be placed on an entity and which contains an SQL fragment that is automatically added to SQL queries generated for that entity.

To demonstrate this, we add the @Where annotation and a column named DELETED to the Foo entity:

@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; } }

The following test confirms that everything works as expected:

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

In this article, we looked at different ways in which data can be deleted with Hibernate. We explained basic concepts and some best practices. We also demonstrated how soft-deletes can be easily implemented with Hibernate.

L'implementazione di questo tutorial sull'eliminazione di oggetti con Hibernate è disponibile su Github. Questo è un progetto basato su Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.