description du problème

Comme nous l'avons dit plus haut, l' annotation LazyCollectionOption.EXTRA a un problème - elle effectue une requête distincte à la base de données pour chaque objet. Nous devons en quelque sorte expliquer à Hibernate que nous voulons qu'il charge immédiatement tous les objets enfants de nos objets parents.

Les développeurs d'Hibernate ont trouvé une solution à ce problème, l' opérateur de recherche de jointure dans HQL.

Exemple de requête HQL :

select distinct task from Task t left join fetch t.employee order by t.deadline

Dans cette requête, tout est simple et complexe à la fois. Essayons de le construire pièce par pièce.

Option 1

Nous voulons télécharger tous les objetstâche, triés par échéance. Voici à quoi ressemblerait cette demande :

select task from Task t order by t.deadline

Alors que tout est clair. Mais le champemployéde la classe Task contiendra une collection d'Employés annotés avec l' annotation EXTRA . Et les objets de cette collection ne seront pas chargés.

Option 2

Forcer Hibernate à charger des objets enfants pour un objettâche.

select task from Task t join fetch t.employee order by t.deadline

Avec de l'aide, nous lions explicitement les entités Task et Employee dans notre requête. Hibernate le sait déjà car nous utilisons les annotations @ManyToMany sur ces champs.

Mais nous avons besoin d'une instruction de jointure pour la compléter avec une instruction de récupération pour obtenir une récupération de jointure . C'est ainsi que nous disons à Hibernate que les objets des collections Task.employee doivent être chargés depuis la base de données lorsque notre requête est exécutée.

Variante 3

La solution précédente a quelques bugs. Tout d'abord, après avoir utilisé la jointure, SQL ne nous renverra pas d'objetstâche, auxquels aucun objet n'est associé dans la table Employee. C'est exactement ainsi que fonctionne la jointure interne .

Nous devons donc augmenter notre jointure avec un opérateur gauche et la transformer en une jointure gauche . Exemple:

select task from Task t left join fetch t.employee order by t.deadline

Variante 4

Mais ce n'est pas tout. Si dans votre code la relation entre les entités est plusieurs à mai, il y aura des doublons dans les résultats de la requête. Le même objettâchepeuvent être trouvés sur différents employés (objets Employé).

Vous devez donc ajouter le mot clé distinct après le mot sélectionné pour vous débarrasser de l'objet tâche en double.

select distinct task from Task t left join fetch t.employee order by t.deadline

C'est ainsi qu'en 4 étapes nous sommes arrivés à la demande par laquelle nous avons commencé. Eh bien, le code Java aura l'air tout à fait attendu :

String hql = " select distinct task from Task t left join fetch t.employee order by t.deadline";
Query<Task> query = session.createQuery( hql, Task.class);
return query.list();

JOIN FETCH Limitations

Personne n'est parfait. Instruction JOIN FETCH aussi. Il a pas mal de limitations. Et le premier utilise les méthodes setMaxResults() et setFirstResult() .

Pour l'instruction JOIN FETCH, notre Hibernate va générer une requête très complexe dans laquelle nous combinons trois tables en une seule : employee, task et employee_task. En fait, il ne s'agit pas d'une demande d'employés ou de tâches, mais de tous les couples employés-tâches connus.

Et SQL peut appliquer ses instructions LIMIT et OFFSET exactement à cette requête de paires employé-tâche. En même temps, il découle clairement de la requête HQL que nous voulons obtenir exactement des tâches (Task), et si nous redistribuons nos paramètres FirstResult et MaxResult, alors ils devraient se référer spécifiquement aux objets Task.

Si vous écrivez un code comme celui-ci :

String hql = " select distinct task from Task t left join fetch t.employee order by t.deadline";
Query<Task> query = session.createQuery( hql, Task.class);
       	    query.setFirstResult(0);
        	   query.setMaxResults(1);
return query.list();

Hibernate ne pourra alors pas convertir correctement FirstResult et MaxResult vers les paramètres OFFSET et LIMIT de la requête SQL.

Au lieu de cela, il fera trois choses :

  • La requête SQL sélectionnera généralement toutes les données de la table et les renverra à Hibernate
  • Hibernate sélectionnera les enregistrements nécessaires dans sa mémoire et vous les renverra
  • Hibernate émettra un avertissement

L'avertissement ressemblera à ceci :

WARN [org.hibernate.hql.internal.ast.QueryTranslatorImpl] HHH000104: 
firstResult/maxResults specified with collection fetch; applying in memory!