Hibernate et la LazyInitializationException

Tuto : Hibernate et la LazyInitializationException
Florent TRIPIERMis à jour le 12 Sept 2013
Les développeurs utilisant Hibernate sont tôt ou tard amenés à se heurter à sa fameuse LazyInitializationException. Voyons dans quelles circonstances elle est levée et comment l’éviter définitivement. 

## Pourquoi la LazyInitializationException ?

Pour les victimes de la <span >LazyInitializationException</span>, les raisons de sa propagation sont souvent obscures et ils ont tendance &agrave; imaginer que son apparition est plus ou moins al&eacute;atoire. Il n’en est pourtant rien. 

## Contexte : le lazy loading

La <span . Concr&egrave;tement, cela signifie que lorsque vous chargez une entit&eacute; gr&acirc;ce &agrave; Hibernate, ses d&eacute;pendances fonctionnelles en FetchType.LAZY&nbsp;ne seront **jamais** charg&eacute;es tant que vous n’appellerez pas sp&eacute;cifiquement leur getter. 

Exemple : 

```java
@Entity
@Table(name = "commande")
public class Commande implements Serializable {

        @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "id_utilisateur")
  private Utilisateur utilisateur;

        public Utilisateur getUtilisateur() {
    return utilisateur;
  }

  public void setUtilisateur(Utilisateur pUtilisateur) {
    utilisateur = pUtilisateur;
  }
}
```

Ici, si je charge une commande avec Hibernate, j’ai bien mon objet Commande, mais je n’ai pas l’utilisateur. Ce n’est que lorsque j’appellerai la m&eacute;thode getUtilisateur() qu’implicitement Hibernate passera la req&ecirc;te SQL pour charger mon Utilisateur.&nbsp; 

Ce principe est valable pour le mapping en @ManyToOne aussi bien que pour le @OneToMany, le @OneToOne ou le @ManyToMany. Il est d&eacute;finit par la valeur de la propri&eacute;t&eacute; fetch, qui n’a que deux valeurs possibles : LAZY et EAGER (ce dernier signifie que les d&eacute;pendances fonctionnelles sont charg&eacute;es en m&ecirc;me temps que l’objet principal : &agrave; utiliser avec parcimonie et subtilit&eacute; si vous ne voulez pas r&eacute;cup&eacute;rer l’ensemble de votre base de donn&eacute;es &agrave; chaque requ&ecirc;te). Le FetchType par d&eacute;faut est EAGER pour @ManyToOne et @OneToOne, et LAZY pour @OneToMany et @ManyToMany. 

## Comment &ccedil;a marche ?

Hibernate g&egrave;re une grosse map de toutes les entit&eacute;s qu’il conna&icirc;t. C’est ce qui lui permet de retrouver l’objet Utilisateur associ&eacute; &agrave; mon objet Commande dans l’exemple pr&eacute;c&eacute;dent : puisqu’il conna&icirc;t cette commande et qu’il conna&icirc;t via le mapping le moyen de retrouver un Utilisateur associ&eacute; &agrave; une Commande, il est en mesure de charger mon Utilisateur.&nbsp; 

Le probl&egrave;me de la <span >LazyInitializationException&nbsp;</span>vient de ce qu’**Hibernate ne charge jamais de d&eacute;pendances fonctionnelles d’objets qu’il ne conna&icirc;t pas**, c’est-&agrave;-dire qui ne sont pas dans sa map. Hors, **la dur&eacute;e de vie d’une map Hibernate est celle de l’EntityManager&nbsp;**utilis&eacute;. Autrement dit, la map est cr&eacute;&eacute;e avec l’EntityManager&nbsp;et est perdue lorsque celui-ci est ferm&eacute;.&nbsp; 

Exemple : dans le code ci-dessous, la map est cr&eacute;&eacute;e &agrave; la ligne 6, en m&ecirc;me temps que l’EntityManager, et est d&eacute;truite &agrave; la fermeture de ce dernier ligne 17. La fin de la transaction ne d&eacute;truit pas la map. 

