Présentations « Lancer un service en 72h » et « L’impression 3D »

J’ai récemment eu l’occasion de présenter deux sujets au sein de Softeam qui ont été captés et diffusés sur YouTube :

  • L’impression 3D, comment ça marche ? : rapide tour d’horizon des techniques d’impressions, des imprimantes, des logiciels puis démo de la conception d’une pièce à son impression
  • Lancer un service en 72h, vous acceptez le challenge ? : outils et techniques mis en place pour mettre en production en 72h un service de crowdsourcing sur la disponibilité des stations de carburants

 

 

Premiers pas avec un JeeNode

N’ayant pas de programmateur sur mes radiateurs électriques dans mon appartement, j’ai décidé avant l’hiver de concevoir un petit programme permettant de régler la température de manière optimale.

Je me suis donc basé sur un Arduino, des capteurs de températeurs DS18B20, le tout relié à un mon mini-pc gérant la commande des radiateurs via Z-Wave. Pour récupérer la température dans ma chambre, j’ai profité des fils de téléphone inutilisés qui passaient déjà dans les murs.

Tout à très bien marché, le soucis c’est que je n’ai jamais pris le temps de finaliser correctement la partie « filaire » de l’installation, et je me suis donc retrouvé avec un arduino posé sur le sol de mon salon connecté à tous pleins de fils.

Voulant passer à quelque chose d’un peu plus intégré, j’ai donc commencé à regarder comment il serait possible de récupérer la température, de préférence sans-fil, pour pouvoir disposer les capteurs où bon me semble. Afin de limiter le coût, j’ai cherché à réutiliser des capteurs existants de stations météo. Cela permet aussi d’avoir des capteurs bien fini, étanche, avec le logement pour les piles, etc…

Il fallait donc trouver un système, de préférence à base d’Arduino, permettant de recevoir facilement les données sans-fil des sondes existantes. Après un peu de recherche, je suis tombé là dessus : http://gcrnet.net/node/32. D’après cet article, il est apparemment possible de récupérer la température des sondes La Crosse Technology TX29 à l’aide d’un clone d’Arduino, nommé JeeNode, possédant un émetteur/récepteur adéquat. La sonde TX29 est vendu 12,90€ chez Leroy Merlin et me permet pour le même prix d’avoir l’hygrométrie (selon le modèle) et un écran LCD :). Le JeeNode quant à lui est commandable sur le site du constructeur pour 18,50€ (auquel il faut rajouter un adaptateur USB à 13,50€ pour pouvoir le programmer). Voilà qui était dans mes prix !

J’ai donc passé commande du JeeNode et de son adaptateur et voici ce que j’ai reçu :jeenodev6_large

Effectivement, le JeeNode est en kit. 30 minutes et quelques coups de fer à souder plus tard, voici le résultat (note au passage, investissez dans un bon fer à souder, j’ai eu peur à plusieurs reprises d’avoir tout cramé) :
JeeNode v6

Puis viens le moment suspens, on branche tout ça au PC et… ça marche 🙂 On charge le programme disponible sur le site permettant de décoder les trames des sondes de température et on voit s’afficher les températures relevées par les sondes 🙂

Nous recevons le relevé de température toutes les 4 secondes. Chaque sonde dispose d’un identifiant généré aléatoirement lors de l’insertion des piles. Cet identifiant est affiché quelques secondes sur l’écran LCD de la sonde lors de la mise en route.

Comme je ne suis pas fan du mix Arduino/C du programme disponible sur le site gcrnet, j’ai repris le travail effectué et j’ai ré-implémenté tout ça dans un fichier .ino classique. Il est disponible ici : tx29_jeenode. J’y ai aussi ajouté la librairie RF12 modifiée.

Maintenant que tout fonctionne, il va falloir adapter le code pour la prochaine étape : la communication en I2C avec un routeur TP LINK TL-WR703N !

Configurer une WebApp avec JNDI

Une WebApp a quasiment toujours besoin de paramètres de configuration ne serait-ce que pour se connecter à une base de données.
Il existe plusieurs moyens de gérer ces paramètres de configuration tel que les profils Maven voire, si vous êtes resté au XXème siècle, la modification de fichier à la main. Cela demande cependant de livrer une version différente pour chaque environnement ce qui est source d’erreur et fait toujours râler les gens de la production.

