A la découverte de Spring Batch

1. Introduction

 

Spring-Batch répond à un besoin récurrent : la gestion des programmes batchs écrits en Java.Spring-Batch est un framework issu de la collaboration de SpringSource et Accenture.Si le framework semble de plus en plus complet et fonctionnel, celui-ci souffre de sa complexité de configuration et reste un peu difficile d’accès malgré les efforts de l’équipe de développement.

 

Il permet de palier à des problème récurrents lors de développement de batchs:

 

  • Traitement « par lot » pour éviter par exemple de charger en mémoire l’ensemble des données traitées par le batch. Ce type de fonctionnement est adapté à des traitements sur de gros volumes de données. De plus, Spring-batch fournit des implémentations de classes permettant de lire ou d’écrire par lot sur divers types de supports (SQL, fichier plat ; etc.), ce qui évite de réinventer la roue

 

  • Gestion des transactions : Spring-batch s’appuie sur la gestion des transactions fournie par Spring, et permet donc de gérer de façon déclarative les transactions dans vos batchs.

 

  • Gestion de la reprise sur erreur, encore une fonctionnalité que le framework vous aide fortement à mettre en oeuvre.

 

  • Utilisation de Spring : le développeur qui a l’habitude de Spring peut réutiliser facilement ses notions ainsi que les composants de ce framework tels que, par exemple, les JdbcTemplates ou encore l’intégration à Hibernate…

 

  • Cadre de développement : à mon sens, un des apports les plus fondamentaux de Spring-batch est de proposer un cadre de développement autour de notions communes comme par exemple Job, Step, ItemWriter etc, ce qui aide beaucoup à la maintenabilité du code des batchs : un développeur qui doit maintenir différents batchs peut passer de l’un à l’autre, le logiciel est organisé autour des mêmes classes et interfaces.

 

 

Il s’agit donc d’ un framework fournissant un outillage complet, adapté à l’écriture des batchs.

Pour rappel dans le monde informatique, un batch est un programme fonctionnant en StandAlone, réalisant un ensemble de traitements sur un volume de données.

 

2. Architecture

 

 

L’architecture de Spring-Batch est constituée en 2 couches:

 

  • la couche Batch Core
  • la couche Batch Infrastructure

 

La couche « Batch Core » contient une API permettant de lancer, monitorer et de gérer un batch.
Les principales interfaces et classes que contient l’API sont: Job, JobLauncher et Step.
Schématiquement un batch correspond à un job qui va être lancé via un JobLauncher.

Un job est constitué d’une ou plusieurs Step. Une Step correspond à une étape dans un batch.

La couche « Batch Infrastructure » contient un API fournissant comme principales interfaces: ItemReader, ItemProcessor et ItemWriter.

 

Il y a bien d’autres raisons d’utiliser Spring-batch et je vous invite à aller lire la documentation officielle ou vous trouverez de nombreuses informations sur les possibilités du framework.

Maintenant, attaquons notre premier batch…

3. Etude de cas

 

Admettons que nous voulions traiter le fichier CSV avec le contenu suivant:

 

1,DUFOUR,Paul,M

2,DUPUIS,Jean,Mme

3,DUPOND,Guillaume,M

4,MARTIN,Bob,M

5,MICHEL,Sophie,Mme

6,MERCIER,Muriel,Mme

7,VIDAL,Thomas,M

8,JACKSON,Michael,M

9,BROWN,Michele,Mme

10,WILLIAMS,Venus,Mme

 

Il s’agit d’un fichier plat contenant 10 enregistrements de personnes, dont les informations sont séparées par le caractère « , » .

Les informations sont dans l’ordre: l’id de la personne, le nom, le prénom et la civilité.

Nous voulons que notre batch enregistre les personnes en base de données uniquement si leur civilité à la valeur « M ».

Mise en place du projet

Base de données

Pour cela, je vais utiliser une base MySQL, et je vais créer une table PERSONNE ayant la structure suivante:

 

CREATE TABLE PERSONNE (ID INTEGER,

NOM VARCHAR (50),

PRENOM VARCHAR (50),

CIVILITE VARCHAR (3),

PRIMARY KEY (ID)

);

 

Récupérer Spring 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-infrastructure</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</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> 

 

Implémentation du batch

La classe métier  Personne

Cette classe est un JavaBean qui va encapsuler les données d’une personne lue à partir du fichier texte.

Cette classe s’écrit de la façon suivante:

public class Personne {                                                                                                     
private int id;
private String nom;private String prenom;
private String civilite;
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public String getCivilite() {
return civilite;
}
public void setCivilite(String civilite) {
this.civilite = civilite;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}

 

Définition de l’ItemReader

Spring Batch fournit des implémentation de l’interface ItemReader notement pour parser des fichier plats et des fichiers XML.

Notre reader va être déclaré dans le fichier de configuration de la façon suivante:

 

<bean id="personneReaderCSV" class="org.springframework.batch.item.file.FlatFileItemReader">
	<property name="resource" value="input/personnes.txt" />
	<property name="lineMapper">
		<bean class=”org.springframework.batch.item.file.mapping.DefaultLineMapper”>
			<property name=”lineTokenizer”>
				<bean
					class=”org.springframework.batch.item.file.transform.DelimitedLineTokenizer”>
					<property name=”delimiter” value=”,” />
					<property name=”names” value=”id,nom,prenom,civilite” />
				</bean>
			</property>
			<property name=”fieldSetMapper”>
				<bean
					class=”org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper”>
					<property name=”targetType” value=”ideo.springbatch.poc.Personne” />
				</bean>