```java
@PersistenceUnit(unitName = "test-manager")
private EntityManagerFactory emf;

public void maMethode() {
    try {
  em = emf.createEntityManager();
  em.getTransaction().begin();

  // votre code

  em.getTransaction().commit();
    } catch (Exception e) {
  e.printStackTrace();
    } finally {
  try {
            if (em != null)
    em.close();
            } catch (Throwable t) {
    e.printStackTrace();
      }
    }
}
```

La lev&eacute;e de l’exception 

Donc, toujours avec l’exemple de la commande, si j’invoque getUtilisateur() alors que (au choix) : 

  * il n’y a pas d’EntityManager actif ;&nbsp; 
  * la map de l’EntityManager actif ne contient pas ma Commande ; 

une&nbsp;<span >LazyInitializationException est lev&eacute;e.</span> 

## Comment y rem&eacute;dier ?

<span >Tr&egrave;s simplement, en rechargeant l’objet qui pose probl&egrave;me avec Hibernate.&nbsp;</span> 

<span >Exemple :</span> 

{{< highlight java >}}
entityManager.find(Commande.class, commande.getId());
{{< /highlight >}}

Ainsi l’EntityManager actif conna&icirc;t de nouveau votre objet et vous pouvez profiter pleinement du LazyLoading. 

## Les situations les plus fr&eacute;quentes

### Le cas simple : changement de page

Mettons que j’ai un &eacute;cran de listing de mes commandes : j’affiche leur num&eacute;ro, leur montant, leur date, mais aucune information qui n&eacute;cessite d’appeler getUtilisateur(). Dans ma Collection de Commandes, aucun n’objet n’aura son Utilisateur charg&eacute;. Si maintenant j’en s&eacute;lectionne une via mon &eacute;cran de gestion, et que je l’&eacute;dite. Si dans l’&eacute;dition j’affiche par exemple le nom de l’Utilisateur et que dans le code qui intervient dans le passage d’une page &agrave; l’autre je ne recharge pas ma Commande, j’obtiens une LazyInitializationException. Logique ! 

### Le cas compliqu&eacute; : la redirection

Nous nous pla&ccedil;ons dans le cas de l’utilisation de l’API JSF (impl. Mojarra par exemple). 

Reprenons le cas pr&eacute;c&eacute;dent : cette fois je recharge la commande s&eacute;lectionn&eacute;e, puis je redirige vers ma page d’&eacute;dition de commande. J’obtiens encore une&nbsp;<span >LazyInitializationException. Pourquoi ? Parce qu’il y a deux requ&ecirc;tes HTTP ! Le cheminement est le suivant :</span> 

  * <span >mon formulaire est soumis en postback, c’est-&agrave;-dire avec comme URL d’action l’URL courante ;</span> 
  * <span >dans mon code, je recharge mon objet puis je retourne un outcome de redirection ;</span> 
  * <span >mon serveur d’application renvoie un code 302 pour indiquer une redirection ;</span> 
  * <span >le navigateur client envoie alors une deuxi&egrave;me requ&ecirc;te HTTP vers l’URL sp&eacute;cifi&eacute;e dans la r&eacute;ponse 302 ;</span> 
  * <span >alors seulement la page est construite et j’appelle getUtilisateur(). Mais bien s&ucirc;r mon EntityManager n’a pas surv&eacute;cu entre les requ&ecirc;tes HTTP, il y en a donc un autre qui ne conna&icirc;t pas ma Commande.</span> 

<span >​Le fait qu’il y ait une redirection ou pas se param&eacute;tre dans les fichiers de navigation faces-config avec l’attribut redirect : s’il y est, il y a redirection, sinon non.</span> 

Comment rem&eacute;dier &agrave; cela ? On peut imaginer de multiples solutions, en voici une : utiliser un bool&eacute;en plac&eacute; dans le getter d’une entit&eacute; pour signaler si elle doit &ecirc;tre recharg&eacute;e ou non (dans un Bean donc). 

Exemple : 

```java
public Commande getCommande() {
    if(reload) {
        commande = getCommandeManager().getById(commande.getId);
        reload = false;
    }
    return commande;
}
```

Il n’y a plus qu’&agrave; g&eacute;rer astucieusement le bool&eacute;en (c’est-&agrave;-dire le passer &agrave; true pendant lors du premier appel pour que l’objet soit recharg&eacute; lors du second) et le tour est jou&eacute; !