Une autre solution existe, il s’agit de JNDI. JNDI va permettre de déclarer dans la configuration du serveur d’application les paramètres de configuration de votre WebApp et donc ainsi de déporter la configuration spécifique à l’environnement. Le principe de base se rapproche des variables d’environnement système à la différence que le serveur d’application va pouvoir directement référencer des objets comme des pools de connexions aux bases de données.
Cette solution a plusieurs avantages. D’une part les développeurs peuvent configurer leurs serveurs en local en gardant un code commun (au revoir les commit qui écrasent la config d’un autre développeur). D’autre part la production va pouvoir livrer le même package en PréProd et en Prod tout en gardant de leur côté les informations sensibles (mots de passe…) et ce, sans avoir à modifier le package à chaque livraison.

Voyons comment mettre en œuvre cette solution à travers la configuration d’une base de données et d’une simple chaîne de caractères.

WebApp

Les modifications dans la WebApp sont assez minimes. L’exemple ci-dessous utilise Spring MVC ce qui facilite encore d’avantage la récupération des objets JNDI.

Il faut tout d’abord définir dans le fichier web.xml les variables JNDI auxquelles vous allez vouloir accéder :

	<!-- JDBC -->
	<resource-ref>
		<res-ref-name>jdbc/jndiDS</res-ref-name>
		<res-type>javax.sql.DataSource</res-type>
		<res-auth>Container</res-auth>
	</resource-ref>

	<!-- Properties -->
	<resource-env-ref>
		<resource-env-ref-name>envName</resource-env-ref-name>
		<resource-env-ref-type>java.lang.String</resource-env-ref-type>
	</resource-env-ref>

La première ressource JNDI est une datasource qui va nous fournir directement l’objet pour se connecter à la base de données. C’est donc le serveur d’application lui même qui va gérer le pool de connexion à la base.
La deuxième ressource est une simple String qui nous donne le nom de l’environnement du serveur d’application.

Voyons maintenant comment injecter les valeurs de ces variables JNDI à l’aide de Spring MVC. Celui-ci dispose d’une balise jee:jndi-lookup pour ce faire :

    <!-- Database -->
    <jee:jndi-lookup jndi-name="jdbc/jndiDS" id="dataSource"/>

    <!-- Creating controllers -->
    <bean id="indexController" class="info.thomazo.alex.web.controllers.IndexController">
    	<property name="dataSource" ref="dataSource"/>
    	<property name="envName"><jee:jndi-lookup jndi-name="java:comp/env/envName"/></property>
    </bean>

Petite subtilité concernant le nom de la ressource String, le nom JNDI doit être précédé de java:comp/env/.

C’est tout, Spring s’occupe de faire le reste pour nous 🙂

Tomcat

Voyons maintenant les modifications à apporter à Tomcat.

La définition des ressources JNDI peut s’effectuer soit pour être commun à toutes les WebApp (un pool pour toutes les applications par exemple), soit pour être instanciée pour chaque WebApp (un pool instancié pour chaque application), soit être spécifique à une seule application.
N’hébergeant par habitude qu’une seule application par instance de Tomcat, j’ai opté pour la deuxième solution, soit une définition commune à tout le serveur mais tout de même instanciée pour chaque application.

La modification s’effectue donc dans le fichier conf/context.xml du répertoire d’installation de Tomcat entre les balises <Context> :

	<Resource
		auth="Container"
		driverClassName="com.mysql.jdbc.Driver"
		factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
		name="jdbc/jndiDS"
		username="dbuser"
		password="dbpass"
		type="javax.sql.DataSource"
		url="jdbc:mysql://dbhost:3306/dbname"
		testOnBorrow="true"
		validationQuery="SELECT 1"
	/>

	<Environment
		name="envName"
		value="dev"
		type="java.lang.String"
	/>

La configuration de la datasource se fait à l’aide de la balise Resource tandis que celle de la String s’effectue avec Environment. Voir en fin d’article pour les liens vers la configuration de la datasource.

Ne pas oublier non plus d’ajouter le jar correspondant à votre base de données dans le répertoire lib/ de Tomcat, dans notre exemple mysql-connector-java-x.y-bin.jar.

Et voilà, rien de bien compliqué non plus ici 🙂

Bien sûr, tous les serveurs d’application permettent de définir des variables JNDI, je vous laisse fouiller pour vous adapter à celui qui vous intéresse.

Log4J

Et si je veux aussi déporter mon log4j.xml à l’extérieur de mon war ?
Pas de problème, dans le projet exemple, j’ai ajouté la classe Log4JInitListener qui se lance donc au démarrage de la WebApp. Cette classe définie dans le fichier web.xml va chercher la ressource JNDI log4jConf qui défini le chemin vers un fichier log4j.xml.

