19 avril 2011

JPA, et après ...

Ceux qui suivent ce blog connaissent déjà Fonzie, mon petit hack pour éliminer l'écriture de toute la couche persistance d'une application JavaEE dans le cas des requêtes simples, qui constituent d'après le projet sur lequel il a été utilisé plus de 90% des cas d'utilisation.

Avec une API puissante, JPA nous permet de construire des requêtes et du code d'accès à la base pour quasiment tous les cas de figure (bien sûr il reste quelques exceptions, sinon le groupe de travail JPA 2.1 serait au chômage). Seulement, cette API est parfois surdimensionnée pour des requêtes simples, de la forme select * from Truc where bibi = 'foc'

Fonzie était inspiré par Grails (GORM), mais n'est pas, loin s'en faut, la seule tentative de simplifier le codage des accès à la base de données. Il y avait déjà Hades, présenté il y a deux ans à Devoxx, qui permet de déclarer un Repository sous forme d'interface et de laisser le framework analyser les noms de méthodes pour en déduire les requêtes. Il y a depuis peu Spring-Data et son module JPA qui reprend la même idée avec la force de frappe de SpringSource/VMWare.

C'est bien, mais certains (j'en suis) regretterons que tous ces facilitateurs ne valident pas les requêtes définies par les programmeurs. Il faut attendre l'exécution pour que le framework nous indique qu'une méthode ne peut être interprétée. Un refactoring de trop et hop, tout plante.

Pour répondre à cette attente il y a donc, depuis JPA 2.0, l'API Criteria. Contrairement à Hibernate Criteria, celle-ci est totalement type-safe et valide la construction des requêtes dès la compilation. C'est donc une très bonne chose a priori. Sauf que ... Criteria s'impose de couvrir 100% des capacités de JPQL, et aboutit à une syntaxe extrêmement lourde et très peu lisible; jugez plutôt, pour la même requête pourtant triviale :

CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<truc> query = cb.createQuery(Truc.class);

        Root<truc> truc = query.from(Truc.class);
        query.where(cb.eq( truc.<string>get( Truc_.bibi ), "foc" ));

        TypedQuery<truc> tq = em.createQuery(query);

L'informatique semble avoir horreur du vide, aussi un framework se propose de combler ce manque de lisibilité. L'idée est de générer (avec un annotation processor Java6) un métamodel qui soit spécifique aux requêtes QueryDSL (les classes Q*), et pas juste le métamodel "neutre" défini par JPA (mais qui par contre pourait servir à d'autres frameworks ... si l'API était sortie du package javax.persistence). QueryDSL, qui d'ailleurs peut accéder à bien plus que JPA, propose de construire des requêtes avec une excellente lisibilité, et 100% type-safe :

import static QTruc.truc;

   public List<Truc> getBibiFoc() {
        return from( truc )
              .where( truc.bibi.eq( "foc" ) )
              .list( truc );
   }

C'est pas beau ça ? Aux parenthèses près, on se croirait sous .Net en train de coder avec LINQ !
Alors, évidemment, il est probable qu'en creusant un peu queryDSL ne supporte pas 100% de JPQL, et ne permette pas de faire un LEFT OUTER JOIN sur une Map d' Embeddable, mais en même temps, le jour où j'aurais vraiment besoin de ça, ce n'est pas de coder le Repository qui exécute cette requête qui va me coûter mais plutôt de comprendre comment on a pu pondre un modèle pareil...