jeudi 8 avril 2010
Lazy loading dans JPA
Par Philippe De Oliveira, jeudi 8 avril 2010 à 22:06 dans Java

Contrairement à ce que l'on pourrait penser, le lazy loading d'entités JPA n'est pas actif par défaut sur l'ensemble des relations entre objets mais uniquement sur certaines. Il s'agit d'un comportement complètement contre-intuitif qui peut induire une perte de performance conséquente.
1- Le lazy loading
Le lazy loading consiste à charger des objets uniquement lorsque l'on y accède explicitement. Prenons l'exemple suivant : une classe Utilisateur contient une collection d'objets de type Livre. Lorsqu'un utilisateur est chargé depuis la base de données, sa collection d'instances de Livre ne doit être chargée que si l'on y accède explicitement. Ainsi, si l'on souhaite uniquement accéder à certaines autres propriétés de l'utilisateur, la collection de ses livres ne sera jamais chargée. De même, toute instance de la classe Livre peut posséder un attribut de type Utilisateur qui ne sera chargé que si l'ont y accède. Pour mettre en place ce mécanisme de manière transparente, un proxy (généré au runtime) est retourné lors du chargement de l'objet Utilisateur.
2 - JPA et ses défauts
On savait déjà que JPA avait de nombreux défauts :
- pas de gestion fine de la session
- pas d'API criteria (corrigé dans JPA 2)
- pas de format XML possible
En voici donc un nouveau : toute relation n'est pas "lazy loadée". En effet, pour les collections (relations one-to-many et many-to-many) la valeur par défaut de l'attribut FETCH est LAZY mais pour les objets contenus dans un autre (relations many-to-one) la valeur par défaut est EAGER ce qui signifie que tout sous-objet est donc chargé.
3 - Conséquences
Si un objet possède 10 relations many-to-one, par défaut, le chargement de cet objet causera donc le chargement de 11 objets (lui plus les 10 qu'il contient). Pour cela, l'implémentation de JPA (hibernat entity manager par exemple) effectue une requête possédant donc 10 "left outer join". Inutile de dire que les performances d'une telle requête sont horribles. Dans un test que j'ai effectué, la où une telle requête prenant 240 secondes, en activant le lazy loading, on obtient un temps de chargement de 0.2 secondes !
4 - Mais pourquoi tant de haine ?
Je me suis demandé pourquoi les membres participants aux JSR 220 et 317 ont bien pu décider ça. Et à vrai dire, je n'ai aucune réponse. Si vous en croisez un, n'hésitez pas à lui demander :-). Voici les pour et contre que j'ai trouvé :
Pour :
- Il peut sembler justifié de charger un objet contenu dans un autre
Contre :
- JPA découle directement d'hibernate. Or dans ce dernier, le lazy loading est actif par défaut pour TOUT type de relation
- Pourquoi avoir deux valeurs par défaut différentes entre deux types de relations ?
- Le lazy loading est uniquement là pour des problématique de performances. Pourquoi donc ne pas l'utiliser tout le temps ?
5 - Conclusion
Comme JPA fait partie de Java EE, il y a fort à parier que cette valeur par défaut ne changera plus jamais. Donc, la seul solution aujourd'hui est d'indiquer systématique comme lazy "loadée" tout relation many-to-one :
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "foreignId") public User getUser() { return user; }
Commentaires
1. Le vendredi 9 avril 2010 à 18:22, par bibi
2. Le samedi 10 avril 2010 à 00:36, par Philippe De Oliveira
3. Le jeudi 9 septembre 2010 à 16:04, par Patricio
4. Le jeudi 9 septembre 2010 à 19:12, par Philippe De Oliveira
5. Le vendredi 10 septembre 2010 à 08:13, par Patricio
6. Le samedi 30 juillet 2011 à 20:55, par madechievaili
7. Le jeudi 27 octobre 2011 à 00:38, par hiba
8. Le lundi 5 décembre 2011 à 14:14, par fhouillo
Ajouter un commentaire
Les commentaires pour ce billet sont fermés.