Ressource
Plusieurs solutions existent pour supprimer les N+1 requêtes. Elles ne se valent pas : certaines déplacent le problème, d’autres le règlent mais introduisent d’autres contraintes.
Les différentes solutions
| Solution | Principe | Verdict |
|---|---|---|
FetchType.EAGER | forcer le chargement dans le mapping | ❌ ne règle rien et pénalise toutes les requêtes |
JOIN FETCH dans une méthode dédiée | une méthode de repository par plan de chargement | ✅ ma préférée |
@BatchSize | charger les associations par paquets | ⚠️ atténue le problème sans le supprimer |
@Fetch(FetchMode.SUBSELECT) | toutes les collections en une requête, via une sous-requête | ⚠️ efficace mais implicite et non standard |
| Entity Graph | le plan de chargement passé en paramètre de la requête | ✅ quand les combinaisons se multiplient |
| Projection DTO | ne charger aucune entité, juste les colonnes utiles | ✅ en lecture seule |
Ma préférence : les méthodes dédiées
L’approche que je retiens est celle des méthodes de repository dédiées : le mapping reste LAZY partout, et le repository expose une méthode par plan de chargement.
findAll() // ne charge que l'étudiant
findAllWithLivres() // ajoute un join fetch sur les livresC’est le meilleur compromis pour trois raisons
- le mapping reste neutre : aucune requête ne paie un chargement dont elle n’a pas besoin ;
- le coût est lisible : le nom de la méthode annonce ce qu’elle ramène, sans avoir à ouvrir l’entité ;
- c’est du JPQL standard : pas d’annotation propriétaire, pas de comportement implicite.
Son défaut connu est la combinatoire : avec plusieurs associations, le nombre de méthodes explose (findAllWithLivresAndAdresses, findAllWithLivresAndCursus…). C’est le seul cas où je bascule vers un Entity Graph, qui permet de garder une requête générique et de passer le plan de chargement en paramètre.