Cycle de vie de l'entité Hibernate

1. Vue d'ensemble

Chaque entité Hibernate a naturellement un cycle de vie dans le cadre - il est soit dans un état transitoire, géré, détaché ou supprimé.

Comprendre ces états à la fois au niveau conceptuel et technique est essentiel pour pouvoir utiliser Hibernate correctement.

Pour en savoir plus sur les différentes méthodes Hibernate qui traitent des entités, consultez l'un de nos précédents tutoriels.

2. Méthodes d'assistance

Tout au long de ce didacticiel, nous utiliserons systématiquement plusieurs méthodes d'assistance:

  • HibernateLifecycleUtil.getManagedEntities (session) - nous l'utiliserons pour récupérer toutes les entités gérées à partir du magasin interne d'une session
  • DirtyDataInspector.getDirtyEntities () - nous allons utiliser cette méthode pour obtenir une liste de toutes les entités marquées comme `` sales ''
  • HibernateLifecycleUtil.queryCount (requête) - une méthode pratique pour effectuer une requête count (*) sur la base de données intégrée

Toutes les méthodes d'aide ci-dessus sont importées statiquement pour une meilleure lisibilité. Vous pouvez trouver leurs implémentations dans le projet GitHub lié à la fin de cet article.

3. Tout est question de contexte de persistance

Avant d'entrer dans le sujet du cycle de vie des entités, nous devons d'abord comprendre le contexte de persistance .

En termes simples, le contexte de persistance se situe entre le code client et le magasin de données . C'est une zone de transit où les données persistantes sont converties en entités, prêtes à être lues et modifiées par le code client.

Théoriquement parlant, le contexte de persistance est une implémentation du modèle d'unité de travail. Il garde une trace de toutes les données chargées, suit les modifications de ces données et est chargé de synchroniser éventuellement les modifications dans la base de données à la fin de la transaction commerciale.

JPA EntityManager et Hibernate's Session sont une implémentation du concept de contexte de persistance . Tout au long de cet article, nous utiliserons Hibernate Session pour représenter le contexte de persistance.

L'état du cycle de vie de l'entité Hibernate explique comment l'entité est liée à un contexte de persistance , comme nous le verrons ensuite.

4. Entité gérée

Une entité gérée est une représentation d'une ligne de table de base de données (bien que cette ligne ne doive pas encore exister dans la base de données).

Ceci est géré par la session en cours d'exécution , et chaque modification apportée sera suivie et propagée automatiquement à la base de données .

La session charge l'entité à partir de la base de données ou attache à nouveau une entité détachée. Nous discuterons des entités détachées dans la section 5.

Observons du code pour obtenir des éclaircissements.

Notre exemple d'application définit une entité, la classe FootballPlayer . Au démarrage, nous initialiserons le magasin de données avec quelques exemples de données:

+-------------------+-------+ | Name | ID | +-------------------+-------+ | Cristiano Ronaldo | 1 | | Lionel Messi | 2 | | Gianluigi Buffon | 3 | +-------------------+-------+

Disons que nous voulons changer le nom de Buffon pour commencer - nous voulons mettre son nom complet Gianluigi Buffon au lieu de Gigi Buffon.

Tout d'abord, nous devons commencer notre unité de travail en obtenant une Session:

Session session = sessionFactory.openSession();

Dans un environnement serveur, nous pouvons injecter une Session dans notre code via un proxy contextuel. Le principe reste le même: nous avons besoin d'une Session pour encapsuler la transaction commerciale de notre unité de travail.

Ensuite, nous demanderons à notre session de charger les données du magasin persistant:

assertThat(getManagedEntities(session)).isEmpty(); List players = s.createQuery("from FootballPlayer").getResultList(); assertThat(getManagedEntities(session)).size().isEqualTo(3); 

Lorsque nous obtenons une session pour la première fois , son magasin de contexte persistant est vide, comme le montre notre première instruction assert .

Ensuite, nous exécutons une requête qui récupère les données de la base de données, crée une représentation d'entité des données et renvoie finalement l'entité que nous pouvons utiliser.

En interne, la session garde une trace de toutes les entités qu'elle charge dans le magasin de contexte persistant. Dans notre cas, le magasin interne de la Session contiendra 3 entités après la requête.

Maintenant, changeons le nom de Gigi:

Transaction transaction = session.getTransaction(); transaction.begin(); FootballPlayer gigiBuffon = players.stream() .filter(p -> p.getId() == 3) .findFirst() .get(); gigiBuffon.setName("Gianluigi Buffon"); transaction.commit(); assertThat(getDirtyEntities()).size().isEqualTo(1); assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Gianluigi Buffon");

4.1. Comment ça marche?

Lors de l'appel à transaction commit () ou flush () , la session trouvera toutes les entités sales de sa liste de suivi et synchronisera l'état avec la base de données.

