Accueil > Non classé > Pourquoi pas des requêtes spécifiques ?

Pourquoi pas des requêtes spécifiques ?

Aujourd’hui je vais vous présenter un petit truc tout bête pour améliorer les performances d’une application utilisant un ORM, et en particulier de l’affichage d’une page de type back-office.

Problème

Vous avez un problème de performance lors de l’affichage d’une page importante de votre application. La page en question ne contient en gros qu’un tableau de données, avec une vingtaine de colonnes, le tout paginé qui plus est. Un cas assez courant dans les applications de gestion type back-office.

Votre chef vous explique que les utilisateurs seraient plus heureux (plus productifs en tout cas) si la page s’affichait instantanément plutôt qu’en 5 ou 10 secondes. Votre AMOA vous regarde avec pitié et étonnement : elle affiche le même tableau sans soucis dans son Squirrel avec une requête SQL pas très compliquée. Le directeur de projet vous soutient qu’Hibernate/JPA c’est lent et qu’on était mieux en JDBC.

Pour afficher le tableau en question, vous passez par une requête JPA/QL qui sélectionne les entités que vous cherchez (disons Person), puis vous utilisez le graphe d’objets résultant pour afficher la page :


1
String hql = "select p from Person join fetch ... where p.age > 18 ...";

Bien sûr Person est une classe énorme avec toutes les informations imaginables (pays de naissance, nom de l’école maternelle, nom de jeune fille de la grand mère paternelle, …), et vous avez  besoin de fetcher des associations en plus pour charger vos informations. L’ORM traduit tout cela en une ou plusieurs requêtes peu efficaces (sous selects, jointures, …) et chargeant beaucoup de données (50 colonnes sur trois tables), dont finalement peu sont utilisées.

Solution

Une solution toute simple à cela, est de récupérer uniquement les informations que l’on désire afficher :


1
String hql = "select new PersonInfoQueryBean(p.firstname, p.lastname, p.age, p.sideInfos.birthCountry, ...) from Person p inner join p.children ... where ..."

Cette requête sera traduite en SQL par l’ORM, qui chargera uniquement les colonnes demandées (firstname, lastname, …), puis le résultat sera placé dans un bean qui servira pour l’affichage.

La requête exécutée est donc aussi rapide que celle de votre AMOA (normal c’est la même), et le sur-coût pour charger les données en mémoire et les afficher est peu significatif. La page s’affiche plus vite, votre chef est content.

Mais la requête est spécifique à ma vue !

Oui certes. Mais ne l’était-elle pas déjà ? Etait-elle utilisée ailleurs dans l’application ? Non ? Où est le souci alors ?

Il est de plus possible de « ranger » ce type de requêtes dans un « DAO » dédié, placé dans le même projet que la page affichée, pour éviter de surcharger du code central partagé avec du code spécifique à un client. C’est particulièrement judicieux si vous avez plusieurs requêtes de ce type.

Il y a plus de code à maintenir…

Effectivement, on a un bean en plus de l’entité, ainsi que la liste des champs utilisés qui s’ajoute à la masse de code de l’application. Pour une fonctionnalité importante, le coût ne me semble pas prohibitif.

Pourquoi pas du SQL (JDBC) directement alors ?

L’avantage du JPA/QL (alias EJB/QL, HQL …), c’est que l’on reste indépendant de la base de données utilisée. On peut par exemple utiliser HSQL pour les tests des requêtes, MySQL ou PostGreSQL sur les postes de développeurs et Oracle sur l’environnement cible.

Outroduction

Voilà, un petit truc pragmatique donc. Pour l’avoir appliqué, je peux dire que la différence peut être visible à l’oeil nu. Cela facilite de plus le tuning de requêtes et la recherche de possibilités d’optimisation sur les jointures et les indexations.

Share
  1. Stéphane Landelle
    20/01/2011 à 12:24 | #1

    Effectivement, du strict point de vue des performances, cette démarche permet d’obtenir de meilleurs résultats dans le cas où l’on ne souhaite récupérer pour une requête donnée qu’une partie des attributs de l’entité. En effet, Hibernate va générer une clause SELECT ne contenant que les champs sélectionnés et les Records seront donc plus petits.

    En revanche, elle présente un certain nombre d’inconvénients:
    *) le code devient plus difficile à maintenir : il faut définir sur les entités des constructeurs spécifiques prenant les différents attributs en paramètres. Que faire lorsqu’un veut rajouter un nouvel attribut? On le rajoute dans le constructeur et on fait une passe sur toutes les requêtes? On crée un nouveau constructeur? Pas évident…
    *) les beans ainsi retournés ne sont pas attachés à une session, on ne pourra donc pas charger d’associations de manière lazy

    A mon sens, si cette technique est intéressante, il faut la limiter à des cas bien particuliers. Peut être que si l’on se retrouve à ne vouloir charger qu’une partie des attributs, c’est que la modélisation est mal adaptée et qu’il aurait fallu splitter les données sur plusieurs tables et faire de la composition.

  2. Raphaël LEMAIRE
    21/01/2011 à 09:51 | #2

    Effectivement, comme je le dis dans l’article il y a plus de code à maintenir.

    Pour « résoudre » le premier point, tu peux utiliser la syntaxe du type select p.a, p.b, p.c, … from … ce qui renvoie un Object[] et ensuite utiliser des setters pour construire le bean : l’ajout d’un nouvel attribut ne casse plus le code. Mais c’est super verbeux.

    De plus si tu ajoute un attribut, il y a de forte chance que tu veuille le récupérer de la base également, et donc de passer sur toutes les requêtes.

    Enfin les beans sont spécifiques à une ou deux pages, une ou deux requêtes, le coût du changement reste raisonnable.

    Pour le second point, eh bien l’idée est de charger tout ce dont on a besoin d’un coup, sans sous select et sans lazy loading. Les beans ne sont pas des entités, mais juste des beans. Donc ce n’est pas un soucis.

    La modélisation peut très bien représenter les données sans forcement correspondre au tableau qu’on désire afficher. Par exemple on peut vouloir un tableau avec les numéros des contrats, le nombre de commandes associées au contrat et le nom et prénom de l’acheteur. Pour autant ces informations n’ont rien à faire dans la même entité (on a deux 1-N dans cet exemple).

    Je ne pense pas qu’il soit possible d’avoir une modélisation qui permette de résoudre facilement toute les requêtes d’une grosse application.

    Ma proposition dans cet article n’est pas un modèle pour toute l’application, mais la résolution pragmatique d’un problème précis.

  1. Pas encore de trackbacks