Le premier réflexe est souvent de forcer le chargement dans le mapping.
@OneToMany(mappedBy = "etudiant", fetch = FetchType.EAGER)
private List<Livre> livresLus;Ça ne supprime pas le N+1
Sur une requête JPQL, Hibernate exécute la requête principale, puis une requête par étudiant pour remplir la collection.
em.createQuery("select e from Etudiant e", Etudiant.class).getResultList();SELECT * FROM etudiant;
SELECT * FROM livre WHERE etudiant_id = 1;
SELECT * FROM livre WHERE etudiant_id = 2;
-- ...Le seul changement est que ces requêtes sont déclenchées plus tôt, sans qu’on les ait demandées. Le nombre d’allers-retours avec la base est identique.
Note
L’
EAGERne produit une jointure que sur unem.find(). Dès qu’on passe par JPQL ou Criteria, Hibernate exécute la requête telle qu’elle est écrite, puis complète les associationsEAGERpar des requêtes supplémentaires.
Le coût est imposé à toutes les requêtes
C’est le reproche principal. L’EAGER s’applique partout : toutes les requêtes sur Etudiant paieront le chargement des livres, y compris celles qui n’en ont pas besoin.
- un comptage d’étudiants ;
- un écran de liste qui n’affiche que les noms ;
- une recherche par nom.
Le coût est inscrit dans le mapping alors que le besoin appartient au cas d’usage. Et comme le mapping est partagé par toute l’application, un EAGER ajouté pour un écran dégrade tous les autres.
À retenir
Le mapping doit rester
LAZYpartout. C’est la requête, et non l’entité, qui décide de ce qui est chargé.