Si la ressource JNDI n’existe pas, rien n’est chargé et le log4j.xml fourni dans le war continu d’être utilisé. Attention tout de même à ce que votre fichier XML externe ne comporte pas d’erreur. En effet, Log4J ne permet pas de détecter les erreurs de parsing d’un fichier donc s’il y a un problème lors de celui-ci, aucune configuration ne sera appliquée.

Conclusion

Vous avez maintenant les premières pistes pour utiliser JNDI. N’hésitez pas, malgré la configuration à faire à la première installation, cela simplifie toutes les autres livraisons 🙂

Voici quelques liens pour finir :

Ajouter des traces avec Byteman

Ne vous est-il jamais arrivé de tomber sur un soucis sur une application en production, le log ne produisant qu’un message abscons (voir aucun message) et ne vous aidant absolument pas à la résolution de votre problème. Bien sûr, comme il s’agit de production, on ne peux pas se permettre de modifier le code (si par hasard il est à notre disposition) et de relivrer afin de rajouter des traces un peu partout.

Comment faire pour s’en sortir ? Avant de vous énerver et de maudire le développeur sur 12 générations, je vous propose de jeter un œil sur Byteman. Byteman va nous permettre de modifier le code compilé à l’aide de règles avant le démarrage de notre programme.

Prenons un exemple simple, voici la classe dans laquelle le problème apparaît :

package org.alexthomazo.blog.byteman;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
	
	private static Logger log = LoggerFactory.getLogger(App.class);
	
	public static void main(String[] args) {
		List<String> files = Arrays.asList("/dir/inexistant", "/other/also-inexistant");
		
		for (String file : files) {
			if (!checkFile(file)) log.warn("File inexistant");
		}
	}
	
	private static boolean checkFile(String filename) {
		return new File(filename).exists();
	}
}

Si on execute cette classe, la sortie console va ressembler à ça :

18:36:56.136 [main] WARN  org.alexthomazo.blog.bm.App - File inexistant
18:36:56.178 [main] WARN  org.alexthomazo.blog.bm.App - File inexistant

On se rend tout de suite compte qu’avec ces logs on ne peut pas déterminer quel fichier est inexistant. C’est ici que Byteman entre en jeu. Il va nous permettre de rajouter une ligne de log lors de l’appel de la méthode checkFile().

Téléchargez la dernière version sur le site de Byteman (binaries + docs) : http://www.jboss.org/byteman/downloads
Même si Byteman ne s’installe pas à proprement parler, il est plus simple de l’utiliser en ajoutant des variables d’environnement :

export BYTEMAN_HOME=/downloaddir/byteman/
export PATH=${PATH}:${BYTEMAN_HOME}/bin
set BYTEMAN_HOME=C:\Downloads\byteman

Ajustez bien sûr le répertoire de téléchargement à l’endroit où vous avez décompressé Byteman.

Byteman fonctionne à l’aide de fichiers décrivant des règles permettant de rajouter du code aux endroits qui nous intéressent. Dans notre cas, nous voulons ajouter un log à l’entrée de la méthode checkFile(). Voici le fichier pour effectuer cela :

RULE display file checked
CLASS App
METHOD checkFile
AT ENTRY
IF true
DO org.alexthomazo.blog.byteman.App.logger.info("checking file [" + $1 + "]" )
ENDRULE

Chaque règle comporte différents attibuts :

  • RULE: Nom de la règle
  • CLASS: Classe sur laquelle ajouter le code
  • METHOD: Méthode sur laquelle ajouter le code
  • AT ENTRY: On souhaite ajouter notre code à l’entrée de la méthode
  • IF: L’exécution du code peut être conditionné, ici nous souhaitons l’exécuter à chaque appel donc on indique true
  • DO: Code a exécuter, $0 représente this, $1 le 1er paramètre de la fonction, $2, le 2ème, etc…
  • ENDRULE: Marqueur de fin de règle

Dans notre exemple la règle est assez simple, on exécute à l’entrée de la méthode checkFile, le code org.alexthomazo.blog.byteman.App.logger.info(« checking file [ » + $1 + « ] » ). Comme logger est une variable statique, nous devons l’appeler à l’aide du nom de sa classe. Si la variable avait été une variable normale, il aurait fallu utiliser $0.logger.

Voyons maintenant comment lancer Byteman. Il existe deux méthodes, la première en spécifiant toutes les options à java, la deuxième en utilisant le script fourni par Byteman. Voici un exemple pour Linux et Windows

