JPA : les illusions sur les NamedQueries
Un certain nombre d’idées reçues existent sur les NamedQueries. On les dit plus performantes car mises en cache, on les dit plus sûres car validées au chargement. Etudions ces points et regardons quels sont les réels avantages des NamedQueries.
Rappel sur les NamedQueries
Une NamedQuery est une requête nommée. Ce n’est pas une nouveauté de la norme JPA car elles existaient dans Hibernate bien avant. Une namedQuery est un moyen de donner un non à une requête JPQL et de la rappeler par ce nom par la suite.
Création :
@Entity
@NamedQuery(name = "findAllCustomers", query = "Select c From Customers c")
public class Client {
...
}
Utilisation :
public List findAll() {
return entityManager.createNamedQuery("findAllCustomers").getResultList();
}
Une NamedQuery est plus performante
La rumeur dit qu’une NamedQuery est plus performante car elle est mise en cache.
Techniquement, au démarrage, le moteur JPA va compiler la NamedQuery puis la mettre en cache dans le statementCache des Connections. Cela rappelle fortement les PreparedStatement, non ? Or, Hibernate utilise de toute façon des PreparedStatements si le driver Jdbc le permet. Une NamedQuery ne fait donc pas mieux qu’une requête dynamique, même s’il y a le coût de parsing de la requête si celle-ci est dynamique.
Je n’ai pas trouvé de benchmark entre les deux approches (NamedQuery versus requête dynamique). La principale variable en jeu est le driver Jdbc et donc la base de données utilisée.
Une NamedQuery est validée
Une NamedQuery est validée au lancement de l’application avant d’être soit placée dans le statementCache. Cela permet théoriquement de contrôler la syntaxe JPQL, que le mapping est correct, que les entités utilisées sont annotées et que les colonnes ont bien un attribut (ou un getter/setter) dans les entités.
Dans les faits, cette phase de validation est très limitée (testée avec Hibernate 3.3 et mysql) :
- Pour une entité inexistante (“From EntiteInexistante”), une erreur est remontée
- Pour une colonne inexistante (“From Client where colonneInexistante is null”), aucune erreur n’est soulevée
Aujourd’hui, je ne vois donc pas en quoi cette phase de validation apporte de la valeur. Si la conversion JPQL vers SQL n’est pas complète, elle n’empêche pas de valider les requêtes sur la base de données cible.
NamedQuery, alors pourquoi ?
Une fois éliminées ces illusions, il ne reste pas grand-chose d’attrayant aux NamedQueries. On sait qu’elles ne sont pas systématiquement pas plus performantes et que la validation n’est pas complète. Il leur reste cependant trois petits avantages :
- Les NamedQueries sont réutilisables en plusieurs endroits. Ce cas est principalement utile quand l’entityManager est injecté dans la couche de service (et donc qu’il n’y a pas de couche de DAO) ;
- Elles sont chargées au démarrage ce qui permet de diminuer la réponse de l’application au premier accès, mais c’est au détriment du temps de chargement de l’application ;
- Les requêtes sont regroupées avec le mapping (@Column…), ce qui permet de faciliter leur écriture.
Pour conclure, je pense que les NamedQueries résultent plus d’une question de goût et de convention d’écriture que d’un réel intérêt technique et factuel.
March 28th, 2010 at 12:36
C’est toujours le pb de la spécification vs l’implémentation. La spec JPA 2.0 (§3.8.13) te dis ceci :
Named queries are static queries expressed in metadata. Named queries can be defined in the Java Persistence
query language or in SQL. Query names are scoped to the persistence unit.
Nulle part JPA ne parle d’optimisation, de cacher les named queries, de les précompiler, de les charger au démarrage. En fait, les spec Java EE ne parlent que très rarement de performance (seul exemple en date, l’API de cache de second niveau de JPA 2.0). Le titre de ton blog devrait donc être “Hibernate : les illusions sur les NamedQueries”, puisque JPA ne te promets aucune performance en plus.
Il serait intéressant que comparer les autres implémentations (OpenJPA, EclipseLink) et voir si elles optimisent ou pas les NamedQueries.
March 28th, 2010 at 14:47
Merci de ton retour, Antonio.
J’ai trop lu d’arguments non prouvés sur les NamedQueries.
Oh oui, un bench serait vraiment intéressant pour tirer des conclusions et savoir si elles apportent réellement de la valeur.
March 28th, 2010 at 18:36
Sauf erreur de ma part, le but premier d’une named query est simplement d’être … nommée. Le but étant de pouvoir réutiliser les queries un peu partout et surtout de leur donner un sens plus métier qu’une requête JPQL. Je ne pense pas que l’ont va privilégier l’utilisation d’une named query pour la performance, mais plutôt pour faciliter la maintenance d’une application. En ce qui me concerne (mais c’est très subjectif), ici on se trompe un peu de problème.
Cependant il est vrai qu’il serait cool de bencher correctement l’utilisation des named queries afin de voir le coût de cette possibilité sur le temps d’exécution.
March 28th, 2010 at 22:40
A mon sens, le seul véritable avantage offert par hibernante avec les NamedQueries est la possibilité externaliser les requêtes pour les rendre plus facilement exploitable par une équipe de perfs ou de db. (encore faut il en avoir besoin). Du coup je n’utilise quasi jamais la version utilisant les annotations. En pratique, il s’agit d’une première étape à la transformation d’une requête en procédure stockée…
Si elles sont dites plus performantes, c’est qu’elles remplacent souvent a posteriori (en cas de problème) un mapping non réfléchi
Il n’y a pas de secret, l’entropie d’un système reste la même quelque soit votre solution :
- soit vous déléguez à hibernate&co la partie compliquée, au risque de mal l’utiliser
- soit vous passer le temps nécessaire pour arriver à une solution plus réfléchie
Erwan
March 31st, 2010 at 16:29
Bon comme tu le montres, Tom, les NamedQueries ne sont pas le Graal!
Néanmoins, ca me semble apporter déjà une vérification qui est mieux que rien! Une fois un client, m’a demandé de changer le nom de chaque table, mais pas la structure… Une vérification sur le nom des entités est toujours la bienvenue!
Et le chargement au démarrage et non au fil de l’eau, me fait rappeler l’esprit de l’optimisation “-server” des JVM. Mais je crois que dans le cas des NamedQueries, je classifierai cela parmi les pico optimisations.
Par contre, j’ai besoin d’éclaircissements sur un point plus important: est ce que JPA+NamedQuery et (Hibernate+Driver JDBC autorisant PreparedStatement) font le boulot en double avec leur cache?
March 31st, 2010 at 18:04
@Eric : JPA n’est que l’API. derrière, ce sera du PreparedStatement mais, comme le dit Antonio, la spec JPA ne préconise rien là-dessus.
Conclusion de tout ça : hibernate fait du PreparedStatement pour les query dynamiques, donc ce n’est pas vrai de dire qu’une NamedQuery est plus performante.
March 31st, 2010 at 18:06
Eric,
Je ne comprends pas le rapport avec les NamedQueries car qu’on les utilise ou non, JPQL se base sur le nom des entity et non pas des tables.
April 2nd, 2010 at 01:10
Merci Tom pour ta réponse.
@Alain, bien souvent, on mappe une table à une entité et réciproquement:
@Entity
@Table(name=”EXEMPLE”)
public class MaClass {
…
}
April 2nd, 2010 at 11:31
@Eric : Tout à fait, mais ici lorsque l’on écrit une requête JPQL on tapera sur l’entity MaClass et non pas sur la table EXEMPLE, et ceci avec ou sans une NamedQuery.
April 5th, 2010 at 18:24
@Alain, c’est une possibilité technique mais nous devons conserver certaines règles de nomenclatures et de lisibilités.
Par exemple, pour une meilleure lisibilité du code il est problématique d’avoir une table “T_ACHETEUR” dans la base de données, sa classe Java pour le mapping “P03_01_HumanBeingEntity” et une entitée JPA “buyer”.
Techniquement, c’est possible… mais lorsque le fonctionnel est complexe et les entités nombreuses, une nausée est vite arrivée chez le développeur.
Dans mon cas, la BDD était le fil rouge, le processus (dev Java) s’adapte à la BDD et pas l’inverse.
Afin de garder la logique de la nomenclature et faciliter sa maintenance, nous avons modifiés:
1- le nom des tables dans notre SGBDR favori
2- le nom des classes java
3- le nom de nos entités (@Entity(name = “myEntityName”))
4- nos requetes *QL
Le point 1 a pour partie sensible de ne pas faire de faute de frappe (jusque là, ca va)
Le point 2 a la même partie sensible que le point 1, le refactoring Java est simplifié par des EDI comme Eclipse et la compilation nous alerte et pointe les oublis et autres erreurs humaines.
Le point 3 a la même partie sensible que le point 1
Le point 4 ne pourra être testé que par des tests unitaires (mais une appli est rarement couverte à 100%) ou en recette (rarement couvert aussi à 100%).
Dans le cas du point 4, comment s’assurer rapidement d’une non régression?
La vérification des entités de NamedQuery permet d’éviter des oublis de non modification dans nos requêtes JPQL, sans attendre l’exécution de la requête.
August 3rd, 2010 at 22:02
Tiens, c’est marrant, en expliquant ceci, tu donnes quant même une piste intéressante qui pourrait être exploitée :
Virer la couche DAO et utiliser des namedQueries / et/ou entityManger direct dans la couche service.
Merci pour le post !