Suppression d'objets avec Hibernate

1. Vue d'ensemble

En tant que cadre ORM complet, Hibernate est responsable de la gestion du cycle de vie des objets persistants (entités), y compris les opérations CRUD telles que la lecture , l' enregistrement , la mise à jour et la suppression .

Dans cet article, nous explorons différentes manières dont les objets peuvent être supprimés d'une base de données à l'aide de Hibernate et nous expliquons les problèmes courants et les pièges qui peuvent survenir.

Nous utilisons JPA et prenons du recul et n'utilisons l'API native Hibernate que pour les fonctionnalités qui ne sont pas standardisées dans JPA.

2. Différentes façons de supprimer des objets

Les objets peuvent être supprimés dans les scénarios suivants:

  • En utilisant EntityManager.remove
  • Lorsqu'une suppression est mise en cascade à partir d'autres instances d'entité
  • Lorsqu'un OrphanRemoval est appliqué
  • En exécutant une instruction de suppression JPQL
  • En exécutant des requêtes natives
  • En appliquant une technique de suppression réversible (filtrage des entités supprimées de manière réversible par une condition dans une clause @Where )

Dans la suite de l'article, nous examinons ces points en détail.

3. Suppression à l'aide d'Entity Manager

La suppression avec EntityManager est le moyen le plus simple de supprimer une instance d'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()); 

Dans les exemples de cet article, nous utilisons une méthode d'assistance pour vider et effacer le contexte de persistance si nécessaire:

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

Après avoir appelé la méthode EntityManager.remove , l'instance fournie passe à l' état supprimé et la suppression associée de la base de données se produit au prochain vidage.

Notez que l' instance supprimée est de nouveau persistante si une opération PERSIST lui est appliquée . Une erreur courante est d'ignorer qu'une opération PERSIST a été appliquée à une instance supprimée (généralement, parce qu'elle est mise en cascade à partir d'une autre instance au moment du vidage), car la section 3.2.2 de la spécification JPA exige qu'une telle instance soit persistait à nouveau dans un tel cas.

Nous illustrons cela en définissant une association @ManyToOne de Foo à Bar :

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

Lorsque nous supprimons une instance Bar référencée par une instance Foo qui est également chargée dans le contexte de persistance, l' instance Bar ne sera pas supprimée de la base de données:

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

Si la barre supprimée est référencée par un Foo , l' opération PERSIST est mise en cascade de Foo à Bar car l'association est marquée avec cascade = CascadeType.ALL et la suppression n'est pas planifiée. Pour vérifier que cela se produit, nous pouvons activer le niveau de journal de suivi pour le package org.hibernate et rechercher des entrées telles que l' annulation de la planification de la suppression d'entités .

4. Suppression en cascade

La suppression peut être appliquée en cascade aux entités enfants lorsque les parents sont supprimés:

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

Ici, la barre est supprimée car la suppression est en cascade de foo , car l'association est déclarée pour mettre en cascade toutes les opérations du cycle de vie de Foo à Bar .

Notez que l' opération REMOVE en cascade dans une association @ManyToMany est presque toujours un bogue , car cela déclencherait la suppression des instances enfants qui peuvent être associées à d'autres instances parentes. Cela s'applique également à CascadeType.ALL , car cela signifie que toutes les opérations doivent être mises en cascade, y compris l' opération REMOVE .

5. Suppression des orphelins

La directive orphanRemoval déclare que les instances d'entité associées doivent être supprimées lorsqu'elles sont dissociées du parent, ou de manière équivalente lorsque le parent est supprimé.

Nous le montrons en définissant une telle association de Bar à Baz:

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

Ensuite, une instance Baz est automatiquement supprimée lorsqu'elle est supprimée de la liste d'une instance Bar parent :

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

La sémantique de l' opération orphanRemoval est complètement similaire à une opération REMOVE appliquée directement aux instances enfants affectées , ce qui signifie que l' opération REMOVE est ensuite mise en cascade sur les enfants imbriqués. En conséquence, vous devez vous assurer qu'aucune autre instance ne fait référence aux instances supprimées (sinon, elles sont à nouveau persistantes).

6. Suppression à l'aide d'une instruction JPQL

Hibernate prend en charge les opérations de suppression de style 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());

Il est important de noter que les instructions JPQL de style DML n'affectent ni l'état ni le cycle de vie des instances d'entité déjà chargées dans le contexte de persistance , il est donc recommandé de les exécuter avant de charger les entités affectées.

7. Suppression à l'aide de requêtes natives

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'implémentation de ce didacticiel sur la suppression d'objets avec Hibernate est disponible à l'adresse over sur Github. Il s'agit d'un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.