De plus en plus d’applications utilisent Hibernate. Mais de temps en temps, pour des raisons de performance, on peut être tenté de remplacer les requêtes Hibernate par du SQL. Et là, les choses se gâtent.
Débloque les + belles offres tech en 10 mins
En effet, le cycle de vie d’un objet chargé avec Hibernate est comme suit :
- Chargement de l’objet avec une requête en HQL ou par critères
- L’application met à jour l’objet, celui-ci reste en mémoire
- L’application appelle session.save(). Cette méthode ne fait que synchroniser l’objet avec le cache hibernate dans le cas où celui-ci est absent. Autrement dit, dans notre cas, elle ne fait rien.
- L’objet est vraiment écrit en base soit lors de l’appel à session.flush(), soit lors de la fermeture de la session si celle-ci est en flush automatique, c’est le cas par défaut.
Maintenant où est le problème ? Eh bien, supposons qu’entre l’étape 2 et l’étape 3 l’application fasse une écriture en base sur notre objet, sans passer par Hibernate. Ce dernier ne saura pas qu’il y a eu une mise à jour dans son dos. Dès lors, après que la mise à jour de données a été faite par le SQL, Hibernate va l’écraser au moment du flush() et donc, il n’y a aucun moyen de prédire ce qui va se passer et les données en base seront probablement incohérentes.
Il existe plusieurs moyens de contourner le problème :
- Soit avant de faire un appel au DAO en SQL, on flush la session. Maintenant cette solution est « sale » et ne fonctionne pas dans un contexte transactionnel.
- Soit quand on rencontre le cas ci-dessus on fait une requête hibernate moins sélective en base, quitte à refaire du filtrage dans le DAO. C’est certes plus lent en terme de transfert réseau, mais d’un autre côté on peut ainsi bénéficier du cache de session Hibernate et surtout nos données sont en sécurité.
- Soit l’application requiert un grand nombre de requêtes spécifiques à un SGBD comme par exemple du Oracle Text, et dans ce cas, le mieux est encore de ne pas utiliser Hibernate mais plutôt Spring JDBC ou du MyBatis.
Débloque les + belles offres tech en 10 mins
Cet article vous a plu ? Vous aimerez sûrement aussi :
- Les transactions en Java/JEE
- La sécurisation des applications web
- Utilisation efficace de la base de données dans vos applications Web
Julien
Moi c’est Julien, ingénieur en informatique avec quelques années d’expérience. Je suis tombé dans la marmite étant petit, mon père avait acheté un Apple – avant même ma naissance (oui ça date !). Et maintenant je me passionne essentiellement pour tout ce qui est du monde Java et du système, les OS open source en particulier.
Au quotidien, je suis devops, bref je fais du dév, je discute avec les opérationnels, et je fais du conseil auprès des clients.
Hum l’utilisation d’un champ version devrait normalement régler ce genre de problème non?
Par ailleurs pour moi les problèmes de performance se rencontrent plus généralement à la lecture qu’à l’écriture et on peut demander à la session hibernate (donc passant par le cache) d’executer une SQL query si je me souvient bien.
Cordialement.
Bonjour,
En fait non, quand on demande à la session Hibernate d’exécuter une query SQL, celle-ci l’envoie directement au driver JDBC sans mettre à jour son état interne. La doc ici n’est pas très claire, je vous invite à essayer un exemple simple pour vous en convaincre du type une table avec deux champs, on charge une entité avec Hibernate qu’on met à jour avec une valeur, on met à jour ensuite la table avec du SQL natif en passant par la session et ensuite on flush la session. Contrôlez le résultat, vous risquez d’être surpris.
Il y a qq trucs sur stackoverflow sur le sujet, notamment ceci :
http://stackoverflow.com/questions/15595627/hibernate-query-cache-applicable-for-native-queries
Pour l’utilisation du champ version, l’approche peut fonctionner mais pas dans tous les cas. En effet dans le cas où le premier update en SQL met à jour la version, les données ne seront pas remplacées par du Hibernate. Maintenant cela pose deux problèmes :
1/ La gestion côté Hibernate doit être totalement transactionnelle, ce qui n’est hélas pas toujours le cas dans la vraie vie.
2/ Dans le cas où il y a un mix d’update en SQL et d’update en Hibernate au sein du même thread, on risque tout de même se retrouver avec un truc assez horrible à déboguer, et il faut aussi que le SQL mette à jour la version de manière atomique.
Néanmoins c’est tout de même plus safe que ne rien faire du tout.
Bon après un peu de recherche je viens de trouver ceci :
https://access.redhat.com/site/documentation/en-US/JBoss_Enterprise_Application_Platform/5/html/Hibernate_Entity_Manager_Reference_Guide/batch.html
« As already discussed, automatic and transparent object/relational mapping is concerned with the management of object state. This implies that the object state is available in memory, hence updating or deleting (using SQL UPDATE and DELETE) data directly in the database will not affect in-memory state »
-> D’où le risque de perte de données avec un update ou un delete en SQL natif.