États des objets dans la session d'Hibernate

1. Introduction

Hibernate est un cadre pratique pour gérer les données persistantes, mais comprendre son fonctionnement en interne peut parfois être délicat.

Dans ce didacticiel, nous allons découvrir les états des objets et comment se déplacer entre eux. Nous examinerons également les problèmes que nous pouvons rencontrer avec les entités détachées et comment les résoudre.

2. Session d'Hibernate

L' interface de session est le principal outil utilisé pour communiquer avec Hibernate. Il fournit une API nous permettant de créer, lire, mettre à jour et supprimer des objets persistants. La session a un cycle de vie simple. Nous l'ouvrons, effectuons certaines opérations, puis le fermons.

Lorsque nous opérons sur les objets pendant la session , ils s'attachent à cette session . Les modifications que nous apportons sont détectées et enregistrées à la fermeture. Après la fermeture, Hibernate rompt les connexions entre les objets et la session.

3. États des objets

Dans le contexte de la session d'Hibernate , les objets peuvent être dans l'un des trois états possibles: transitoire, persistant ou détaché.

3.1. Transitoire

Un objet que nous n'avons attaché à aucune session est à l'état transitoire. Comme il n'a jamais été conservé, il n'a aucune représentation dans la base de données. Parce qu'aucune session n'en a connaissance, elle ne sera pas enregistrée automatiquement.

Créons un objet utilisateur avec le constructeur et confirmons qu'il n'est pas géré par la session:

Session session = openSession(); UserEntity userEntity = new UserEntity("John"); assertThat(session.contains(userEntity)).isFalse();

3.2. Persistant

Un objet que nous avons associé à une session est dans l'état persistant. Nous l'avons enregistré ou lu à partir d'un contexte de persistance, il représente donc une ligne dans la base de données.

Créons un objet, puis utilisons la méthode persist pour le rendre persistant:

Session session = openSession(); UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); assertThat(session.contains(userEntity)).isTrue();

Alternativement, nous pouvons utiliser la méthode save . La différence est que la méthode persist enregistrera simplement un objet et que la méthode save générera également son identifiant si nécessaire.

3.3. Détaché

Lorsque nous fermons la session , tous les objets qu'elle contient se détachent. Bien qu'ils représentent toujours des lignes dans la base de données, ils ne sont plus gérés par aucune session :

session.persist(userEntity); session.close(); assertThat(session.isOpen()).isFalse(); assertThatThrownBy(() -> session.contains(userEntity));

Ensuite, nous allons apprendre à enregistrer des entités transitoires et détachées.

4. Enregistrer et rattacher une entité

4.1. Enregistrer une entité transitoire

Créons une nouvelle entité et sauvegardons-la dans la base de données. Lorsque nous construisons l'objet pour la première fois, il sera à l'état transitoire.

Pour conserver notre nouvelle entité, nous utiliserons la méthode persist :

UserEntity userEntity = new UserEntity("John"); session.persist(userEntity);

Maintenant, nous allons créer un autre objet avec le même identifiant que le premier. Ce deuxième objet est transitoire car il n'est encore géré par aucune session , mais nous ne pouvons pas le rendre persistant en utilisant la méthode persist . Il est déjà représenté dans la base de données, donc ce n'est pas vraiment nouveau dans le contexte de la couche de persistance.

Au lieu de cela, nous utiliserons la méthode de fusion pour mettre à jour la base de données et rendre l'objet persistant :

UserEntity onceAgainJohn = new UserEntity("John"); session.merge(onceAgainJohn);

4.2. Enregistrer une entité détachée

Si nous fermons la session précédente , nos objets seront dans un état détaché. De la même manière que dans l'exemple précédent, ils sont représentés dans la base de données, mais ils ne sont actuellement gérés par aucune session . Nous pouvons les rendre à nouveau persistants en utilisant la méthode de fusion :

UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); session.close(); session.merge(userEntity);

5. Entités imbriquées

Les choses se compliquent quand on considère les entités imbriquées. Supposons que notre entité utilisateur stocke également des informations sur son manager:

public class UserEntity { @Id private String name; @ManyToOne private UserEntity manager; }

Lorsque nous sauvegardons cette entité, nous devons penser non seulement à l'état de l'entité elle-même, mais également à l'état de l'entité imbriquée. Créons une entité utilisateur persistante, puis définissons son gestionnaire:

UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); UserEntity manager = new UserEntity("Adam"); userEntity.setManager(manager);

Si nous essayons de le mettre à jour maintenant, nous obtiendrons une exception:

assertThatThrownBy(() -> { session.saveOrUpdate(userEntity); transaction.commit(); });
java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.baeldung.states.UserEntity.manager -> com.baeldung.states.UserEntity 

Cela se produit parce qu'Hibernate ne sait pas quoi faire avec l'entité imbriquée transitoire.

5.1. Entités imbriquées persistantes

Une façon de résoudre ce problème consiste à conserver explicitement les entités imbriquées:

UserEntity manager = new UserEntity("Adam"); session.persist(manager); userEntity.setManager(manager);

Ensuite, après avoir validé la transaction, nous pourrons récupérer l'entité correctement enregistrée:

transaction.commit(); session.close(); Session otherSession = openSession(); UserEntity savedUser = otherSession.get(UserEntity.class, "John"); assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

5.2. Opérations en cascade

Les entités imbriquées transitoires peuvent être persistantes automatiquement si nous configurons correctement la propriété en cascade de la relation dans la classe d'entité:

@ManyToOne(cascade = CascadeType.PERSIST) private UserEntity manager;

Maintenant, lorsque nous persistons l'objet, cette opération sera mise en cascade sur toutes les entités imbriquées:

UserEntityWithCascade userEntity = new UserEntityWithCascade("John"); session.persist(userEntity); UserEntityWithCascade manager = new UserEntityWithCascade("Adam"); userEntity.setManager(manager); // add transient manager to persistent user session.saveOrUpdate(userEntity); transaction.commit(); session.close(); Session otherSession = openSession(); UserEntityWithCascade savedUser = otherSession.get(UserEntityWithCascade.class, "John"); assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

6. Résumé

Dans ce didacticiel, nous avons examiné de plus près le fonctionnement de la session Hibernate en ce qui concerne l'état des objets. Nous avons ensuite inspecté certains problèmes qu'il peut créer et comment les résoudre.

Comme toujours, le code source est disponible à l'adresse over sur GitHub.