$ java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:addlog.btm -jar byteman-test-0.0.1-SNAPSHOT.jar
OU
$ bmjava.sh -l addlog.btm -jar byteman-test-0.0.1-SNAPSHOT.jar
> java -javaagent:%BYTEMAN_HOME%/lib/byteman.jar=script:addlog.btm -jar byteman-test-0.0.1-SNAPSHOT.jar
OU
> bmjava -l addlog.btm -jar byteman-test-0.0.1-SNAPSHOT.jar

Attention, sous Windows vous devez vous assurer d’avoir le répertoire bin de Byteman dans votre path pour exécuter la deuxième méthode.

Voici ce que devrait maintenant afficher la console :

15:46:05.390 [main] INFO  org.alexthomazo.blog.byteman.App - checking file [/dir/inexistant]
15:46:05.404 [main] WARN  org.alexthomazo.blog.byteman.App - File inexistant
15:46:05.404 [main] INFO  org.alexthomazo.blog.byteman.App - checking file [/other/also-inexistant]
15:46:05.405 [main] WARN  org.alexthomazo.blog.byteman.App - File inexistant

Et voilà, vous récupérez le nom du fichier sur lequel le test est effectué et vous pouvez débloquer votre problème 🙂

Si par hasard vous n’avez pas les sources, je vous conseille d’utiliser JD-GUI qui fait du bon boulot pour vous les fournir 😛

La seule petite contrainte à tout ça, c’est qu’il faut au moins Java 6. Même si cela devrait être maintenant assez répandu, il existe encore pas mal de fous qui utilisent encore Java 5 (voir 1.4) :p

N’hésitez pas à creuser dans la doc, il y’a plein d’autres options : http://www.jboss.org/byteman/documentation (comme l’ajout de règle à la volée sur un programme en cours d’exécution)

Récupérer la console Maven sur Eclipse Indigo

Depuis l’installation d’Eclipse Indigo, la console Maven de m2e n’affichait plus rien. J’ai fini par regarder et il s’avère que le logging dans la console est un plugin à part, et que celui-ci est optionnel à l’installation de m2e !

Bref, voici le plugin a installer (avec un nom des plus explicites…) :

JPA 2 et Spring

Cet article a pour but de vous donner une introduction rapide de JPA 2 ainsi qu’un exemple de mise en place avec Spring.

JPA (Java Persistance API) est la norme JEE permettant de faire de la persistance en base de données. En d’autres termes, cela va permettre de sauvegarder les objets contenant vos données en base à l’aide d’une API normalisée et ce quelle que soit la base de données utilisée. Il s’agit du successeur d’Hibernate.

Spring va nous servir pour faire l’injection de dépendances (IoC). Je devrais pour bien faire parler de CDI qui est la norme JEE pour l’IoC (au même titre que JPA pour la persistance). Cependant, je n’ai pas encore eu le temps de me pencher dessus. Comme j’avais déjà la version Spring de fonctionnelle, j’ai préféré partir là dessus, l’adaptation à CDI ne devrait cependant pas poser trop de soucis.

Je vous conseille de récupérer le code, si vous avez git via un :

git clone git://github.com/alexthomazo/jpa2-spring.git

soit en téléchargeant l’archive ici ou encore en visualisation directe .

Le modèle

Le modèle utilisé comme exemple est celui d’une application permettant de gérer des albums photos ainsi que leur visionnage par des utilisateurs. Voici le modèle physique de données :

Les users appartiennent à un ou plusieurs groups. Chaque groupe a accès à un ou plusieurs photo_albums. Cependant, les utilisateurs peuvent avoir des privilèges particuliers, a savoir avoir un accès à un album en particulier (via la table photo_album_user_allowed) ou se voir refuser l’accès malgré son appartenance à un groupe ayant accès (photo_album_user_denied). Chaque photo_albums est composé d’un ou plusieurs album_items (photo ou vidéo).

Le script de création des tables se trouve dans src/main/db/schema.sql.

La configuration JPA

La première étape va consister à définir l’implémentation utilisée pour JPA. En effet, comme je le disais en introduction, JPA n’est qu’une API. Elle ne réalise rien en tant que tel. Il existe donc plusieurs implémentation de l’API tel que Hibernate de JBoss ou OpenJPA d’Apache.

Pour ce faire, il suffit de créer un fichier persistence.xml dans le répertoire META-INF du classpath (à l’aide de Maven, il sera donc logiquement dans src/main/resources).

Le contenu du fichier en lui même est assez succinct, il sert principalement à définir l’implémentation à utiliser (ici Hibernate) :

