> ## Content Index
> Fetch the complete content index at: https://www.sfeir.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Hibernate 5 et la duplication d’entités : petit plongeon dans le code
- URL: https://www.sfeir.dev/back/hibernate-5-et-la-duplication-dentites-plongeons-dans-le-code-dhibernate/
- Published: 2024-02-06T08:30:00.000Z
- Updated: 2025-04-25T12:39:44.000Z
- Description: Hibernate et le problème des entités dupliquées. C'est un problème connu mais savez-vous pourquoi cela ce produit ? Explorons dans les bas-fonds du code pour comprendre ce phénomène !
- Author: Ling-Chun SO
- Tags: Back, Hibernate

Si vous avez déjà travaillé sur une application utilisant Hibernate, vous avez probablement déjà rencontré le problème de duplication d’entités. Lorsque que vous récupérez une collection d’entités, vous vous retrouvez avec un ensemble comprenant des doublons de la même instance.

C’est une problématique plutôt connue et il existe plusieurs moyens pour le résoudre, que cela soit par l’utilisation du mot-clé **`DISTINCT`**, la collection **`Set`** ou encore les EntityGraph de JPA. Cependant, il m’est arrivé d’avoir un comportement étrange avec les EntityGraph, celui-ci retournant des entités dupliquées ! Je vous propose ici de plonger [sous les entrailles d’Hibernate 5](https://www.sfeir.dev/back/traquer-une-fuite-memoire-cas-detude-avec-hibernate-5-ne-tombez-pas-dans-le-in/) pour comprendre tout les tenants et aboutissant de ce comportement.

## Une petite application Spring Boot

Pour démontrer ce comportement, prenons un simple projet en exemple, à savoir Spring Boot 2.7 connecté à une base de données : [https://github.com/Shaolans/HibernateEntityGraphDeduplicationIssue](https://github.com/Shaolans/HibernateEntityGraphDeduplicationIssue?ref=sfeir.dev)

Le schéma de la base de données est un modèle simple :

![eee](https://lh7-us.googleusercontent.com/B1GUip5FYvVimROZSqJq2wjAJ9sCr2oDH8TZlKYT1nDv_9t0sgU6tcGJZEFx683M2OYzmfR20Gh403JlSkDwLA1caqViM1cRY0CnimbR_veybtilMFBKiNz3yi39xIzdrT6Kd2bVUtgCi_tu1ZuaMPo)

Schéma de la base de données

Les entités Hibernate sont définies comme suit :

![](https://lh7-us.googleusercontent.com/5xxjMlnxrV_7ImmwCLkZv1_ZHdRhLdBjSrgZaPyVxrAuZNw72uzVoQUwjwkRlNVkjOLKo94ookO-8UJUYTFVVXNBBppIQNjRxBAdH_0tQYwufKUEi7itjRz3UB78SePxuxei2wYKSQ3RtNK8qNrZBFc)

Définition des classes Java

Nous allons nous concentrer sur les requêtes générées par ce référentiel JPA :

```java
@Repository
public interface AuthorRepository extends JpaRepository<Author, Integer> {

    @Query("SELECT a FROM Author a LEFT JOIN FETCH a.books")
    List<Author> findAuthorsLeftJoinFetchNoEntityGraph();

    @EntityGraph(attributePaths = "books")
    @Query("FROM Author a LEFT JOIN a.books")
    List<Author> findAuthorsLeftJoinWithEntityGraph();

    @Query("FROM Author a LEFT JOIN a.books")
    List<Author> findAuthorsLeftJoinWithoutEntityGraph();

    @EntityGraph(attributePaths = "books")
    @Query("FROM Author a INNER JOIN a.agent WHERE a.agent.name like '%John%'")
    List<Author> findAuthorsWithJoinNotFetchedAndEntityGraph();

    @EntityGraph(attributePaths = "books")
    @Query("SELECT a FROM Author a INNER JOIN a.agent WHERE a.agent.name like '%John%'")
    List<Author> findAuthorsWithJoinNotFetchedAndEntityGraphAndSelect();
}

```

## Duplication d'entités

### Origine

Pour comprendre pourquoi la duplication d'entités se produit, considérons un cas d'utilisation où nous souhaitons récupérer tous les auteurs et leurs livres. Afin d'éviter le problème de [requête N+1](https://vladmihalcea.com/n-plus-1-query-problem/?ref=sfeir.dev), ajoutons dans la requête JPQL le mot-clé **`FETCH`**. Cela permet la récupération à la fois des auteurs et de leurs livres associés dans une seule requête SQL, éliminant ainsi la nécessité de récupérer individuellement les livres pour chaque auteur.

```java
@Query("SELECT a FROM Author a LEFT JOIN FETCH a.books")
List<Author> findAuthorsLeftJoinFetchNoEntityGraph();
```

L'invocation de cette méthode produit à la compilation d'une requête SQL équivalente à la suivante :

```SQL
SELECT A, B FROM AUTHOR A LEFT JOIN BOOK B ON A.ID = B.BOOK_AUTHOR_ID;
```

Dans le cas où vous avez 2 auteurs ayant respectivement 2 et 3 livres, [le résultat de la requête SQL](https://www.sfeir.dev/back/des-automates-declaratifs/) produira par exemple le tableau suivant :

![](https://lh7-us.googleusercontent.com/woabPvuS2z_kskU0WWQZpBMizqWUBlr6IdLJ2qGy0sepK1NnwVcTzp8jB08FMGyYURRmSlrw2W6Po5BTLph3RXU8a9l1x-8-8vByjTJd0PiGmQ8iktJmxT75wgB6yCg2meAB82KkghSPhYhXZvyLdkE)

Résultat de la requête SQL

En fonction du type de jointure, l'association entre les tables **`Author`** et **`Book`** est établie en fonction de la condition spécifiée (ici la clé primaire de **`Author`**). Cela produira, pour chaque livre correspondant à un auteur, une nouvelle ligne contenant les colonnes de la table **`Book`** ainsi que les colonnes de la table **`Author`**.

Hibernate, lorsqu'il récupère cet ensemble de résultats, est assez intelligent pour résoudre l'entité elle-même sans la dupliquer (il n’existe qu’une seule instance pour une entité). Dans notre cas, nous obtenons seulement deux instances de l'entité **`Author`**. Cependant, il gère la résolution ligne par ligne et ne dé-duplique pas ces lignes par défaut.

Cela explique pourquoi la liste retournée en appelant **`findAuthorsLeftJoinFetchNoEntityGraph`** renvoie ce qui suit :

```java
[Author@5640, Author@5640, Author@5641, Author@5641, Author@5641]
```

**`Author@5640`** étant l'instance représentant l'entité **`Author`** de J.R.R Tolkien et **`Author@5641`** représentant l’entité **`Author`** Isaac Asimov.

Cette duplication est principalement observée dans les relations **`@OneToMany`** et **`@ManyToMany`**, car la jointure produite par ces types relations peut correspondre à plusieurs lignes de la table associée. En revanche, les relations **`@OneToOne`** et **`@ManyToOne`** correspondent au plus à une seule entrée dans la table associée et ne provoquent pas de duplication.

### Solutions

Bien que ce problème de duplication soit assez courant et se produit souvent lors du développement d'applications, il existe des moyens de résoudre ce problème.

#### Set

Une solution simple consiste à utiliser le type de retour **`Set<Author>`** au lieu de **`List<Author>`** :

```java
@Query("FROM Author a LEFT JOIN FETCH a.books")
Set<Author> findAuthorsLeftJoinFetchNoEntityGraph();
```

Lorsque les résultats sont collectés, les entités dupliquées seront filtrées à travers le **`Set`**.

#### mot-clé `DISTINCT`

Une autre approche consiste à utiliser le mot-clé **`DISTINCT`** :

```java
@Query("SELECT DISTINCT a FROM Author a LEFT JOIN FETCH a.books")
List<Author> findAuthorsLeftJoinFetchNoEntityGraph();
```

Il convient de noter que l'utilisation de **`DISTINCT`** a deux significations sémantiques :

- **SQL** : La signification SQL conventionnelle implique le filtrage des valeurs en double dans les colonnes sélectionnées.
- **Hibernate** : Cela indique le besoin de filtrer les duplications dans l'entité parente.

Lorsque le mot-clé **`DISTINCT`** est utilisé dans une requête JPQL, cela entraîne la génération d'une requête SQL qui inclut le mot-clé **`DISTINCT`** et indique également à Hibernate de filtrer les entités dupliquées. Cependant, l'utilisation de **`DISTINCT`** en SQL entraîne un certain coût lors de son exécution au niveau de la base de données. Dans certains cas, l'objectif est de filtrer uniquement les entités dupliquées induites par les jointures.

Pour de tels cas, nous pouvons utiliser le paramètre d’Hibernate **`HINT_PASS_DISTINCT_THROUGH`** et éviter la génération de **`DISTINCT`** dans la requête SQL :

```java
@QueryHints(@QueryHint(name = HINT_PASS_DISTINCT_THROUGH, value = "false"))
@Query("SELECT DISTINCT a FROM Author a LEFT JOIN FETCH a.books")
Set<Author> findDistinctAuthorsLeftJoinFetchNoEntityGraph();
```

#### EntityGraph

Depuis JPA 2.1, EntityGraph est une fonctionnalité qui a été introduite et permettant aux développeurs de définir les stratégies de chargement des entités. Dans notre cas, nous pouvons spécifier facilement avec l'annotation **`@EntityGraph`** le chargement des livres en même temps que les auteurs, comme illustré ci-dessous :

```java
@EntityGraph(attributePaths = "books")
@Query("FROM Author a LEFT JOIN a.books WHERE a.books.size > 2")
List<Author> findAuthorsLeftJoinWithEntityGraph();
```

Cette requête JPQL est conçue pour récupérer tous les auteurs (ainsi que leurs livres respectifs) qui ont plus de 2 livres. Même si nous n'avons pas ajouté explicitement le mot-clé **`FETCH`** dans la requête SQL, grâce à l'EntityGraph, les livres seront chargés et l'entité parente **`Author`** ne sera pas dupliquée.

**Note** : Ceci est un exemple simple d'utilisation d'EntityGraph. Il existe des façons plus complètes et sophistiquées de l'utiliser, mais ce n'est pas le but de cet article.

## EntityGraph et la duplication d’entités

Bien qu'il soit établi que les EntityGraph gère la déduplication des entités, examinons un scénario plus complexe.

Considérons l'entité **`Author`**, qui a une relation **`ManyToOne`** avec l’entité **`Agent`**. Supposons que nous voulons récupérer tous les auteurs et leurs livres dont leur agent a un nom contenant "John", mais que pour une raison arbitraire nous n’avons pas besoin d’exploiter dans notre code l’entité **`Agent`**, il est donc inutile de le récupérer. Dans ce cas, nous pouvons accomplir cela avec le code suivant :

```java
@EntityGraph(attributePaths = "books")
@Query("FROM Author a INNER JOIN a.agent WHERE a.agent.name like '%John%'")
List<Author> findAuthorsWithJoinNotFetchedAndEntityGraph();
```

Étant donné l’utilisation d'EntityGraph, nous ne devrions pas avoir d’entités dupliquées, mais lors de l'exécution de la méthode, le résultat est inattendu :

![](https://lh7-us.googleusercontent.com/KU8PaB_09pVtEPomzXu_7Xo_2Lq6quLDkJEf4EL3yiMLI3tVqqHNzuHNP8Y5kFTwMLumfq8je0ft1M3316aVvsazRMQSiKncgbV9DFmUCmA4f8pzVrjwYT8qfuKwlsufW-Ll3fpy_U2FPwQz4v0jlJ0)

Résultat de l'appel findAuthorsWithJoinNotFetchedAndEntityGraph

Des entités dupliquées sont récupérées de manière inattendue malgré l'utilisation d'EntityGraph. Pour comprendre les raisons de ce comportement, explorons un peu le code d'Hibernate.

## Comment Hibernate déduplique les entités

Hibernate est un ORM assez complet et complexe, il peut être difficile de comprendre entièrement tous ses mécanismes sous-jacents dans les moindre détails. Cependant dans notre cas, puisque nous voulons comprendre les mécanismes de déduplication, nous nous concentrerons sur la méthode [**list**](https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java?ref=sfeir.dev#L367)de la classe [**QueryTranslatorImpl**](https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java?ref=sfeir.dev).

![](https://lh7-us.googleusercontent.com/ajqzJbvtWYx72gdniuoGDa2XnR7V2vr1HxTuc3deduvFIHq_lfkTAP3s-CAhUO2h1POi43dq733RTLgym7fR_8VdFDlbu2TiSrR0SHPpsov1DSv56t-70YtcSGKchw5mPX1GUMfM3tT0CpJDHRJD-Hs)

Code partiel de l'implémentation de la classe QueryTranslatorImpl

Pour des raisons de clarté, certaines parties du code ont été omises afin de se concentrer sur les points les plus importants.

Lors de l'invocation d'une méthode de la classe **`AuthorRepository`**, l'exécution finira éventuellement par arriver à cet extrait de code, et voici une analyse de la fonctionnalité :

1. Ce snippet calcule un booléen appelé **`needsDistincting`** et comme son nom l'indique, il identifiera si l'ensemble de résultats doit être dédupliqué.  
Lorsque nous utilisons le mot-clé **`DISTINCT`**, un EntityGraph ou limitons la taille de la récupération **ET** qu'il y a une opération **`FETCH`** dans la requête JPQL, la distinction (déduplication) doit être appliquée.  
Nous pouvons observer que chaque fois qu’EntityGraph est utilisé, la distinction doit être appliquée, car celui-ci modifie la requête source pour récupérer (**`FETCH`**) les entités qui ont été fournies dans **`@EntityGraph`**.
2. L'invocation de cette méthode exécutera la requête dans la base de données, récupérera l'ensemble de résultats et résoudra les entités. Cette liste pourrait renvoyer des entités dupliquées.
3. C'est la partie la plus intéressante : lorsque Hibernate doit distinguer/dédupliquer les entités, celui-ci utilise un **`IdentitySet`** pour filtrer les entités. Il s'agit d'une implémentation de `**Set**` qui utilise l'égalité référentielle (`==`) plutôt que **`equals`**.

À ce stade, nous avons établi que l'utilisation d'EntityGraph devrait filtrer les entités dupliquées, mais que s'est-il passé pour causer le comportement précédemment démontré ?

## Comment EntityGraph peut-il retourner duplicats d’entités ?

### Comparons

Afin de mieux comprendre la situation, comparons les résultats de l'appel de `**queryLoader.list( session, queryParametersToUse )**` `(**2**)` lors de l'invocation des méthodes suivantes :

```java
@EntityGraph(attributePaths = "books")
@Query("FROM Author a LEFT JOIN a.books")
List<Author> findAuthorsLeftJoinWithEntityGraph();
```

et

```java
@EntityGraph(attributePaths = "books")
@Query("FROM Author a INNER JOIN a.agent WHERE a.agent.name like '%John%'")
List<Author> findAuthorsWithJoinNotFetchedAndEntityGraph();
```

Lors de l’appel à la méthode **`findAuthorsLeftJoinWithEntityGraph`**, les résultats retournés contiennent :

![](https://lh7-us.googleusercontent.com/kXxtHmBcxA-8AJYanoGKpJAsUB3-u5dOA-ZO9SYiJeFoo7km8Tj4Fy6iAthDWA7tLNc_-KDM5wJ1Cv-RHC7qiHJVRtRRxKd1CmCIrQC3PaxqPJA4My5t7aZYcooL_XwCo50vroaQPOjduxtkWliAdro)

Résultats de l'appel de findAuthorsLeftJoinWithEntityGraph

Étant donné que l'EntityGraph est utilisé, la duplication sera résolue grâce à l'**`IdentitySet`**. Dans une finalité la méthode renverra une liste **`List<Author>`** composée de 3 auteurs/instances.

Cependant, lors de l'appel à **`findAuthorsWithJoinNotFetchedAndEntityGraph`**, les résultats retournés contiennent ce qui suit :

![](https://lh7-us.googleusercontent.com/N321RW8Kkaz0fjoX1WLSjdTypunSVDytpFdXw0tl0CdqMCLUyd2cbvlBTqzsPrSKkHwE93jKnH6fht5Rur0mhYwa73T-Pszp3mOW7Udqb8piy3OP8mypMLtawv_dabN7jPZJlJo71TegDHyVnJ6t2r0)

Résultat de l'appel de findAuthorsWithJoinNotFetchedAndEntityGraph

Dans ce scénario, le résultat n'est pas une liste d'instances **`Author`**, mais plutôt une liste d'instances distinctes de tableaux d'objets, chacune encapsulant des instances **`Author`** et **`Agent`** dupliquées. La raison de la présence d'entités dupliquées devient évidente. Chaque tableau (**`Object[2]@13551`**, **`Object[2]@135512`**, ..., **`Object[2]@13555`**), en tant qu'instance distincte, n'est pas filtré lors du processus de déduplication avec l'**`IdentitySet`**. Par conséquent, tous les tableaux sont considérés comme unique dans le **`Set`**, conduisant à la duplication observée.

### La raison de cette différence

Afin de comprendre la différence entre l'obtention d'une liste d'instances **`Author`** dans le premier cas et d'une liste de tableaux d'instances **`Author`** et **`Agent`** dans le deuxième cas, nous devons explorer plus en profondeur. Jetons un coup d'œil à la classe [**QueryLoader**](https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java?ref=sfeir.dev) et en particulier à la méthode [**initialize**](https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java?ref=sfeir.dev#L113).

![](https://lh7-us.googleusercontent.com/K4wxvrhdkBmJ2NUSCynr0Ow1KapbMn__-X0xOHQRj3JqmdtLABJNuHal54Czddl9c22ABIOsALJb_xu06rtVXz8yktO4HhoIf5fN5WyBD8qldRbYxPtb3cvJ8l9I3wbD_ShnK6gcPbqWKQwRdH8f70s)

Code partiel de l'implémentation de la classe QueryLoader

Ce code d'initialisation comporte des parties intéressantes, en particulier celle mise en surbrillance en rouge. Celui-ci crée un tableau de booléens **`includeInSelect`** où elle vérifie quelles entités doivent être sélectionnées et retournées.

Prenons l’exemple suivant :

```java
@Query("SELECT a, b FROM Author a LEFT JOIN FETCH a.books b")
```

Le code d’initialisation vérifie le contenu du **`SELECT`** un par un, qui est **`a`** et **`b`** dans cet exemple. Si l'entité sélectionnée provient d'une opération de **`FETCH`** dans la clause **`FROM`**, elle sera omise (**`false`**), car ce **`FETCH`** est utilisé pour charger les entités enfants (mais pas dans le cas de l'entité parente). Sinon, l'entité doit être sélectionnée et retournée.

Dans cet exemple le tableau **`includeInSelect`** retourne **`[true (Author), false (Book)]`**.

Dans notre cas de duplication d'entités avec EntityGraph :

```java
@EntityGraph(attributePaths = "books")
@Query("FROM Author a INNER JOIN a.agent WHERE a.agent.name like 'John'")
List<Author> findAuthorsWithJoinNotFetchedAndEntityGraph();
```

Nous n'avons pas spécifié la clause **`SELECT`**, donc par défaut, elle sélectionne toutes les entités de toutes les tables spécifiées dans la clause **`FROM`**.

![](https://lh7-us.googleusercontent.com/HkhTGHwEHcW0NYmk515enAJm848qgKGo0QegIVh0Pf9FpMEJxscZIC_DcMn3GTAMqTfo7AL7i0ZcZYnoO3lFSk71vOAIDfgKC1tyCXwjem4bxFhJ_S3hnFf98Lve5SQdL96wOGQu2bd6FhJtiIql4Y0)

État du tableau includeInSelect après l'exécution du code d'initialisation

A la fin de l’initialisation, le tableau **`includeInSelect`** retourne un tableau **`[true (Author), false (Book), true (Agent)]`** où **`Author`** et **`Agent`** (2 éléments) devraient être sélectionnés, car seulement l’entité **`Book`** est soumise à une opération **`FETCH`** en raison de l'utilisation d'EntityGraph.

Cela explique la différence entre **`findAuthorsLeftJoinWithEntityGraph`** et **`findAuthorsWithJoinNotFetchedAndEntityGraph`**.

Le premier ne retourne qu'une valeur par ligne car la clause **`SELECT`** ne contenait que les entités **`Author`** et **`Book`** (le **`SELECT`** de **`Book`** a été intégré dans la même requête SQL par EntityGraph). Cependant, comme **`Book`** est soumis à une opération **`FETCH`**, il n'a pas été pris en compte dans le tableau **`includeInSelect`** et n'a donc pas été retourné dans la liste des résultats.

Le second retourne un tableau par ligne car **`Author`** et **`Agent`** étaient dans la clause **`SELECT`** et ne sont pas soumis à une opération **`FETCH`**. Ainsi, il retourne un tableau composé de 2 éléments par ligne.

En résumé, Hibernate retourne de manière consistante un tableau, et le contenu de ce tableau est déterminé par les entités spécifiées dans la clause **`SELECT`** qui ne sont pas associées à des opérations **`FETCH`**.

Cependant, si nous suivons la logique précédente, **`findAuthorsLeftJoinWithEntityGraph`** devrait également renvoyer un tableau, mais un tableau contenant un seul élément (**`Author`**). C'est exact, c'est le cas, cependant, en examinant l'implémentation à la ligne [**476**](https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java?ref=sfeir.dev#L476) et [**620**](https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java?ref=sfeir.dev#L620)de [**QueryLoader**](https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/loader/hql/QueryLoader.java?ref=sfeir.dev), lorsque le tableau ne contient qu'un seul élément, cet élément est simplement extrait du tableau et retourné.

### Comment y remédier

Pour garantir le filtrage des entités dupliquées lors de l'application de la distinction, l'ensemble de résultats devrait être une liste d'entités plutôt qu'une liste de tableaux d'entités. Ce comportement est déterminé par les entités sélectionnées. Une solution simple consiste à inclure l’entité souhaitée explicitement dans la clause **`SELECT`** :

```java
@EntityGraph(attributePaths = "books")
@Query("SELECT a FROM Author a INNER JOIN a.agent WHERE a.agent.name like '%John%'")
List<Author> findAuthorsWithJoinNotFetchedAndEntityGraph();
```

La clause **`SELECT`** ne contiendra que les entités **`Author`** et **`Book`** (le **`SELECT`** de Book étant générée par l'EntityGraph) car **`Agent`** n'est plus une entité incluse dans le **`SELECT`** :

![](https://lh7-us.googleusercontent.com/LfgD4tqy7J3GqC8RSV91gAxn2Ow3bvhKaFDNClwqZCwEbsN0-QynvTBiVj-QVqJAUGRjeaeEBy96tl3GnFufPZRX5a_UfaCEI1hGV5qy6rK4usejHL_8ktCLX6xFV0YdUw0Nkgf5KAN4Gh1T6Ml1ThA)

État du tableau includeInSelect après l'exécution du code d'initialisation

Etant donnée que **`Book`** est une opération **`FETCH`**, le tableau **`includeInSelect`** ne sélectionne qu'un élément (**`Author`**) et au lieu de renvoyer un tableau, il renverra directement l'instance **`Author`** qui passera ensuite par l'**`IdentitySet`** pour être dédupliquée.

## Conclusion

La duplication d'entités se produit lorsque des tables sont jointes et que plusieurs lignes correspondent. Il existe plusieurs méthodes pour résoudre ce problème, notamment l'utilisation de **`Set`**, du mot-clé **`DISTINCT`** ou de l’EntityGraph pour dédupliquer les entités. Dans les deux derniers cas, la déduplication est réalisée grâce à un indicateur Hibernate qui accumule les résultats dans un **`IdentitySet`**. 

Cependant, cette solution n’est pas infaillible en raison du fait qu’en fonction de la clause **`SELECT`**, cela pourrait renvoyer pour chaque ligne une nouvelle instance de tableau des entités sélectionnées. Cela pourrait compromettre la déduplication par le biais de l'**`IdentitySet`**. 

Il est donc conseillé, lors de l'écriture de requêtes JPQL, de toujours spécifier la clause **`SELECT`**. Si elle n'est pas fournie, toutes les entités de la clause **`FROM`** qui ne sont pas soumises à une opération **`FETCH`** seront sélectionnées par défaut provoquant de potentiel duplication. 

Le problème des entités dupliquées est résolu dans Hibernate 6 (livré dans Spring Boot 3), car celui-ci introduit une meilleure gestion et automatique des entités dupliquées. Cependant, la sortie d’Hibernate 6 est relativement récente, de nombreuses applications fonctionnent toujours avec Hibernate 5 ou des versions antérieures. Concernant la migration d’Hibernate 5 à 6, celle-ci peut s'avérer périlleuse et complexe. Ainsi, la compréhension de la manière dont Hibernate 5 gère ces situations reste pertinente pour le nombre considérable d'applications qui l'exploitent toujours.