			</property>

		</bean>

	</property>

</bean>

 

Dans cette configuration nous précisons que:

– la classe FlatFileItemReader (pour lire un fichier plat) sera utilisée comme ItemReader pour lire notre fichier,

– le séparateur de champ est le caractère « , »

– chaque ligne est composée des champs: id,nom,prénom et civilité

– toutes ces informations seront stockées dans la classe Personne.

Nous avons donc définit notre reader sans rien coder! Bien sûr, il est possible de définir notre propre implémentation de l’interface ItemReader.

Définition de l’ItemProcessor

C’est dans l’ItemProcessor qu’il y aura la place pour implémenter les règles de gestion de notre batch.

Notre classe d’implémentation sera écrite de la façon suivante:

 

public class PersonProcessor implements ItemProcessor<Personne, Personne>
{

public Personne process(final Personne personneInput) throws Exception
{

Personne personneOutput = null;

//si la civilite a la valeur M la personne sera ecrite en base sinon on la  rejett
if (“M”.equals(personneInput.getCivilite()))
{

personneOutput = new Personne();

personneOutput.setCivilite(personneInput.getCivilite());

personneOutput.setId(personneInput.getId());

personneOutput.setNom(personneInput.getNom());

personneOutput.setPrenom(personneInput.getPrenom());

}

return personneOutput;

}

 

 

 

Puis dans le fichier de configuration, l’ItemProcessor sera définit de la façon suivante:

<bean id="personProcessor" class="ideo.springbatch.poc.PersonProcessor" />

 

Définition de l’ItemWriter

L’ItemWriter va persister les données qui ont été traitées via l’ItemProcessor. Dans cet exemple je vais sauvegarder les données en base de données dans la table PERSONNE, en utilisant le template jdbcTemplate fournit par Spring.

Ce qui va donner dans la classe d’implémentation:

@Transactional (propagation=Propagation.REQUIRED, rollbackFor=Exception.class)

public class PersonJdbcWriter implements ItemWriter<Personne>{

private JdbcTemplate jdbcTemplate;

private static final String REQUEST_INSERT_PERSONNE = "insert into PERSONNE (id,nom,prenom,civilite) values (?,?,?,?)";

private static final String REQUEST_UPDATE_PERSONNE = "update PERSONNE set nom=?, prenom=?, civilite=? where id=?";

public void write(List<? extends Personne> items) throws Exception {

for (Personne personne : items) {

final Object object [] = {personne.getNom(),personne.getPrenom(), personne.getCivilite(),personne.getId()};

//on tente un update

int nbLigne = jdbcTemplate.update(REQUEST_UPDATE_PERSONNE, object);

//si le nombre de ligne mise a jour vaut 0, on fait un insert

if (nbLigne == 0) {

final Object object2 [] = {personne.getId(),personne.getNom(),personne.getPrenom(), personne.getCivilite()};

jdbcTemplate.update(REQUEST_INSERT_PERSONNE, object2);

} else {

}

}

}

public JdbcTemplate getJdbcTemplate() {

return jdbcTemplate;

}

public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {

this.jdbcTemplate = jdbcTemplate;

}

}

 

Dans le fichier de configuration Spring, l’ItemWriter sera définit de la façon suivante:

<bean id="personDaoWriter" class="ideo.springbatch.poc.PersonJdbcWriter" >

<property name="jdbcTemplate" ref="idJdbcTemplate" />

</bean>

 

Définition du job

 

Le job sera défini de la façon suivante:

 

<job id="importPersonnes"

xmlns="http://www.springframework.org/schema/batch">

<step id="readWritePersonne">

<tasklet>

<chunk reader="personneReaderCSV" writer="personDaoWriter" processor="personProcessor" commit-interval="2" />

</tasklet>

</step>

</job>

 

Notre job sera donc constitué d’une seule Step dont la tasklet est de type « Chunk ».

Le chunk est un concept important dans Spring Batch qui définit l’enchainement qui va s’executer dans une transaction.
Le chunk aura comme propriétés l’item reader, l’item processor et l’item writer définit plus haut.
Le paramètre commit-interval du chunk définit le nombre d’items qui va être stocké en mémoire avant d’être persistés.

Pour executer notre batch il suffit de lancer le programme main suivant:

 

public class BatchPersonne
{

public static void main (String [] args) throws Exception
{

ClassPathXmlApplicationContext cpt = new ClassPathXmlApplicationContext("batch-context.xml");

cpt.start();

JobLauncher jobLauncher = (JobLauncher) cpt.getBean("jobLauncher");

Job job = (Job) cpt.getBean("importPersonnes");

JobParameters parameter = new JobParametersBuilder().addDate("date", new Date()).addString("input.file", "C:/envdev/travail/in/personnes.txt").toJobParameters();

jobLauncher.run(job, parameter);

}

}

 

Conclusion

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. Il réduit considérablement les possibilités de Spring-batch.

 

Spring Batch présente des fonctionnalités avancées assez intéressantes:

– gestion de la reprise sur erreur
– possibilité de « skipping »
– possibilité de parallélisation

Spring Batch est donc un framework complet, robuste et stable.

Malgré sa complexité, il peut s’avérer interessant dans le cadre de batchs devant traiter de gros volumes de données.

0 responses on "A la découverte de Spring Batch"

Leave a Message

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

© WayofCode. All rights reserved.

Setup Menus in Admin Panel