<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
	<persistence-unit name="appdb" transaction-type="RESOURCE_LOCAL">
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
	</persistence-unit>
</persistence>

Il ne reste plus qu’à rajouter la dépendance dans le pom.xml :

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-entitymanager</artifactId>
	<version>4.1.0.Final</version>
</dependency>

Le mapping

Afin de faire correspondre notre modèle de donnée avec nos classes Java, nous allons devoir indiquer à JPA comment réaliser le mapping entre nos tables, nos classes et nos attributs. Pour cela, des annotations sont à notre disposition. Prenons un exemple simple avec la classe Group (les classes sont dans le package org.alexthomazo.blog.model.db) :

@Entity
@Table(name="groups")
public class Group {

	private int groupId;
	private String title;

	private Set users;

	@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="group_id")
	public int getGroupId() {
		return groupId;
	}

	public String getTitle() {
		return title;
	}

	@ManyToMany(mappedBy="groups")
	public Set getUsers() {
		return users;
	}

	/* + setters */
}

La première annotation est @Entity qui déclare la classe comme un objet persistant. Elle est complétée par @Table qui précise le nom réel de la table dans la base de données. Si la classe et la table ont le même nom, elle peut être omise.

Le reste du mapping se fait ensuite de manière automatique en fonction des getters définis dans la classe. JPA mappe par défaut les attributs comme dans cet exemple avec title. Si l’on souhaite ajouter un getter qui n’a pas lieu d’être persisté, il faut l’annoter avec @Transient.

Certains attributs ont des annotations permettant de modifier leurs comportements et sont positionnées sur leurs getters. Par exemple, groupId a une annotation précisant son rôle de clé primaire (@Id), le fait que cette clé primaire est générée automatiquement par la base de données sous-jacente (@GeneratedValue) et le nom de la colonne en base (@Column).

Prenons maintenant l’exemple du mapping camera_owner qui lie un item à un user. Il s’agit donc ici d’un lien 1-n (un user associé à un ou plusieurs item). Dans la classe AlbumItem, nous avons :

@ManyToOne
@JoinColumn(name="camera_owner")
public User getCameraOwner() {
	return cameraOwner;
}

Nous définissons le type de relation à l’aide de @ManyToOne (ici 1-n). Puis nous spécifions le nom de la colonne qui sert pour la jointure avec @JoinColumn.

Le principe est le même concernant les relations n-n. Voyons l’exemple entre user et group (un user peut être dans plusieurs groupes et un groupe contient plusieurs users). Dans la classe User, nous avons :

@ManyToMany
@JoinTable(name="user_group",
	joinColumns=@JoinColumn(name="user_id"),
	inverseJoinColumns=@JoinColumn(name="group_id"))
public Set getGroups() {
	return groups;
}

Cette fois-ci nous spécifions une relation n-n avec @ManyToMany. Puis nous indiquons la table faisant le mapping entre user et group avec @JoinTable. Le paramètre joinColumns sert à préciser la colonne représentant la classe courante dans la table de jointure tandis que inverseJoinColumns permet de spécifier la colonne correspondant à l’extrémité de la relation (ici Group).

Nous pouvons alors spécifier dans la classe Group la relation inverse :

@ManyToMany(mappedBy="groups")
public Set getUsers() {
	return users;
}

Étant donné que la relation a déjà été entièrement décrite dans la classe User, il suffit ici de spécifier uniquement l’attribut auquel on fait référence dans la classe de destination à l’aide de @ManyToMany.

Voici pour les grandes lignes du mapping avec JPA. Bien sûr il existe d’autres annotations et d’autres paramètres pour faire bien plus que l’exemple, tout est indiqué dans la spécification JPA (voir a la fin de l’article pour le lien). Je fait aussi l’impasse sur les stratégies de chargement, lazy loading et autre joyeusetés, il s’agit de donner ici une première vue sur JPA.

Le méta-modèle

Maintenant que nos tables sont mappées avec nos classes, nous voulons pouvoir effectuer des requêtes sur celles-ci afin de récupérer nos données, les modifier, etc…

Il existe deux mécanismes de requêtage avec JPA : JPQL et l’API Criteria. Le premier s’approche plus d’un système comme SQL où l’on va énoncer sa requête sous forme textuelle :

SELECT u FROM User u

L’autre façon, l’API Criteria, va nous permettre d’énoncer les requêtes programmatiquement afin de valider la cohérence de celles-ci à la compilation à l’aide de la vérification des types des variables. C’est ce système que j’ai choisi d’utiliser.

