Suppression d’objets avec Hibernate

1. Vue d’ensemble

En tant que structure ORM complète, Hibernate est responsable de la gestion du cycle de vie des objets persistants (entités), y compris des opérations CRUD telles que read , save , update et delete .

Dans cet article, nous explorons différentes manières de supprimer des objets d’une base de données à l’aide d’Hibernate ** et nous expliquons les problèmes courants et les pièges qui peuvent survenir.

Nous utilisons JPA et ne faisons que revenir en arrière et utilisons l’API native Hibernate pour les fonctionnalités non normalisées dans JPA.

2. Différentes manières de supprimer des objets

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

  • En utilisant EntityManager.remove

  • Lorsqu’une suppression est effectuée en cascade à partir d’autres instances d’entités

  • Quand un orphanRemoval est appliqué

  • En exécutant une instruction delete JPQL

  • En exécutant des requêtes natives

  • En appliquant une technique de suppression logicielle (filtrage des entités supprimées progressivement)

par une condition dans une clause @ Where )

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

3. Suppression à l’aide du gestionnaire d’entités

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 removed et la suppression associée de la base de données se produit lors du vidage suivant.

Notez que l’instance supprimée est à nouveau persistante si une opération PERSIST lui est appliquée . Une erreur courante consiste à ignorer qu’une opération PERSIST a été appliquée à une instance supprimée (généralement, car elle est mise en cascade depuis une autre instance au moment du vidage), car la section 3.2.2 de http://download.oracle . com/otndocs/jcp/persistence-2__1-fr-eval-spec/index.html[spécification JPA]indique que cette instance doit être conservée de 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 le Bar supprimé est référencé 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 un-scheduling entity deletion .

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, bar est supprimé car celui-ci est mis en cascade depuis foo , l’association étant déclarée pour cascader toutes les opérations de cycle de vie de Foo à Bar .

Notez que il est presque toujours un bogue de mettre en cascade l’opération REMOVE dans une association @ ManyToMany ** , car cela entraînerait la suppression d’instances enfants pouvant être associées à d’autres instances parent. Ceci 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. Enlèvement des orphelins

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

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

@Entity
public class Bar {
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Baz> bazList = new ArrayList<>();

   //other mappings, getters and setters
}

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

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 tout à fait similaire à une opération REMOVE appliquée directement aux instances enfant affectées ** , ce qui signifie que l’opération REMOVE est ensuite cascadée aux enfants imbriqués. Par conséquent, vous devez vous assurer qu’aucune autre instance ne fait référence à celles supprimées (sinon, elles sont ré-persistantes).

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

Hibernate prend en charge les opérations de suppression de type 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és 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

Nous avons parfois besoin de recourir à des requêtes natives pour réaliser quelque chose qui n’est pas pris en charge par Hibernate ou qui est spécifique à un fournisseur de base de données.

Nous pouvons également supprimer des données dans la base de données avec des requêtes natives:

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

La même recommandation s’applique aux requêtes natives comme aux instructions de style JPA DML, c’est-à-dire que les requêtes natives n’affectent ni l’état ni le cycle de vie des instances d’entités chargées dans le contexte de persistance avant l’exécution des requêtes .

8. Suppression douce

Souvent, il n’est pas souhaitable de supprimer les données d’une base de données à des fins d’audit et de conservation de l’historique. Dans de telles situations, nous pouvons appliquer une technique appelée suppression douce. Fondamentalement, nous marquons simplement une ligne comme étant supprimée et nous la filtrons lors de la récupération des données.

Afin d’éviter de nombreuses conditions redondantes dans les clauses where de toutes les requêtes lisant des entités supprimables à la lumière, Hibernate fournit l’annotation @ Where pouvant être placée sur une entité et contenant un fragment SQL ajouté automatiquement aux requêtes SQL générées pour cette entité.

Pour illustrer cela, nous ajoutons l’annotation @ Where et une colonne nommée DELETED à l’entité 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;
    }
}

Le test suivant confirme que tout fonctionne comme prévu:

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

Dans cet article, nous avons examiné différentes manières de supprimer des données avec Hibernate. Nous avons expliqué les concepts de base et certaines meilleures pratiques. Nous avons également montré comment la suppression douce peut être facilement mise en œuvre avec Hibernate.

L’implémentation de ce didacticiel Suppression d’objets avec Hibernate est disponible à l’adresse over sur Github . Ceci est un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.