Un DSL SQL pour Java

Alors que Hibernate est très largement répandu dans les projets Java pour accéder à une base de données relationnelle, il arrive que l’utilisation en direct de l’API JDBC reste pertinente.
En effet, il demeure intéressant de rester en SQL « pur » plutôt que de sortir la grosse artillerie lorsque :

  • les données de la base sont plutôt pensées tuple qu’objet : le modèle de données ne présente pas de relations complexes entre
    elles.
  • lors de la reprise d’un existant, il arrive que du code métier soit implémenté en PL/SQL par exemple. Les requêtes peuvent alors faire régulièrement appel à ces fonctions.

Lorsque nous sommes arrivés sur un projet de migration d’une application existante qui cumulait les contraintes précédentes (surtout beaucoup de procédures stockées), JDBC nous semblait une bonne idée. Seulement voilà, lorsqu’il a fallu écrire la requête à 60 critères de recherche qui étaient présents selon les critères que l’utilisateur avait entré sur sa belle IHM, je me suis posé la question de comment faire pour construire ladite requête.
Fallait-il faire des tas de « if » pour tester la présence d’une valeur dans chacun des critères de recherche ? Faire les jointures nécessaires selon les critères fournis ? J’ai entraperçu quelque chose de pas très sympa à écrire et à maintenir.

Pourquoi ne pas encapsuler le choix d’ajouter les clauses SQL à ma requête derrière une API qui le ferait selon les valeurs que je lui donne ? Et puis pourquoi pas faire ressembler cette API à du SQL pour pas inventer une nouvelle API ?

Voilà donc comment est né ce petit DSL Java pour produire des requêtes SQL. Pour le cas d’usage précédemment décrit d’une recherche multicritère, il suffit d’écrire une seule fois la requête et les filtres s’activeront ou désactiveront selon les valeurs fournies à ces filtres : null, le filtre est retiré de la requête; non null, le filtre est conservé et la valeur fournie est variabilisée.

Par exemple :

SelectQuery query = select(c("firstname"), //
            c("lastname"), //
            f("myFunc", 42)) //
      .from("client") //
      .where(c("firstname")).eq(criteria.getClientFirstname()) //
      .and(c("lastname")).eq(criteria.getClientLastname()) //
      .and(c("age")).geq(criteria.getClientMinAge()) //
       ....

Une utilisation typique avec un SimpleJdbcTemplate de Spring devient :

List<client> clients = getNamedParameterJdbcTemplate().query(query.toSql(),
       query.getParams(), new ClientRowMapper());

Le résultat du toSql() deviendra (si les valeurs sont non null) :

SELECT firstname, lastname, myFunc(:myFunc1) 
FROM client 
WHERE firstname=:firstname2 
AND lastname=:lastname3 
AND age=:age4 ...

Il reste toutefois possible de tester la valeur NULL dans la requête SQL.
Soit en encapsulant la valeur dans un objet Nullable :

...
     .where(c("fisrtname")).eq(new Nullable(criteria.getClientFirstname())) //
...

Soit en utilisant l’opérateur eqOrIsNull :

...
    .where(c("firstname")).eqOrIsNull(criteria.getClientFirstname()) //
...

Au final, même si d’autres langages tel que Groovy ou Scala auraient permis d’obtenir une syntaxe du DSL plus claire, j’ai été
agréablement surpris du résultat obtenu en Java qui reste suffisament lisible.

Le code source est disponible sur GitHub à l’adresse suivante :
https://github.com/octo-technology/java-sql-dsl

Il n’est pas complet, tout SQL n’est pas (encore) accessible, mais allez-y, forkez ;)