Cependant nous allons avoir un soucis. En effet, comment faire pour exprimer nos conditions ? Si nous appelons l’accesseur de notre classe, nous allons récupérer la valeur de l’attribut et non sa représentation. Par exemple, si nous voulons récupérer la liste des utilisateurs dont le prénom est Alice, il va falloir trouver un moyen d’exprimer la représentation de l’attribut firstname et non sa valeur (une String en l’occurrence que l’on récupérerais avec getFirstname()).

Afin de pallier à ce problème, JPA introduit la notion de méta-modèle. Ce méta-modèle généré à partir de notre mapping va permettre de décrire les classes et les attributs de notre modèle. Concrètement, chaque classe va se voir créer une classe correspondante postfixée avec un underscore (User aura une classe User_, Group une classe Group_, etc…).

Si l’on regarde les classes ainsi générées, on remarque que pour chaque attribut de notre modèle, un attribut de classe correspondant a été créé. Par exemple pour Group :

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Group.class)
public abstract class Group_ {
	public static volatile SingularAttribute groupId;
	public static volatile SetAttribute users;
	public static volatile SingularAttribute title;
}

Les attributs ont un type spécifique générique comprenant la classe d’appartenance et le type de l’attribut source. Ainsi, le compilateur va pouvoir vérifier la cohérence de nos requêtes Criteria car il dispose d’un moyen d’exprimer le type d’un attribut.

La configuration de la génération de ce méta-modèle s’effectue dans le pom.xml au niveau des balises <build>. Grâce au plugin m2e-apt, il nous suffit de configurer la compilation du projet avec une JRE au minimum 6 pour la gestion des annotations (ici 7). Il faut aussi ajouter la dépendance suivante qui contient le code qui génère tout ça :

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-jpamodelgen</artifactId>
	<version>1.2.0.CR1</version>
</dependency>

Ne pas oublier d’installer le plugin m2e-apt via les connecteurs Maven (Window -> Preferences -> Maven -> Discovery -> Open Catalog -> m2e-apt) puis de l’activer après redémarrage d’eclipse (Window -> Preferences -> Maven -> Annotation Processing -> Automatically configure JDT APT).
S’il n’est pas disponible dans le catalogue, il est possible de l’ajouter via le site Eclipse suivant : http://download.jboss.org/jbosstools/updates/m2e-extensions/m2e-apt

À ce stade, nous avons un modèle de classes représentant nos objets en base de données ainsi qu’un méta-modèle nous permettant d’effectuer des requêtes sur ce modèle, voyons comment.

Les DAO

Les DAO vont contenir le code permettant d’effectuer les requêtes sur la base de données. Dans l’exemple, ils sont dans le package org.alexthomazo.blog.model.dao :

J’ai choisi de garder le modèle interface/implémentation dans l’optique de pouvoir changer si besoin de JPA vers un autre système. Ce système n’est pas forcément très pertinent (à mon avis le changement ne se fera jamais) mais à le mérite de montrer l’utilisation d’une interface/implémentation avec un système d’injection de dépendances.

Chaque DAO commence donc par définir une interface (ex: IUserDao). Afin d’éviter de réécrire les mêmes méthodes de base a chaque fois (get, update, list, etc…), chaque interface hérite de l’interface AbstractDao. Cette interface générique permet de définir un certain nombre de méthodes de base à implémenter.

L’implémentation passe donc par la création d’une classe dans le sous-package jpa. Afin de ne pas ré-implémenter les méthodes de base définie par AbstractDao à chaque fois, on hérite de la classe d’implémentation AbstractJPADAOImpl. Cette classe contient l’implémentation des méthodes de base ce qui permet de gérer dans notre implémentation que les méthodes spécifiques.

Si nous prenons l’exemple du DAO de User, qui ne contient donc aucune méthode spécifique, sa création se limite aux deux fichiers suivants :

public interface IUserDao extends AbstractDao {
}
@Controller
public class UserDao extends AbstractJPADAOImpl implements IUserDao {
}

Lors de la définition des classes, nous spécifions le type d’objet du modèle sur lequel le DAO agit ainsi que la clé primaire afin de faire fonctionner la classe générique AbstractJPADAOImpl. De plus, on ajoute sur l’implémentation l’annotation @Controller qui servira ensuite à Spring à créer l’objet (cf. paragraphe sur Spring).

L’API Criteria

Maintenant que nous avons un endroit où écrire nos requêtes, voyons un peu comment les écrire.

Commençons avec un exemple simple, comment récupérer la liste des utilisateurs :