Notez que nous n'avons pas besoin d'appeler de méthode pour informer Session que nous avons changé quelque chose dans notre entité - puisqu'il s'agit d'une entité gérée, toutes les modifications sont propagées automatiquement à la base de données.

Une entité gérée est toujours une entité persistante - elle doit avoir un identifiant de base de données, même si la représentation de ligne de base de données n'est pas encore créée, c'est-à-dire que l'instruction INSERT est en attente de la fin de l'unité de travail.

Voir le chapitre sur les entités transitoires ci-dessous.

5. Entité détachée

Une entité détachée est juste une entité POJO ordinaire dont la valeur d'identité correspond à une ligne de base de données. La différence avec une entité gérée est qu'elle n'est plus suivie par aucun contexte de persistance .

Une entité peut se détacher lorsque la session utilisée pour la charger a été fermée, ou lorsque nous appelons Session.evict (entité) ou Session.clear () .

Voyons cela dans le code:

FootballPlayer cr7 = session.get(FootballPlayer.class, 1L); assertThat(getManagedEntities(session)).size().isEqualTo(1); assertThat(getManagedEntities(session).get(0).getId()).isEqualTo(cr7.getId()); session.evict(cr7); assertThat(getManagedEntities(session)).size().isEqualTo(0);

Notre contexte de persistance ne suivra pas les changements dans les entités détachées:

cr7.setName("CR7"); transaction.commit(); assertThat(getDirtyEntities()).isEmpty();

Session.merge (entité) /Session.update (entité) peut (re) joindre une session :

FootballPlayer messi = session.get(FootballPlayer.class, 2L); session.evict(messi); messi.setName("Leo Messi"); transaction.commit(); assertThat(getDirtyEntities()).isEmpty(); transaction = startTransaction(session); session.update(messi); transaction.commit(); assertThat(getDirtyEntities()).size().isEqualTo(1); assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Leo Messi");

Pour plus d'informations sur Session.merge () et Session.update (), voir ici.

5.1. Le champ d'identité est tout ce qui compte

Jetons un œil à la logique suivante:

FootballPlayer gigi = new FootballPlayer(); gigi.setId(3); gigi.setName("Gigi the Legend"); session.update(gigi);

Dans l'exemple ci-dessus, nous avons instancié une entité de la manière habituelle via son constructeur. Nous avons rempli les champs avec des valeurs et nous avons défini l'identité sur 3, ce qui correspond à l'identité des données persistantes appartenant à Gigi Buffon. L'appel de update () a exactement le même effet que si nous avons chargé l'entité à partir d'un autre contexte de persistance .

En fait, Session ne distingue pas l'origine d'une entité réattachée.

C'est un scénario assez courant dans les applications Web pour construire des entités détachées à partir de valeurs de formulaire HTML.

En ce qui concerne Session , une entité détachée n'est qu'une entité simple dont la valeur d'identité correspond à des données persistantes.

Be aware that the example above just serves a demo purpose. and we need to know exactly what we're doing. Otherwise, we could end up with null values across our entity if we just set the value on the field we want to update, leaving the rest untouched (so, effectively null).

6. Transient Entity

A transient entity is simply an entity object that has no representation in the persistent store and is not managed by any Session.

A typical example of a transient entity would be instantiating a new entity via its constructor.

To make a transient entity persistent, we need to call Session.save(entity) or Session.saveOrUpdate(entity):

FootballPlayer neymar = new FootballPlayer(); neymar.setName("Neymar"); session.save(neymar); assertThat(getManagedEntities(session)).size().isEqualTo(1); assertThat(neymar.getId()).isNotNull(); int count = queryCount("select count(*) from Football_Player where name="Neymar""); assertThat(count).isEqualTo(0); transaction.commit(); count = queryCount("select count(*) from Football_Player where name="Neymar""); assertThat(count).isEqualTo(1);

As soon as we execute Session.save(entity), the entity is assigned an identity value and becomes managed by the Session. However, it might not yet be available in the database as the INSERT operation might be delayed until the end of the unit of work.

7. Deleted Entity

An entity is in a deleted (removed) state if Session.delete(entity) has been called, and the Session has marked the entity for deletion. The DELETE command itself might be issued at the end of the unit of work.

Let's see it in the following code:

session.delete(neymar); assertThat(getManagedEntities(session).get(0).getStatus()).isEqualTo(Status.DELETED);

However, notice that the entity stays in the persistent context store until the end of the unit of work.

8. Conclusion

Le concept de contexte de persistance est essentiel pour comprendre le cycle de vie des entités Hibernate. Nous avons clarifié le cycle de vie en examinant les exemples de code illustrant chaque statut.

Comme d'habitude, le code utilisé dans cet article se trouve à l'adresse over sur GitHub.