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;
}