CriteriaQuery q = getBuilder().createQuery(User.class);
q.select(q.from(User.class));
return getEm().createQuery(q).getResultList();

À l’aide du CriteriaBuilder, nous construisons une CriteriaQuery qui nous renverra des objets User. Puis, nous effectuons un select depuis les objets de la classe User. Nous utilisons enfin l’EntityManager pour exécuter notre requête et nous renvoyer la liste du résultat.

Si nous reprenons l’exemple de tout à l’heure et voulons sélectionner uniquement les utilisateurs ayant le prénom Alice, il faudrait écrire :

CriteriaQuery q = getBuilder().createQuery(User.class);
Root user = q.from(User.class);
q.select(user);
q.where(q.equal(user.get(User_.firstname), "Alice"));
return getEm().createQuery(q).getResultList();

On voit ici l’utilisation du méta-modèle via la classe User_ qui permet de faire une restriction sur le prénom.

Prenons un exemple un peu plus complexe, la récupération de la liste des Item dans un Album :

public List getList(int photoAlbumId) {
	CriteriaBuilder b = getBuilder();

	//creating criteria
	CriteriaQuery q = b.createQuery(AlbumItem.class);
	Root item = q.from(AlbumItem.class);
	q.select(item);

	//joins to fetch
	item.fetch(AlbumItem_.cameraOwner);
	item.fetch(AlbumItem_.photoAlbum);

	//adding restriction
	q.where(b.equal(item.get(AlbumItem_.photoAlbum), photoAlbumId));

	//ordering
	q.orderBy(
		b.asc(item.get(AlbumItem_.shootdate)),
		b.asc(item.get(AlbumItem_.file))
	);

	return getEm().createQuery(q).getResultList();
}

L’utilisation de fetch permet de charger les attributs « externe » de la classe au moment de la requête SQL (à l’aide d’un JOIN) afin d’éviter que le parcours de la liste des items ne déclenche une requête à chaque affichage du prénom du preneur de la photo par exemple.
La restriction s’effectue ensuite sur un autre attribut « externe ». On remarque que même si le type de cet attribut est de la classe PhotoAlbum, JPA s’accommode tout à fait de la clé primaire associée à cet objet et on évite ainsi soit de le récupérer avant, soit de créer un « faux » objet qui n’aurait contenu que la clé.
Le reste permet d’ordonner les résultats avant d’effectuer la requête en base.

L’API Criteria n’est pas spécialement très facile à prendre en main, cependant je trouve que la vérification des types à la compilation est un véritable plus et évite de récupérer des erreurs douteuses à l’exécution suite a une requête mal écrite et mal testée. Je vous conseille de regarder les quelques exemples fourni dans le mini-projet et de ne pas hésiter à en piocher d’autres sur Internet.

L’intégration avec Spring et les tests

Maintenant que nous avons notre modèle en place et que nous pouvons y faire de requêtes, voyons comment utilisez ça dans le cadre de tests unitaires.

Afin de créer les différents DAO définis ci-avant, on utilise Spring. Celui-ci va se charger de faire les new pour créer les objets (en singleton) et va venir injecter la référence de ceux-ci dans nos tests (et à termes dans nos services). Les tests se trouvent dans le même package que les DAO (comme ça on peux tester les méthodes protected) mais dans le répertoire src/test/java et ne seront donc pas inclus dans le JAR final.

Voyons l’exemple du test de GroupDao :

public class GroupDaoTest extends AbstractDaoTest {

	@Autowired
	private IGroupDao groupDao;

	@Test
	public void testCount() {
		assertEquals("Group Nb", 3, groupDao.count());
	}

	@Test
	public void testGet() {
		Group group = groupDao.get(2);
		assertEquals("name", "Friends", group.getTitle());
	}

	@Test
	public void testUsers() {
		Group group = groupDao.get(1);
		Set users = group.getUsers();
		assertEquals("nbUsers", 2, users.size());
	}
}

Afin de faire fonctionner Spring, la classe doit étendre de AbstractDaoTest. C’est cette classe qui s’occupe de démarrer le conteneur Spring qui créé les objets (avec @Component) et injecte les références.
Dans l’exemple, on voit que l’attribut groupDao de type IGroupDao a l’annotation @Autowired. Cette annotation Spring spécifie au conteneur que l’on souhaite se faire injecter la référence d’un objet implémentant l’interface IGroupDao. Étant donné qu’un seul objet déclaré en tant que tel existe (notre GroupDao avec son tag @Component), Spring va pouvoir y insérer la référence vers cet objet. Si aucun ou plusieurs objets avaient implémentés l’interface, Spring aurait levé une exception au démarrage de son conteneur.

