documentation officielle ou vous trouverez de nombreuses informations sur les possibilités du framework.
Maintenant, attaquons notre premier batch...
J'utilise Maven 2 pour construire mon projet : l'ajout de Spring-batch à mon projet se résume alors aux dépendances à ajouter dans le fichier pom.xml
:
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-infrastructure</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
Aux dépendances Spring-batch, il faut ajouter, pour cet exemple, quelques dépendances Spring. Pour plus de concision, j'ajoute la dépendance globale vers Spring, mais on pourra être plus rigoureux par la suite et n'ajouter que les modules nécessaires à notre projet.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.5</version>
</dependency>
Notre exemple est un batch qui va effectuer un traitement sur des personnes. Pour modéliser cette donnée, j'utilise un simple POJO :
public class Person {
private Long id;
private String name;
// ... getters & setters
@Override
public String toString() {
return "Person:id="+id+",name="+name;
}
public Person(Long id, String name) {
super();
this.id = id;
this.name = name;
}
}
L'objectif est d'avoir un batch qui lit un ensemble de personnes dans une source de données et traite ces données. Dans cet exemple, nous allons lire un ensemble de données dans un tableau statique. Le traitement consistera simplement à écrire ces objets dans la sortie standard.
Pour comprendre ce que nous codons, nous avons besoin d'introduire les notions suivantes, qui ne sont qu'une partie des notions de Spring-batch :
Un batch correspond à une classe de type Jobs
. Ces Jobs
contiennent une séquence de Steps
. Un Step peut être de type item-oriented ou tasklet. Dans notre cas, nous souhaitons traiter un ensemble de personnes par lot : le type de step adapté est donc item-oriented. Un step de ce type a besoin de 2 objets : un ItemReader
et un ItemWriter
.
Ces concepts sont très importants dans Spring-batch. Je ne les détaille pas tous ici, car ce n'est pas l'objet de cet article. Je vous recommande d'aller lire cette page qui les détaille : http://static.springsource.org/spring-batch/1.0.x/spring-batch-docs/reference/html/core.html
Spring-batch fournit une implémentation par défaut de Job et de Step. Les seules choses que nous allons coder ici sont l'ItemReader et l'ItemWriter de notre classe Person : en effet, pour exécuter le step, Spring-batch a besoin de savoir comment lire une personne et comment l'écrire.
A noter que Spring-batch vient avec des ItemReaders et ItemWriters préconçus. Exemple : fichier plat, XML, requête SQL, etc. Voir ici : http://static.springframework.org/spring-batch/1.0.x/spring-batch-docs/reference/html/spring-batch-infrastructure.html
Cette classe est donc chargée de récupérer les données. Dans notre cas, on lit simplement une variable statique. La méthode principale est donc la méthode read
qui a pour rôle de lire un morceau unitaire de donnée (l'item). Les méthodes mark
et reset
permettent respectivement de mémoriser une position dans le flux de données (ici : l'ensemble des personnes) et de revenir à cette position.
public class PersonReader implements ItemReader {
static Person personArray = new Person[100];
static {
for (int i = 0; i < 100; i++) {
personArray[i] = new Person(((Integer) i).longValue(), "name"+i);
}
}
static int readIndex = -1;
public void mark() throws MarkFailedException {
readIndex++;
}
public Object read() throws UnexpectedInputException, NoWorkFoundException, ParseException {
if (readIndex>=personArray.length) {
return null;
}
return personArray[readIndex];
}
public void reset() throws ResetFailedException {
}
}
Cette classe définit comment écrire l'objet que l'on nous passe. En clair, on répond ici à la question "Que fait-on avec chaque personne ?", ceci par la méthode write
. La méthode clear
permet d'effacer le contenu du buffer d'écriture, et flush
permet de provoquer immédiatement l'écriture du buffer.
public class PersonConsoleWriter implements ItemWriter {
private StringBuilder sb = new StringBuilder();
public void clear() throws ClearFailedException {
sb = new StringBuilder();
}
public void flush() throws FlushFailedException {
System.out.print(sb);
sb = new StringBuilder();
}
public void write(Object o) throws Exception {
Person person = (Person) o;
sb.append(person.toString()+"n");
}
}
Nous avons donc nos deux classes composant notre batch, on peut donc s'intéresser à la partie la plus compliquée de Spring-batch : la configuration.
La configuration se fait dans un fichier XML Spring classique que nous avons appelé ici : batch-sample-context.xml
.
Tout d'abord, il faut configurer nos deux beans correspondant au reader et au writer :
Ensuite, il faut configurer le composant qui permet de lancer un batch, le "jobLauncher". La façon la plus simple d'instancier cet objet est d'utiliser la classe SimpleJobLauncher :
Simple, mais on voit que l'on a besoin d'un "jobRepository" qui permet de suivre et de reprendre l'avancement des taches. L'utilisation de la classe MapJobRepositoryFactoryBean
permet encore un fois d'avoir une configuration réduite, mais vous serez amené à utiliser d'autres formes de repository dès lors que vous voudrez utiliser des fonctionnalités de Spring-batch telles que la possibilité de rejouer un batch, ou bien de pouvoir conserver l'historique de façon persistante afin de tracer quel jobs et quels steps se sont exécutés, quand, et avec quel code retour (OK, KO...).
On voit que l'on a besoin d'un transaction manager. Cette propriété est obligatoire, ce qui est à mon sens dommage pour les cas simples comme le nôtre où nous n'utilisons pas les transactions. Heureusement, il existe une classe nous permettant de nous passer d'autres objets : ResourcelessTransactionManager
. Il s'agit en quelque sorte d'un transaction manager "bouchon" à utiliser quand on n'a pas besoin de transactions impliquant des ressources transactionnelles telles que bases de données, JMS, XA...
Une fois ces objets techniques configurés, il nous reste le gros du sujet : la configuration de notre batch.
Comme dit précédemment, un batch est formé d'un job qui lui-même est composé d'étapes (steps) :
Et nous avons alors une configuration complète de notre batch, ouf !
Pour voir la configuration complète : batch-sample-context.xml
Une fois tout ce travail effectué, nous avons envie de pouvoir lancer notre batch. Spring-batch fournit le nécessaire pour lancer le batch en ligne de commande.
Pour le test, j'utilise Eclipse : aller dans le menu "Run > Run as..." et configurer une nouvelle application Java avec :
org.springframework.batch.core.launch.support.CommandLineJobRunner
batch-sample-context.xml minimal
Le premier argument est le nom du fichier Spring à utiliser. Le second est le nom du job à lancer (ID du bean Spring).
Vous trouverez aussi un fichier .launch pour eclipse dans le projet complet (voir plus loin pour y accéder)
Pour tester avec Maven 2, il faut lancer la commande suivante :
mvn compile exec:java -Dexec.mainClass="org.springframework.batch.core.launch.support.CommandLineJobRunner" -Dexec.args="batch-sample-context.xml minimal"
De part ses possibilités, Spring-Batch requiert une configuration complexe qui au premier contact peut être assez repoussante. Ce premier batch est simpliste et très réducteur. Par exemple, l'utilisation des classes ResourcelessTransactionManager
et MapJobRepositoryFactoryBean
réduit considérablement les possibilités de Spring-batch.
Néanmoins, j'espère que ce premier pas vous sera utile pour ensuite vous plonger dans ce framework en profondeur, et pouvoir ajouter au fur et à mesure les concepts et fonctionnalités que l'on trouvera dans la documentation et les exemples. L'exemple complet est disponible sur la forge publique Octo : minimal-spring-batch-sample
Bon courage à vous !
Article écrit par Benoit Lafontaine et Julien Jakubowski