Hibernate LazyInitializationException : Quelle solution acceptable?

Qui n'a pas rencontré une LIE (LazyInitializationException) dans ses projets à base d'ORM Hibernate?

Que signifie LIE exactement?
Quelle(s) sont les solutions acceptables ou élégantes pour résoudre l'exception LIE?

La dernière question est pertinente, surtout au vu des astuces de contournement de l'exception LIE.

La suite de ce billet répond à ces questions, avec comme d'habitude des exemples concrets.

L'objet de ce billet est de présenter une solution "élégante" et performante qui préserve la lisibilité du code et facilite sa maintenance.

Rappelons qu'une entité persistante a un cycle de vie :

  • soit c'est une entité attached (traduction : entité attachée au contexte de persistance),
  • soit elle est detached (entité déconnectée du contexte de persistance).

Une entité est attached tant que sa session hibernate n'est pas fermée.
Après la fermeture de la session d'Hibernate, l'entité devient "detached".

C'est bien de session Hibernate dont il s'agit et non de transaction.

Résumons : une entité gérée par une session Hibernate reste sous le contrôle d'Hibernate jusqu'à la fermeture (manuelle ou par le conteneur) de cette session.
Et, tant que l'entité est sous le contrôle d'Hibernate (c'est à dire attached) il est bon de savoir que :
- toute modification sur l'entité sera impactée sur la base par la session hibernate. Le quand et le comment dépendent d'autres paramètres de config.
- L'accès aux collections lazy (chargement tardif) est autorisé.

Par contre, à partir de l'instant où l'entité est "detached" (et sachant qu'avant qu'elle ne le devienne, ses collections lazy n'ont pas été pré-chargées (initialisées))
il n' y a alors aucun moyen d'accéder à ses collections lazy (ou tout autre attribut lazy) à moins de ré-attacher à nouveau cette entité.

Q. Que faire si je dois accéder à ces collections (lazy) sans prendre une LIE?

R. Les développeurs connaissent les "astuces" pour répondre à ce besoin.
Certaines astuces ne permettent pas de garder la lisibilité et la maintenabilité du code.

Rappelons aussi l'approche simple qui consiste à ré-attacher l'entité puis charger les collections lazy.
Mais, cette approche n'est pas toujours convenable ni performante.

Une seconde approche consiste à précharger les collections avant que l'entité passe au stade "detached".
Pour le faire, le mieux est d'utiliser la méthode statique Hibernate.initialize( ... ).
Ainsi, le code est mieux lisible et facile à maintenir! Néanmoins ce n'est pas toujours approprié.

Q. Pourquoi n'est-ce pas toujours approprié?
R. Car pour certains cas, charger toute la collection (ou toute la base) pour ne lire qu'une information particulière (exemple, la taille de la collection) ne peut être justifié.

Q. Y a t-il d'autres solutions pour éviter l'exception LIE?

R. Une autre solution, qui pour certains, est un anti-pattern et, pour d'autres, le contournement idéal: il s'agit de OSIV : OpenSessionInView.
Afin de ne pas alourdir ce billet, je ne détaillerai pas OSIV mais voici des liens utiles :
OSIV C'est quoi?

OSIV anti-pattern?

Quoiqu'on en dise, ce n'est pas la solution idéale pour tous les cas, et le risque de pénaliser les performances de l'application est très présent.

Q. Quelle est donc la solution acceptable pour éviter l'exception LIE?

R. La méthode createFilter de org.hibernate.Session permet justement de ne pas charger toute la collection.
Voici un exemple qui illustre mieux mes propos.
Supposons que nous souhaitions avoir la taille de la collection (liste) des événements associés à une personne donnée.
La classe Person possède une relation @ManyToMany comme ceci:

//* class Person (extrait)
@ManyToMany(
			targetEntity=Event.class,
			fetch=FetchType.LAZY,
			cascade={CascadeType.ALL}
	)
	@JoinTable(
			name="PERSON_EVENT",
			joinColumns=@JoinColumn(name="PERSON_ID")
	)
	private Set<Event> listeEvent;

Et la classe Event est un POJO ordinaire avec l'annotation @Entity. Elle n'a aucune relation déclarée avec Person:

@Entity
public class Event {
	@Id private long id;
	private String title;
       //omis les getters setters

La classe Junit ci-après illustre l'emploi de createFilter pour récupérer la taille des événements d'une personne:


public class TestCreateFilter {
	@Autowired
	SessionFactory sessionFactory;
	@Test
	public void testCreateFilter4DetachedBean() {
		Session sess = SessionFactoryUtils.getSession(sessionFactory, true);
		Person pers = (Person) sess.get(Person.class, 1L);
		// ICI on calcule juste la taille
		long nb = (Long) sess.createFilter(pers.getListeEvent(),
				" select count(*)").uniqueResult();
		Assert.assertTrue(nb>=0);
		sess.close(); // la session hibernate fermée, pers est detached!  
		System.out.println("NB EVENTs: " + nb);
	}
}

J'espère que ce billet est utile pour mieux gérer les collections des entités détachées dans hibernate.
L'utilisation de createFilter apporte une solution élégante et performante.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.