Voyons comment fonctionne le démarrage du conteneur Spring dans la classe AbstractDaoTest :

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/appDb.xml"})
@Transactional
public abstract class AbstractDaoTest extends AbstractTransactionalJUnit4SpringContextTests {

	@Autowired
	DataSource dataSrc;

	private static boolean databaseLoaded = false;

	@Before
	public void beforeTest() {
		setDataSource(dataSrc);

		if (!databaseLoaded) {
			super.executeSqlScript("classpath:test-data.sql", false);
			databaseLoaded = true;
		}
	}
}

Plusieurs mécanismes rentrent en jeu ici :

  • l’annotation @RunWith est une annotation JUnit qui prend en paramètre la classe qui va servir à démarrer les tests. On lui indique une classe du framework de test de Spring qui va s’occuper de démarrer le conteneur Spring.
  • l’annotation @ContextConfiguration est une annotation Spring qui permet de spécifier le fichier de configuration Spring à utiliser pour démarrer le conteneur.
  • l’annotation @Transactional est une annotation permettant de spécifier que l’appel de chaque méthode dans la classe de test démarre une transaction sur la base de données. Si une exception est levée dans la méthode, un rollback de la transaction est effectué, si tout se passe bien, un commit est fait.
  • la classe hérite de AbstractTransactionalJUnit4SpringContextTests qui permet d’exposer une référence vers un object SimpleJdbcTemplate pouvant être utile si l’on souhaite passer des requêtes SQL en dur lors des tests (non utilisé ici). Elle sert aussi a gérer le mécanisme de transaction.

De plus, la classe comporte une méthode annotée avec @Before. Cette méthode est lancée avant chaque groupe de test et va insérer la référence de la datasource dans la classe parente. Si le jeu de donnée de test n’a pas encore été chargé, elle va le charger dans la base de données. On verra dans la configuration qu’on utilise ici une base de données embarquée en mémoire d’où l’obligation de recharger le jeu de test à chaque fois.

Enfin, voici le fameux fichier XML de configuration de Spring :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- H2 dataSource for testing environnement -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
		<constructor-arg>
			<bean class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
				<property name="driverClass" value="org.h2.Driver" />
				<property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;TRACE_LEVEL_SYSTEM_OUT=2" />
			</bean>
		</constructor-arg>
	</bean>

	<!-- provides a H2 console to look into the db if necessary -->
	<!-- 
	<bean id="org.h2.tools.Server-WebServer" class="org.h2.tools.Server"
			factory-method="createWebServer" depends-on="dataSource"
			init-method="start" lazy-init="false">
		<constructor-arg value="-web,-webPort,11111" />
	</bean>
	 -->

	<!-- Loading JPA -->
	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="jpaProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
				<prop key="hibernate.hbm2ddl.auto">create</prop>
				<prop key="hibernate.connection.release_mode">after_transaction</prop>
				<prop key="hibernate.show_sql">true</prop>
			</props>
		</property>
	</bean>
	
	<!-- Transaction Manager -->
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory"/>
	</bean>
	
	<!-- Command list scanning -->
	<context:component-scan base-package="org.alexthomazo.blog.model.dao"/>
</beans>

Trois beans sont créés et une configuration appellée :

  • Le bean dataSource instancie la base de données embarquée en mémoire H2
  • Le bean entityManagerFactory permet de créer l’EntityManager utilisé dans nos DAO et permettant d’effectuer les requêtes sur la base
  • Le bean transactionManager permet de gérer les transactions de la base de données
  • L’instruction component-scan indique à Spring quels packages scanner à la recherche d’annotation @Component pour savoir quels objets créer

Pour finir, il faut bien sûr importer toutes les dépendances qui vont bien dans le pom.xml :

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-orm</artifactId>
	<version>${spring.version}</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-tx</artifactId>
	<version>${spring.version}</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>${spring.version}</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.10</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<version>1.3.164</version>
	<scope>test</scope>
</dependency>

Il suffit maintenant de lancer notre test dans Eclipse avec un simple Run as > JUnit Test ou créer une configuration pour lancer les tests de tout le projet :

Si tout s’est bien passé, tout devrait être vert :

Conclusion

Ouf, voilà la fin. Finalement ça fait pas mal d’un coup, ceci dit une fois qu’on a compris comment tout ça s’imbrique, ça va un peu mieux.

N’hésitez pas à forker, à commenter, etc… 🙂

Pour finir, quelques liens utiles :