Un DSL SQL pour Java

le 21/05/2010 par David Rousselie
Tags: Software Engineering

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 ;)