Spring-Batch : par quel bout le prendre ?

SpringSpring-Batch répond à un besoin récurrent : la gestion des programmes batchs écrits en Java. 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. Personnellement, j’ai passé quelques heures pour faire fonctionner mon premier batch. Les exemples fournis fonctionnent rapidement, et illustrent très bien les possibilités qu’offre Spring-Batch. Mais, comme ces possibilités sont très riches, les exemples sont nombreux et (nécessairement) complexes. On lit la documentation, on regarde les multiples exemples en détail, et au moment d’implémenter notre premier batch et de plonger pleinement dans le cœur du sujet, on se pose la question « Mais par quel bout je commence ? ».

Donc, pour permettre aux gens qui, comme moi, aiment bien créer leur « hello-world » afin de bien comprendre ce qu’ils utilisent, voici un exemple minimal d’un projet Spring-Batch.

Pourquoi Spring-Batch ?

Avant de vous expliquer comment commencer avec Spring-Batch, je vous propose un petit rappel :

Spring-Batch a pour vocation de fournir un framework robuste permettant de développer plus facilement des programmes batch en Java. En effet, jusqu’à très récemment, il n’existait aucune solution open source sur laquelle s’appuyer quand on voulait faire un tel programme. Il fallait donc utiliser diverses API pour réaliser les fonctionnalités de base de son batch – par ex. lire / écrire dans des bases de données, manipuler des fichiers texte etc. Typiquement, on commençait par écrire un bon vieux programme Java avec sa méthode « public static void main(String args){} »… Ce qui d’ailleurs peut être une bonne solution pour des besoins simples et ponctuels.

Alors, quand passer à Spring-Batch ? Par exemple, dès que l’on rencontre l’une des problématiques suivantes :

  • 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.

A noter également que l’architecture d’un batch réalisé avec Spring-batch permet de le rendre facilement testable par un harnais de tests unitaires automatisés. Il est notamment relativement simple, grâce à Spring, de remplacer certaines classes par des bouchons, ou encore de travailler sur une base de données en mémoire HSQLDB au lieu de la « vraie » base.

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…

Initialisation du projet

Dépendances Maven

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>

La classe métier Person

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;
  }
}

Implémentation du batch

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

L’ItemReader

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 {
  }
}

L’ItemWriter

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

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

Lancement du batch

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 :

  • Classe principale :
org.springframework.batch.core.launch.support.CommandLineJobRunner
  • Arguments :
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"

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. 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

