mars
2008
Tout programmeur ayant travaillé avec Hibernate ou JPA ou Spring ou tout autre framework suffisamment puissant a déjà au mieux entendu parler des proxie, et au pire en a souffert
Seulement, c’est un mal dont il faut vivre avec, car sans eux, on aurait pas les transactions déclaratives, le lazy fetching, etc.
Bref, je voudrais parler ici d’une utilisation intélligente des proxies: c’est pas mon idée, et ça a été abondamment utilisé dans d’autres situations, la plus récente étant sur le mailing list de Wicket où un (brillant) développeur a proposé un version du CompoundPropertyModel basé sur les proxies pour effectuer le binding d’une manière fortement typée.
Je m’en suis inspiré pour faire de même, mais pour Spring JDBC.
Il se trouve que je suis en train de développer un mini-framework au dessus de Spring JDBC qui permet d’automatiser quelques taches redondantes et répétitives.
Dans le cas le plus simple, il s’agit d’automatiser la création du ParameterizedRowMapper de Spring, qui permet de créer et remplir un Objet à partir d’un ResultSet (JDBC).
Par exemple, étant donné une entité Person:
public class Person {
private Long id;
private String name;
private Date birthDate;
//getters + setters
}
qui est persisté dans une table person(id, name, birth_date), le ParametrizedRowMapper Spring correspondant s’écrit:
public class PersonMapper implements ParameterizedRowMapper<Person> {
@Override
public Person mapRow(ResultSet resultSet, int arg1) throws SQLException {
Person person = new Person();
person.setId(resultSet.getLong("id"));
person.setName(resultSet.getString("name"));
person.setBirthDate(resultSet.getDate("birth_date"));
return person;
}
}
(J’en parle plus en détail dans ce billet).
Ce qui est fastidieuxx à la longue, mais surtout, ne représente aucunement matière à perdre du temps la dessus.
C’est pour ça que j’aimerais bien automatiser cela.
J’ai donc crée une sorte de mécanisme qui permet de décrire le lien entre une classe et une table (à la manière des hbm d’Hibernate ou encore les annotations de JPA), mais en plus simple : pas de relations.
Ce qui ressemble à ceci:
IMapping mapping = new SimpleMapping<Person>()
.table("person")
.clazz(Person.class)
.id("id")
.map("name")
.map("birthDate", "birth_date");
Il s’agit en gros d’une sorte de Map qui relie les champs de la classe aux colonnes de la table, mais présenté sous une forme plus expressive (notez le style fluent, via chainage), ainsi que un gain relatif en verbosité pour le cas où le champ et la colonne ont le même nom, auquel cas je ne spécifie que l’un d’eux (pour id et name).
J’ai aussi développé un ParameterizedRowMapper générique qui étant donné un mapping, fait le boulot tout seul, mais là n’est pas la question.
En effet, je voulais surtout améliorer le mécanisme du mapping, en essayant de limiter les erreurs de frappe potentielles.
C’est malheureusement inévitable pour les noms des colonnes, mais pour ce qui est des noms des champs, alors là on n’a pas d’excuses
J’ai ainsi mis en place un nouveau type de mapping fortement typé en utilisant les proxies.
Voici de quoi le mapping précédent a l’air avec celui-ci:
StronglyTypedMapping<Person> mapping = new StronglyTypedMapping<Person>(
Person.class);
mapping.setTableName("person");
mapping.id().getId();
mapping.map().getName();
mapping.map("birth_date").getBirthDate();
Ca a l’air … comment dire ? bizarre ? J’éprouve la même sensation en voyant ce bout de code (que j’ai écrit moi-même).
Seulement, c’est terriblement efficace : on élimine déjà la moitié des erreurs de frappe potentielles vu qu’on tape plus les noms des champs comme chaînes de caractères, et permet aussi de bénéficier de l’auto-completion et/ou refactoring.
Pour résumer, pour mapper un champ avec une colonne, je spécifie toujours le nom de la colonne (comme chaine de caractères), mais le champ est spécifié via le getter, ce qui donne:
mapping.map("birth_date").getBirthDate();
au lieu de:
mapping.map("birthDate", "birth_date");
Pour réaliser ça, j’ai eu recours au mécanisme de proxying, mais pas celui du JDK, mais via Javassist, une bibliothèque de manipulation de bytecode de chez JBoss.
En gros, la méthode map retourn un peoxy qui encapsule Person et qui intercepte tous les appels des getters, pour récupérer le nom du champ à lier.
(Je suis en train de voir si je peux garder le style fluent avec, mais c’est pas gagné !)
J’aurais aimé utiliser les Dynamic proxies du JDK (c’est toujours une bibliothèque de moins), seulement ils sont limités aux interfaces, ce qui est trop contraignant et insuffisant: ce serait ennuyant quand même d’imposer que ces PAOs (Persistent Anemic Object) implémentent des interfaces :-O
D’ailleurs, je suis d’avis d’améliorer les Dynamic Proxies standards de Java pour les étendre aussi aux classes dans la version 7.
Pour résumer, voici une bonne façon d’utiliser le proxying : en y allant d’une façon locale et sans polluer les autres couches (le cauchemar de tous: des DTOs au dessus de PAOs proxiés par le framework de persistance).
P.S.: Si ça intéresse quelqu’un, je pourrais poster le code de la chose
D’ailleurs, je pense publier le projet en entier une fois finalisé (il permet aussi de générer les requêtes select, update, insert, delete, ou encore d’avoir un DAO générique et complet).
En fait, j’ai expérimenté avec les relations (j’arrive par exemple à faire du 1..1, et ce une seule requête SQL, quel que soit le nombre de raltions), et ça se pourrait bien qu’on aurait bientôt un YAPF (Yet Another Persistence Framework).
P.S. 2: Pour ceux qui voudront me proposer gentiment d’utilier un framework existant telqu’Hibernate ou JPA, j’aimerais préciser que je n’ai pas le choix, et que je suis obligé de passer à coup de JDBC. J’essais juste de me simplifier la vie à moi et à mes collègues