Jusqu’à la version 2.0 de JPA, les relations de nos beans étaient chargées en LAZY (par défaut) ou en EAGER. Le mode LAZY était utilisé dans la plupart des cas pour avoir des applications performantes et scalables. Pour autant, il y a toujours un moment où le mode LAZY ne suffit plus, nécessitant d’écrire des requêtes spécifiques, sans pour autant que le mode EAGER convienne (ou soit possible). Les utilisateur d’Hibernate penseront notamment à l’irritant LazyInitializedExcpetion. C’est là qu’interviennent les Entity Graphs.
Les Entity Graphs permettent d’indiquer précisément les attributs à charger depuis la base. On peut les utiliser en FETCH ou en LOAD. En FETCH, seuls les attributs spécifiés seront chargés en EAGER. En LOAD, tous les attributs non spécifiés seront chargés selon leurs modes par défaut.
Mais prenons un exemple pour que ce soit plus clair. Disons qu’on s’intéresse au panier d’achat d’un site de e-commerce. On aura un objet Panier, qui représente le panier et qui contient une liste de Lignes. Et chaque ligne pointe un Produit :
1 2 3 4 5 |
1 2 3 4 5 6 7 | public class Ligne { private Long id; private Produit produit; private Panier panier; private int quantite; ... } |
C’est donc un modèle relativement simple. Ici, l’objet Ligne sert de liaison. Voyons ce que ça donne classiquement quand on ajoute les annotations pour le mapping :
1 2 3 4 5 6 7 8 9 10 11 12 |
Ici, c’est important de noter que j’ai indiqué que la liste lignes est LAZY, ce qui veut dire qu’elle ne sera pas chargée lors d’une requête simple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
On va maintenant définir le Graph sur l’objet Panier directement :
1 2 3 4 5 6 7 8 9 10 | @Entity @Table(name = "T_PANIER") @NamedEntityGraph( name = "graph.panier.lignes", attributeNodes = @NamedAttributeNode(value = "lignes", subgraph = "lignes"), subgraphs = @NamedSubgraph(name = "lignes", attributeNodes = @NamedAttributeNode("produit")) ) public class Panier { ... } |
On peut désormais se servir du graphe ainsi défini dans les requêtes. Par exemple, disons qu’on veuille faire un simple find :
1 2 3 4 5 6 7 8 |
Si c’est un peu vague pour vous, voici un exemple dans lequel on définit la requête :
1 2 3 4 5 6 7 8 9 | Long id = 1234; ... final EntityGraph graph = em.getEntityGraph("graph.panier.lignes"); return em.createQuery("select p from PANIER p where p.id=:id", Panier.class) .setHint("javax.persistence.fetchgraph", graph) .setParameter("id", id) .getResultList() .get(0); |
Ce qui est intéressant, c’est qu’on n’a écrit aucun code pour contourner le LAZY. C’est votre ORM qui va comprendre les annotations (et les hints) pour produire les requêtes adaptées. Quand on consulte les logs d’Hibernate, on s’aperçoit qu’il n’a généré qu’une seule requête pour charger l’ensemble des objets qui nous intéressent.