Introduction au framework de traitement par lots Spring Batch

Spring Batch est un framework de traitement par lots léger et complet, faisant partie de l'écosystème Spring. Il est apprécié pour sa flexibilité, sa facilité d'utilisation et sa robustesse en production. Il simplifie grandement la gestion de tâches telles que le traitement efficace de grands volumes de données ou l'exécution planifiée de traitements massifs. Son efficacité est décuplée lorsqu'il est combiné à un framework d'ordonnancement.

Architecture de Spring Batch

L'architecture de Spring Batch est divisée en trois couches principales :

  • Couche Application : Contient les jobs et le code personnalisé développé par l'utilisateur, définissant la logique métier et les flux de traitement spécifiques au projet.
  • Couche Cœur (Batch Core) : Fournit l'environnement d'exécution nécessaire au lancement et à la gestion des jobs, incluant des composants comme JobLauncher.
  • Couche Infrastructure (Batch Enfrastructure) : Sert de fondation aux deux autres couches. Elle comprend les composants essentiels tels que les lecteurs (Reader), les écrivains (Writer) et le framework de reprise sur erreur (Retry).

Concepts Clés

La compréhension des concepts suivants est essentielle pour le développement et le débogage avec Spring Batch :

JobRepository

Ce composant est responsable de l'interaction avec la base de données pour enregistrer l'état des exécutions de jobs (création, mise à jour, statut). Spring Batch nécessite donc une base de données pour gérer son cycle de vie.

JobLauncher

Le JobLauncher est l'interface permettant de démarrer l'exécution d'un Job.

Job

Un Job encapsule l'intégralité d'un processus de traitement par lots. Lancer un traitement, c'est lancer un Job.

  • Job : Définit la structure et la logique d'un traitement.
  • JobInstance : Une instance spécifique d'exécution d'un Job. Différentes instances d'un même Job peuvent être lancées avec des paramètres distincts.
  • JobParameters : Les paramètres associés à une JobInstance.
  • JobExecution : Représente une exécution concrète d'un Job, qui peut réussir ou échouer.

Le rôle du développeur est donc de définir ces Jobs.

Step

Un Step représente une unité de travail au sein d'un Job. Un Job peut être composé d'un ou plusieurs Steps exécutés séquentiellement selon une logique définie. La définition de Steps permet de construire des logiques métier complexes de manière modulaire.

Modèle Lecture-Traitement-Écriture (Item Reader, Item Processor, Item Writer)

La méthode la plus courante pour définir un Step repose sur le modèle : lire des données (ItemReader), les traiter (ItemProcessor), puis les écrire (ItemWriter). Par exemple, un ItemReader peut lire des données depuis un fichier, un ItemProcessor peut effectuer des transformations métier, et un ItemWriter peut persister les données dans une base de données. Spring Batch propose de nombreux readers et writers prêts à l'emploi.

Exemple de Code : Traitement de Fichiers CSV

Illustrons ces concepts avec un exemple concret : lire des données depuis plusieurs fichiers CSV, les traiter, puis les écrire dans un fichier CSV de sortie.

Configuration de Base

Ajoutez les dépendances nécessaires dans votre fichier pom.xml :


<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
 <groupId>com.h2database</groupId>
 <artifactId>h2</artifactId>
 <scope>runtime</scope>
</dependency>
 

Utiliser H2 comme base de données en mémoire est pratique pour le développement. Pour la production, une base de données externe comme Oracle ou PostgreSQL est recommandée.

La classe principale de l'application Spring Boot nécessite l'annotation @EnableBatchProcessing :


@SpringBootApplication
@EnableBatchProcessing
public class PkslowBatchJobMain {
   public static void main(String[] args) {
       SpringApplication.run(PkslowBatchJobMain.class, args);
   }
}
 

Définissons une classe d'entité simple pour représenter les données :


package com.pkslow.batch.entity;
public class Employee {
   private String id;
   private String firstName;
   private String lastName;

   // Getters and setters are assumed to be present
}
 

Voici un exemple de contenu pour un fichier CSV d'entrée :


id,firstName,lastName
1,Lokesh,Gupta
2,Amit,Mishra
3,Pankaj,Kumar
4,David,Miller
 

Implémentation du Modèle Lecture-Traitement-Écriture

ItemReader

Pour lire à partir de plusieurs fichiers CSV, nous utilisons MultiResourceItemReader :


@Value("input/inputData*.csv") // Charge les ressources correspondant au pattern
private Resource[] inputResources;

@Bean
public MultiResourceItemReader<Employee> multiResourceItemReader() {
   MultiResourceItemReader<Employee> resourceItemReader = new MultiResourceItemReader<Employee>();
   resourceItemReader.setResources(inputResources);
   resourceItemReader.setDelegate(flatFileItemReader()); // Définit le lecteur délégué pour chaque ressource
   return resourceItemReader;
}