8 commentaires sur “Un DSL SQL pour Java”

  • Bonjour, dans la même veine, j'ai découvert il y a peu queryDsl qui a une approche similaire, le tout avec typage fort qui évite les erreurs du type 'c("fisrtname)' présent dans l'avant dernier exemple. Le site de ce projet est ici : http://source.mysema.com/display/querydsl/Querydsl Pour information, ils ont des bindings pour tout un tas de technologies dont SQL, Hibernate et Lucene. Un projet à suivre. Florent.
  • Ce DSL rapide me fait penser fortement à ce qu'un autre projet a mis/tenté de mettre en place : LIQUidFORM, http://code.google.com/p/liquidform/ il est déjà assez bien abouti et complet, et utilisé en prod sur au moins un des projets sur lesquels j'ai travaillé :)
  • Merci pour les liens vers QueryDsl et LIQUidFORM que je ne connaissais pas. QueryDsl semble intéressant pour profiter d'un typage fort (oui le 'c("firstname")', c'est pas le top ;) ) et pour profiter d'une aide à l'écriture des requêtes dans son IDE. Quand à LIQUidFORM, je comprends bien le besoin de pouvoir refactorer le code Java sans casser les requêtes JPA. Par contre, le seul besoin que j'avais était de me simplifier l'écriture de requêtes multicritères en supprimant les clauses inutiles avec du JDBC. Et, aucun des 2 projets ne semble le faire ?
  • Merci pour les liens vers QueryDsl et LIQUidFORM que je ne connaissais pas. QueryDsl semble intéressant pour profiter d'un typage fort (oui le 'c("firstname")', c'est pas le top ;) ) et pour profiter d'une aide à l'écriture des requêtes dans son IDE. Quand à LIQUidFORM, je comprends bien le besoin de pouvoir refactorer le code Java sans casser les requêtes JPA. Par contre, le seul besoin que j'avais était de me simplifier l'écriture de requêtes multicritères en supprimant les clauses inutiles avec du JDBC. Et, aucun des 2 projets ne semble le faire ?
  • As-tu regardé ce que propose le Framework iBatis? http://ibatis.apache.org/java.cgi http://svn.apache.org/repos/asf/ibatis/java/ibatis-2/trunk/ibatis-2-docs/en/iBATIS-SqlMaps-2_en.pdf Il gère notamment les paramètres optionnels: select * from ACCOUNT ACC_FIRST_NAME = #firstName# ACC_LAST_NAME = #lastName# ACC_EMAIL like #emailAddress# ACC_ID = #id# order by ACC_LAST_NAME
  • iBatis devient mybatis sur Google code ... Inutile de ré-inventer la roue ..
  • @Adrien Désolé pour le délai de réponse. J'ai regardé rapidement la doc de iBatis (et myBatis). Quand tu parles de paramètres optionels, fais-tu référence à la fonctionnalité "Dynamic SQL" de *Batis ? Si c'est bien celle là, je vois pas bien son utilité si on utilise pas déjà *Batis. Exemple tiré de la doc :
    <select id=”findActiveBlogLike”
          parameterType=”Blog” resultType=”Blog”>
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’
      <if test=”title != null”>
        AND title like #{title}
      </if>
      <if test=”author != null and author.name != null”>
        AND title like #{author.name}
      </if>
    </select>
    
    En fait, c'est le même principe que de le faire avec du code Java et encapsuler les "if" est l'objectif que je cherchais à atteindre. Si ce n'est pas de cette fonctionnalité dont tu parlais, je veux bien un lien vers la doc associée. Merci.
  • C'est exactement pour les raisons susmentionnées, que j'ai crée jOOQ (pour Java Object Oriented Querying). jOOQ permet d'accéder à une base de données à travers d'une DSL. Comme peu d'autres outils similaires, la DSL supporte toutes les propriétés SQL, y inclus les UNION, "nested SELECT", tous les JOIN, "aliasing" (p.ex. pour réaliser un self-join). Y inclus sont aussi des fonctionnalités SQL non-standard comme le support pour UDT, stored procedures, etc. Ceci peut être fait en utilisant du code source généré à partir du schéma de la base de données. Des exemples d'utilisation jOOQ: https://sourceforge.net/apps/trac/jooq/wiki/Examples
    1. Laisser un commentaire

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


      Ce formulaire est protégé par Google Recaptcha