29 commentaires sur “Spring-Batch : par quel bout le prendre ?”

  • Excellent article ! Merci pour ce 'prémachage' qui donne envie de pousser le bouchon plus loin. Encore un produit structurant pour le développement et dans le domaine des batchs c'est ce qu'il manquait un peu. On trouve des sortes de mini frameworks 'maison' un peu partout... et la maintenabilité des applications s'en ressent. Par contre le changement de politique chez Spring concernant les licences peut en rebuter certains...

  • Spring Batch est effectivement une très bonne initiative. Par contre je reste dubitatif sur sa mise en oeuvre par les prods de nos cheres grandes entreprises. Le framework est assez lourd et necessite des bases de données et un queueur pour fonctionner sur une veritable prod avec tous les services (reprise, ..). N'est ce pas trop ?

    Oh la la, Julien Dubois va encore devenir tout rouge en lisant ton post. Il n'y a pas de changement de licence mais un changement sur la politique de support. Pour revenir sur ce sujet je trouve personnellement cela plutot normal et sain.
    En fait c'est ainsi que fonctionne tout éditeur afin de faire évoluer ses clients vers les dernières versions de ses produits. Il fait payer et diminue le support sur les anciennes versions. Ce qui choque les gens c'est l'application de ces regles sur un produit en open-source. Ce qu'il faut voir c'est que sur un produit OSS les équipes ne s'engagent pas sur le support (elles le font mais il n'y a pas de contrat) et très souvent maintiennent peu de branches. SpringSource en s'engageant sur le support de SpringFramework doit mettre des règles sinon leur charge de support deviendra tout simplement ingérable.

  • Merci Arnaud :-)
    Spring Batch est en effet sous licence Apache, comme tous les projets 'Spring-quelquechose'.

  • Au fait, je viens de parler de votre article sur notre blog :
    www.springsource.com/fr/b...

    N'hésitez pas à me contacter la prochaine fois que vous faites un article sur nos technos, surtout qu'on se croise réguliérement (la semaine dernière chez Zenika, et cette semaine c'est moi qui passe chez Octo!)

  • Merci Julien.
    Le monde est effectivement petit,et Spring omni-présent ;-)
    A bientot.

  • Ce qui est séduisant pour certaines productions c'est l'analogie complète qu'il y a entre le framework Spring batch et les batchs MainFrame.
    Dans le cas d'une migration on parle déja la même langue (Job, Step, etc.) ce qui est déja énorme.
    Ce qui va être plus compliqué à gèrer c'est le changement de responsabilité: aujourd'hui les Job (et les priorités, parallélisme, les attentes, etc.) sont directement géré par l'OS et sont donc facile à administrer. Avec SpringBatch il va falloir que les administrateurs mettent un pied dans le serveur d'application...

  • en effet kerny, cette analogie est due au fait que Spring a été développé en appliquant le Domain Driven Design; et ce language c'est equivalent a l'ubiquitous language du domaine des batchs.
    voir : java-chimaera.blogspot.co...

  • Merci pour cet article très didactique !

  • Excellent article, merci. C'est une très bonnes mise en bouche de spring bach.
  • Excellent article,c'est super bien fait bravooo et merci.Cependant j'ai une question est ce possible d'integrer spring batch dans une application web c'est à dire au lieu d'avoir une classe main on aura plutot une partie présentation avec un bouton qui lance le batch(pour ne pas avoir l'affichage en mode console ou arrière plan).Si oui comment faire?
  • Trés bon tutorial pour une entrée en la matière. Je pense que les personnes ayant déjà un petit bagage sur la configuration de Spring gagnerons en compréhension. Je recommande également la section Getting Started du site SpringSource : http://static.springsource.org/spring-batch/getting-started.html
  • Il me semble , qu'on réinvente la roue , Les notions de JOBS:STEPS et autres reprises sont connus depuis 50 ans , Ce sont les logiciels dits "ordonanceurs" , type "controlm" , et consorts qui gèrent les ressources , reprises , et statistiques. Springbatch n est pas un outil de production , En prod , les processus sont bien séparés ; un ordonanceur , est reponsable de toute les batch, enchainement , dépendances , ressources , conditions ...). Les composants n'ont pas à connaître leur mode de scheduling , il sont appelés ou pas , s'ils sont appelés , il font leur JOB ( commit ou rollback ). beaucoup de logiciels ( oracle ... ) offrent la notion de scheduling , alors que cette notion se situe en amont des processus , résultat on ne les utilise pas en PROD ( il ne faut qu'un seul chef , sinon c'est le bordel ) Je ne veux pas faire le vieux scnock , mais j'ai tellement vu de batch java pitoyablement lents et innefficaces , que je suis un peu las de ces théories fumeuses , ou on mélange tout. Ne jetons pas SQL à la poubelle avec une telle légereté. je n'ai rien contre le java , mais pour l'amour du ciel , acceptez de coller une instruction SQL dans votre code , même si c'est un classe de dégénéré , elle vous fera gagner beaucoup de temps , et d'énergie. enfin bref , je me trompe peut-être. comment springbatch fait il pour ordonnacer du java/.net/c/fortran/shell/etsc.... ? c'est pas son JOB
  • Bonjour,
    Les notions de JOBS:STEPS et autres reprises sont connus depuis 50 ans
    En effet, ces termes sont repris volontairement dans Spring Batch.
    Les composants n’ont pas à connaître leur mode de scheduling , il sont appelés ou pas , s’ils sont appelés , il font leur JOB ( commit ou rollback ).
    En fait, c'est le cas des composants écrits avec Spring-batch : ils sont indépendants de la façon dont ils sont appelés, et ils peuvent être appelés par un scheduler comme par ex. $U ou Quartz. Spring Batch n'est pas un scheduler mais juste un framework de développement de batchs.
    Ne jetons pas SQL à la poubelle avec une telle légereté.
    Il ne s'agit pas de jeter SQL :-) Spring batch contient ce qu'il faut pour des batchs en SQL, mais il offre aussi un cadre plus générique pour faire intervenir autre chose dans un batch. Un exemple de batch : lecture de tables en SQL, calculs complexes implémentés en Java sur les données obtenues, et envoi des résultats sur un système via MQ.
  • Que veux-tu faire intervenir dans un batch d'autre de ce qu"il a à faire ? Ne tombe-t-on pas dans le concept pur ? un batch est simple ( un moulin a café ) , il prend un paquet de données et met éventuellement à jour quelque chose. J'ai cherché ce que c'était MQ , ça parle de résilience , suis-je bête ou est-ce que je ne comprends pas ? Quand à la généricité , le reuse and so , les applis d'aujourd'hui ont une durée de vie très limitée , et par là donc , complètement non réutilisable (hormis les webservices) . Nous sommes dans la civilisation du jetable , malheureusement En tout cas , continuons cette discussion , car je te trouve sympathique , j'ai certainement tort , mais je voudrais comprendre pourquoi? ( enfin je ne suis pas vraiment sur d'être dans l'erreur , car par exemple ,le nouveau concept de virtualisation des machines , n'est qu'une resucée des VM de 1975 ). Il y a des interfaces graphiques OK , mais c'est la même chose. Enfin courage , je suis sur que tu fais du bon travail. A+
  • j'ai un peu relu ton post , je le trouve très bien exposé. tu te trouves dans la même position que tous les développeurs objet/mapping SQL , à savoir le bouchonnage , décidément , si springbatch peut apporter qq chose , c'est certainement sur ce genre de sujet , aussi sur les tests de non régression, cet outil est certainement un très bon outil pour le service étude , mais pour la prod , je ne suis pas convaicu. A+
  • Attention : Spring Batch n'est pas un ordonnanceur ! Il s'agit d'un framework de conception de traitements massifs. L'ordonnancement doit toujours se faire via Quartz ou d'autres solutions. De plus, Spring Batch est orienté sur une certaine philosophie qui ne convient pas à tous les traitements. http://blog.infin-it.fr/2010/02/03/spring-batch-les-pieges-a-eviter/
  • bonjour je veux lancer mon batch sur eclipse pour tester mais ça marche il m'affiche un message d'erreur "java.lang.NoClassDefFoundError: org/springframework/batch/core/launch/support/CommandLineJobRunner" peux tu m'aider stp merci
  • Bonjour, tu auras plus de chances sur le forum spring-batch: http://forum.springsource.org/forumdisplay.php?f=41 Ton problème y est déjà résolu ! Et sinon pense à fournir la stacktrace.
  • Cool ce billet, j'ai une mission pr cette fin d'année qui correspond exactement à ce que disait Julien : "Un exemple de batch : lecture de tables en SQL, calculs complexes implémentés en Java sur les données obtenues, et envoi des résultats sur un système via MQ" ou j'aurai : lecture de tables en SQL : liste de plus de 3M de documents (+ métadonnées) compressés en RLE dans un système d'archivage. calculs complexes implémentés en Java sur les données obtenues : décompression des docs, audit du résultat envoi des résultats sur un système via MQ : envoi dans ActiveMQ pr indexage Alfresco. Ajouté à ça un monitoring des traitement dans un appli web. Tout un programme, par contre je ne sais pas trop quelles classes je mettrai en oeuvre (Item-oriented ou tasklet ? différences ?), gestion du parralèlisme de masse ?, etc... Si vs avez des infos je suis preneur.
  • Merci pour ce billet qui permet d'avoir une bonne visibilité de façon simplifiée sur le fonctionnement de Spring-batch. Dès que j'ai un moment, je ferais un lien dessus sur mon propre blog ;)
  • Il manque la dépendance Job JobLauncher, non ?
  • j'essaye de faire fonctionner l'exemple mais je bute sur un pb avec les dépendances maven. par ex, la compilation ne reconnait pas les classes ItemReader et ItemWriter bien qu'elle se trouve sur le jar (package org.springframework.batch.item). quand ca comme dépendance ca donne une erreur (introuvable). l'exemple fonctionne uniquement si on spécifie une à une les classe : en ajoutant org.springframework.batch.item.ItemReader et la meme chose pour les autres classes. une idée ?
  • petite précision : mettre le fichier "batch-sample-context.xml" non pas dans le repertoire "config" de maven mais bien dans "resources".
  • Bonjour, j'ai lu votre article et je l'ai trouvé intéressant merci à vous, cependant j'ai une question vu que je travail actuellement avec spring batch combiné à l'ordonnanceur quartz et ma question est la suivante: Comment relancer un job quand il échoue ou bien qu'il soit suspendu et qu'il reprenne là ou il c'est arrêté. Merci à vous
  • Bonjour, Je ne peux pas acceder à l'exemple car il faut s'authentifier. Comment nous nous identifions?
  • Je dois reprendre du code Spring Batch non terminé et laissé en l’état par un collaborateur parti en cours de projet dans ma boîte et je dois dire que je ne suis pas trop enthousiasmé par ce que je vois (cela me parait largement surdimensionné pour nos besoins). Je reste également assez dubitatif devant vos exemples avec 24 lignes de codes pour parcourir un tableau et 18 lignes de code pour accumuler des chaines dans un StringBuilder … Mais pour parfaire ma connaissance de l’état de l’art Spring Batch j’ai une petite question : entre votre ItemReader et votre ItemWriter, ne devrait-il pas y avoir un ItemProcessor ? Plus précisément, ce que vous faites ici dans un ItemWriter ne devrait-il pas plutôt être fait dans un ItemProcessor (et vous auriez alors un ItemWriter vide dans la mesure où vous ne persistez pas les résultats de votre traitement dans cet exemple simplifié) ?
  • Bonjour, Je n'arrive pas à recupere le code source sur spring batch:le systeme demande une authentification. Merci. Amd
  • Bonjour, Le fichier batch-sample-context.xml (http://forge.octo.com/svn/java/trunk/octo-samples/springbatch-minimal-sample/src/main/resources/batch-sample-context.xml) n'est plus accessible.
  • Bonjour, Je vous remercie pour votre article. Ma question porte sur une bonne pratique de l'utilisation de SpringBatch. Je souhaite effectuer une migration de données entre une base B1 et B2 à l'aide de batchs SpringBatch, et je pense à deux solutions : - Un seul batch avec un ItemReader qui pointe sur ma base B1 et un itemWriter qui permet d'écrire dans la base B2. - Deux batchs : un premier batch d'extraction des données de B1 dans un fichier plat et un second batch qui effectue l'intégration des données dans la base B2 à partir des données contenues dans le fichier plat. Parmi les deux solutions y a t-il une meilleures solution et l'utilisation d'un seul batch pointant dans 2 base de données est-il déconseillé ? si oui pourquoi ? Merci par avance pour vos retours d'expériences.
    1. Laisser un commentaire

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


      Ce formulaire est protégé par Google Recaptcha