@Bean
public FlatFileItemReader<Employee> flatFileItemReader() {
   FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();
   reader.setLinesToSkip(1); // Ignore la ligne d'en-tête

   // Mapper pour analyser chaque ligne CSV
   reader.setLineMapper(new DefaultLineMapper<Employee>() {
       {
           setLineTokenizer(new DelimitedLineTokenizer() {
               {
                   setNames(new String[] { "id", "firstName", "lastName" }); // Noms des colonnes
               }
           });
           setFieldSetMapper(new BeanWrapperFieldSetMapper<Employee>() {
               {
                   setTargetType(Employee.class); // Classe cible pour le mapping
               }
           });
       }
   });
   return reader;
}
 

ItemProcessor

Pour cet exemple simple, le processeur convertit la colonne lastName en majuscules :


@Bean
public ItemProcessor<Employee, Employee> employeeItemProcessor() {
   return employee -> {
       employee.setLastName(employee.getLastName().toUpperCase());
       return employee;
   };
}
 

ItemWriter

L'écrivain enregistre les données traitées dans un fichier CSV de sortie :


@Value("output/outputData.csv")
private Resource outputResource;

@Bean
public FlatFileItemWriter<Employee> csvItemWriter() {
   FlatFileItemWriter<Employee> writer = new FlatFileItemWriter<Employee>();
   writer.setResource(outputResource);
   writer.setAppendAllowed(true); // Permet d'ajouter au fichier existant

   // Agrégateur pour formater chaque ligne écrite
   writer.setLineAggregator(new DelimitedLineAggregator<Employee>() {
       {
           setDelimiter(","); // Séparateur de champs
           setFieldExtractor(new BeanWrapperFieldExtractor<Employee>() {
               {
                   setNames(new String[] { "id", "firstName", "lastName" }); // Champs à extraire
               }
           });
       }
   });
   return writer;
}
 

Définition du Step

Une fois le lecteur, le processeur et l'écrivain définis, on peut créer le Step :


@Autowired
private StepBuilderFactory stepBuilderFactory; // Injection du StepBuilderFactory

@Bean
public Step processCsvFileStep() {
   return stepBuilderFactory.get("processCsvFileStep")
           .<Employee, Employee>chunk(5) // Traite par lots de 5 enregistrements
           .reader(multiResourceItemReader())
           .processor(employeeItemProcessor())
           .writer(csvItemWriter())
           .build();
}
 

L'option chunk(5) indique que les opérations d'écriture seront effectuées après le traitement de 5 enregistrements.

Définition du Job

L'assemblage des Steps forme le Job :


@Autowired
private JobBuilderFactory jobBuilderFactory; // Injection du JobBuilderFactory

@Bean
public Job pkslowCsvProcessingJob() {
   return jobBuilderFactory.get("pkslowCsvProcessingJob")
           .incrementer(new RunIdIncrementer()) // Ajoute un incrément pour chaque exécution
           .start(processCsvFileStep())
           .build();
}
 

Exécution

Après avoir exécuté l'application, les données seront lues depuis les fichiers d'entrée, le lastName sera converti en majuscules, et le résultat sera écrit dans outputData.csv.

Listeners

Les Listeners permettent de réagir à des événements spécifiques du cycle de vie des opérations de lecture, traitement et écriture. Par exemple, on peut enregistrer les erreurs ou notifier d'autres systèmes après une écriture réussie.

Les interfaces correspondantes sont ItemReadListener, ItemProcessListener et ItemWriteListener. Voici un exemple d'implémentation pour ItemWriteListener :


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.ItemWriteListener;
import java.util.List;

public class PkslowWriteListener implements ItemWriteListener<Employee> {
   private static final Log logger = LogFactory.getLog(PkslowWriteListener.class);

   @Override
   public void beforeWrite(List<? extends Employee> list) {
       logger.info("Avant écriture : " + list);
   }

   @Override
   public void afterWrite(List<? extends Employee> list) {
       logger.info("Après écriture : " + list);
   }

   @Override
   public void onWriteError(Exception e, List<? extends Employee> list) {
       logger.error("Erreur lors de l'écriture : " + list, e);
   }
}
 

Pour intégrer un listener au Step :


@Bean
public Step processCsvFileStep() {
   return stepBuilderFactory.get("processCsvFileStep")
           .<Employee, Employee>chunk(5)
           .reader(multiResourceItemReader())
           .listener(new PkslowReadListener()) // Exemple d'ajout d'un listener de lecture
           .processor(employeeItemProcessor())
           .listener(new PkslowProcessListener()) // Exemple d'ajout d'un listener de traitement
           .writer(csvItemWriter())
           .listener(new PkslowWriteListener()) // Ajout du listener d'écriture
           .build();
}
 

L'exécution du job affichera les logs des listeners, démontrant le traitement par lots et l'efficacité du mécanisme de chunking pour réduire la pression sur les opérations d'I/O.

Étiquettes: spring batch Java batch processing itemreader itemprocessor

Publié le 10 juillet à 18h05