Exemple de lot Spring

Bienvenue dans l’exemple de Spring Batch. Spring Batch est un module du framework Spring destiné à l’exécution de tâches en lot. Nous pouvons utiliser Spring Batch pour traiter une série de tâches.

Exemple de Spring Batch

Avant de passer à l’exemple de programme Spring Batch, prenons quelques notions sur les terminologies de Spring Batch.

  • A job can consist of ‘n’ number of steps. Each step contains Read-Process-Write task or it can have single operation, which is called tasklet.
  • La lecture-traitement-écriture consiste essentiellement à lire à partir d’une source telle qu’une base de données, un fichier CSV, etc., puis à traiter les données et à les écrire dans une autre source, telle qu’une base de données, un fichier CSV, un fichier XML, etc.
  • Une tâchelette signifie effectuer une seule tâche ou opération, comme nettoyer les connexions, libérer les ressources une fois le traitement terminé.
  • La lecture-traitement-écriture et les tâchelettes peuvent être enchaînées pour exécuter une tâche.

Exemple de Spring Batch

Prenons un exemple concret d’implémentation de Spring Batch. Nous allons considérer le scénario suivant à des fins d’implémentation. Un fichier CSV contenant des données doit être converti en XML, les données et les balises porteront le nom des colonnes. Ci-dessous, les outils et bibliothèques importants utilisés pour l’exemple de Spring Batch.

  1. Apache Maven 3.5.0 – pour la construction de projet et la gestion des dépendances.
  2. Eclipse Oxygen Release 4.7.0 – IDE pour la création d’une application spring batch maven.
  3. Java 1.8
  4. Spring Core 4.3.12.RELEASE
  5. Spring OXM 4.3.12.RELEASE
  6. Spring JDBC 4.3.12.RELEASE
  7. Spring Batch 3.0.8.RELEASE
  8. MySQL Java Driver 5.1.25 – utilisez en fonction de votre installation MySQL. Ceci est requis pour les tables de métadonnées de Spring Batch.

Structure de répertoire d’exemple Spring Batch

L’image ci-dessous illustre tous les composants de notre projet exemple Spring Batch.

Dépendances de Maven Spring Batch

Voici le contenu du fichier pom.xml avec toutes les dépendances requises pour notre projet exemple spring batch.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.journaldev.spring</groupId>
	<artifactId>SpringBatchExample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>SpringBatchDemo</name>
	<url>https://maven.apache.org</url>

	<properties>
		<jdk.version>1.8</jdk.version>
		<spring.version>4.3.12.RELEASE</spring.version>
		<spring.batch.version>3.0.8.RELEASE</spring.batch.version>
		<mysql.driver.version>5.1.25</mysql.driver.version>
		<junit.version>4.11</junit.version>
	</properties>

	<dependencies>

		<!-- Spring Core -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring jdbc, for database -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring XML to/back object -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-oxm</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- MySQL database driver -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.driver.version}</version>
		</dependency>

		<!-- Spring Batch dependencies -->
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-core</artifactId>
			<version>${spring.batch.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-infrastructure</artifactId>
			<version>${spring.batch.version}</version>
		</dependency>

		<!-- Spring Batch unit test -->
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-test</artifactId>
			<version>${spring.batch.version}</version>
		</dependency>

		<!-- Junit -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>com.thoughtworks.xstream</groupId>
			<artifactId>xstream</artifactId>
			<version>1.4.10</version>
		</dependency>

	</dependencies>
	<build>
		<finalName>spring-batch</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-eclipse-plugin</artifactId>
				<version>2.9</version>
				<configuration>
					<downloadSources>true</downloadSources>
					<downloadJavadocs>false</downloadJavadocs>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>${jdk.version}</source>
					<target>${jdk.version}</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Traitement de fichier d’entrée CSV avec Spring Batch

Voici le contenu de notre fichier CSV d’exemple pour le traitement de lot avec Spring Batch.

1001,Tom,Moody, 29/7/2013
1002,John,Parker, 30/7/2013
1003,Henry,Williams, 31/7/2013

Configuration de tâche Spring Batch

Nous devons définir un bean Spring et une tâche Spring Batch dans un fichier de configuration. Voici le contenu du fichier job-batch-demo.xml, qui est la partie la plus importante du projet Spring Batch.

<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:batch="https://www.springframework.org/schema/batch" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://www.springframework.org/schema/batch
		https://www.springframework.org/schema/batch/spring-batch-3.0.xsd
		https://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans-4.3.xsd
	">

	<import resource="../config/context.xml" />
	<import resource="../config/database.xml" />

	<bean id="report" class="com.journaldev.spring.model.Report"
		scope="prototype" />
	<bean id="itemProcessor" class="com.journaldev.spring.CustomItemProcessor" />

	<batch:job id="DemoJobXMLWriter">
		<batch:step id="step1">
			<batch:tasklet>
				<batch:chunk reader="csvFileItemReader" writer="xmlItemWriter"
					processor="itemProcessor" commit-interval="10">
				</batch:chunk>
			</batch:tasklet>
		</batch:step>
	</batch:job>

	<bean id="csvFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader">

		<property name="resource" value="classpath:csv/input/report.csv" />

		<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="names" value="id,firstname,lastname,dob" />
					</bean>
				</property>
				<property name="fieldSetMapper">
					<bean class="com.journaldev.spring.ReportFieldSetMapper" />

					<!-- if no data type conversion, use BeanWrapperFieldSetMapper to map 
						by name <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"> 
						<property name="prototypeBeanName" value="report" /> </bean> -->
				</property>
			</bean>
		</property>

	</bean>

	<bean id="xmlItemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter">
		<property name="resource" value="file:xml/outputs/report.xml" />
		<property name="marshaller" ref="reportMarshaller" />
		<property name="rootTagName" value="report" />
	</bean>

	<bean id="reportMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
		<property name="classesToBeBound">
			<list>
				<value>com.journaldev.spring.model.Report</value>
			</list>
		</property>
	</bean>

</beans>
  1. Nous utilisons FlatFileItemReader pour lire le fichier CSV, CustomItemProcessor pour traiter les données et écrire dans un fichier XML à l’aide de StaxEventItemWriter.
  2. batch:job – Cette balise définit la tâche que nous voulons créer. La propriété Id spécifie l’ID de la tâche. Nous pouvons définir plusieurs tâches dans un seul fichier XML.
  3. batch:step – Cette balise est utilisée pour définir les différentes étapes d’une tâche Spring Batch.
  4. Deux types différents de styles de traitement sont proposés par le framework Spring Batch, qui sont « TaskletStep Oriented » et « Chunk Oriented ». Le style Chunk Oriented utilisé dans cet exemple consiste à lire les données une par une et à créer des « chunks » qui seront écrits, dans une transaction délimitée.
  5. reader: le bean Spring utilisé pour lire les données. Nous avons utilisé le bean csvFileItemReader dans cet exemple, qui est une instance de FlatFileItemReader.
  6. processor: c’est la classe utilisée pour traiter les données. Nous avons utilisé CustomItemProcessor dans cet exemple.
  7. writer: le bean utilisé pour écrire les données dans un fichier XML.
  8. commit-interval: cette propriété définit la taille du chunk qui sera validé une fois le traitement terminé. Fondamentalement, cela signifie que l’ItemReader lira les données une par une et l’ItemProcessor les traitera de la même manière, mais l’ItemWriter écrira les données uniquement lorsqu’elles atteindront la taille de commit-interval.
  9. Trois interfaces importantes utilisées dans le cadre de ce projet sont ItemReader, ItemProcessor et ItemWriter du package org.springframework.batch.item.

Classe modèle Spring Batch

Tout d’abord, nous lisons le fichier CSV dans un objet Java, puis utilisons JAXB pour l’écrire dans un fichier XML. Voici notre classe modèle avec les annotations JAXB nécessaires.

package com.journaldev.spring.model;

import java.util.Date;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "record")
public class Report {

	private int id;
	private String firstName;
	private String lastName;
	private Date dob;

	@XmlAttribute(name = "id")
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	@XmlElement(name = "firstname")
	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	@XmlElement(name = "lastname")
	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@XmlElement(name = "dob")
	public Date getDob() {
		return dob;
	}

	public void setDob(Date dob) {
		this.dob = dob;
	}

	@Override
	public String toString() {
		return "Report [id=" + id + ", firstname=" + firstName + ", lastName=" + lastName + ", DateOfBirth=" + dob
				+ "]";
	}

}

Notez que les champs de la classe modèle doivent être les mêmes que ceux définis dans la configuration du mapper Spring Batch, c’est-à-dire property name="names" value="id,firstname,lastname,dob" dans notre cas.

Spring Batch FieldSetMapper

A custom FieldSetMapper is needed to convert a Date. If no data type conversion is required, then only BeanWrapperFieldSetMapper should be used to map the values by name automatically. The java class which extends FieldSetMapper is ReportFieldSetMapper.

package com.journaldev.spring;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;

import com.journaldev.spring.model.Report;

public class ReportFieldSetMapper implements FieldSetMapper {

	private SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

	public Report mapFieldSet(FieldSet fieldSet) throws BindException {

		Report report = new Report();
		report.setId(fieldSet.readInt(0));
		report.setFirstName(fieldSet.readString(1));
		report.setLastName(fieldSet.readString(2));

		// Format par défaut yyyy-MM-dd
		// fieldSet.readDate(4);
		String date = fieldSet.readString(3);
		try {
			report.setDob(dateFormat.parse(date));
		} catch (ParseException e) {
			e.printStackTrace();
		}

		return report;

	}

}

Spring Batch Item Processor

Maintenant, comme défini dans la configuration du job, un itemProcessor sera déclenché avant l’itemWriter. Nous avons créé une classe CustomItemProcessor.java à cet effet.

package com.journaldev.spring;

import org.springframework.batch.item.ItemProcessor;

import com.journaldev.spring.model.Report;

public class CustomItemProcessor implements ItemProcessor<Report, Report> {

	public Report process(Report item) throws Exception {
		
		System.out.println("Processing..." + item);
		String fname = item.getFirstName();
		String lname = item.getLastName();
		
		item.setFirstName(fname.toUpperCase());
		item.setLastName(lname.toUpperCase());
		return item;
	}

}

Nous pouvons manipuler les données dans l’implémentation de l’ItemProcessor, comme vous pouvez le voir, je convertis les valeurs du prénom et du nom en majuscules.

Fichiers de configuration Spring

Dans notre fichier de configuration de lot de printemps, nous avons importé deux fichiers de configuration supplémentaires – context.xml et database.xml.

<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		https://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

	<!-- stored job-meta in memory -->
	<!--  
	<bean id="jobRepository"
		class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
		<property name="transactionManager" ref="transactionManager" />
	</bean>
 	 -->
 	 
 	 <!-- stored job-meta in database -->
	<bean id="jobRepository"
		class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="transactionManager" ref="transactionManager" />
		<property name="databaseType" value="mysql" />
	</bean>
	
	<bean id="transactionManager"
		class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
	 
	<bean id="jobLauncher"
		class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
		<property name="jobRepository" ref="jobRepository" />
	</bean>

</beans>
  • jobRepository – Le JobRepository est responsable de stocker chaque objet Java dans sa table de métadonnées correcte pour le lot de printemps.
  • transactionManager – cela est responsable de valider la transaction une fois que la taille de l’intervalle de validation et les données traitées sont égales.
  • jobLauncher – C’est le cœur du lot de printemps. Cette interface contient la méthode run qui est utilisée pour déclencher le travail.
<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:jdbc="https://www.springframework.org/schema/jdbc" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans-4.3.xsd
		https://www.springframework.org/schema/jdbc 
		https://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd">

	<!-- connect to database -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/Test" />
		<property name="username" value="test" />
		<property name="password" value="test123" />
	</bean>

	<bean id="transactionManager"
		class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />

	<!-- create job-meta tables automatically -->
	<!-- <jdbc:initialize-database data-source="dataSource"> <jdbc:script location="org/springframework/batch/core/schema-drop-mysql.sql" 
		/> <jdbc:script location="org/springframework/batch/core/schema-mysql.sql" 
		/> </jdbc:initialize-database> -->
</beans>

Spring Batch utilise certaines tables de métadonnées pour stocker des informations sur les travaux en lot. Nous pouvons les faire créer à partir des configurations de lot de printemps, mais il est conseillé de le faire manuellement en exécutant les fichiers SQL, comme vous pouvez le voir dans le code commenté ci-dessus. Du point de vue de la sécurité, il est préférable de ne pas donner d’accès à l’exécution de DDL à l’utilisateur de la base de données du lot de printemps.

Tables de lot de printemps

Les tables Spring Batch correspondent très étroitement aux objets de domaine qui les représentent en Java. Par exemple – JobInstance, JobExecution, JobParameters et StepExecution sont associés respectivement à BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_JOB_EXECUTION_PARAMS et BATCH_STEP_EXECUTION. ExecutionContext est associé à la fois à BATCH_JOB_EXECUTION_CONTEXT et à BATCH_STEP_EXECUTION_CONTEXT. Le JobRepository est responsable de sauvegarder et stocker chaque objet Java dans sa table correcte. Voici les détails de chaque table de métadonnées.

  1. Batch_job_instance: La table BATCH_JOB_INSTANCE contient toutes les informations pertinentes à une JobInstance.
  2. Batch_job_execution_params: La table BATCH_JOB_EXECUTION_PARAMS contient toutes les informations pertinentes à l’objet JobParameters.
  3. Batch_job_execution: La table BATCH_JOB_EXECUTION contient des données pertinentes à l’objet JobExecution. Une nouvelle ligne est ajoutée à chaque fois qu’un Job est exécuté.
  4. Batch_step_execution: La table BATCH_STEP_EXECUTION contient toutes les informations pertinentes à l’objet StepExecution.
  5. Batch_job_execution_context: La table BATCH_JOB_EXECUTION_CONTEXT contient des données pertinentes à ExecutionContext d’un travail. Il existe exactement un ExecutionContext de travail pour chaque JobExecution, et il contient toutes les données de niveau de travail nécessaires pour cette exécution de travail particulière. Ces données représentent généralement l’état qui doit être récupéré après une défaillance afin qu’une JobInstance puisse redémarrer à partir de l’endroit où elle avait échoué.
  6. Contexte_d’exécution_de_l’étape_par_lot: La table BATCH_STEP_EXECUTION_CONTEXT contient des données pertinentes pour le Contexte d’Exécution d’une étape. Il y a exactement un ExecutionContext pour chaque StepExecution, et il contient toutes les données qui doivent être persistées pour une exécution d’étape particulière. Ces données représentent généralement l’état qui doit être récupéré après une défaillance afin qu’une JobInstance puisse redémarrer là où elle a échoué.
  7. Séquence_d’exécution_de_travail_par_lot: Cette table contient la séquence d’exécution des tâches.
  8. Séquence_d’exécution_de_l’étape_par_lot: Cette table contient les données de séquence pour l’exécution des étapes.
  9. Séquence_de_travail_par_lot: Cette table contient les données de séquence du travail au cas où nous aurions plusieurs travaux, nous obtiendrons plusieurs lignes.

Programme de Test Spring Batch

Notre projet d’exemple Spring Batch est prêt, la dernière étape consiste à écrire une classe de test pour l’exécuter en tant que programme Java.

package com.journaldev.spring;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
	public static void main(String[] args) {

		String[] springConfig = { "spring/batch/jobs/job-batch-demo.xml" };

		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(springConfig);

		JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
		Job job = (Job) context.getBean("DemoJobXMLWriter");

		JobParameters jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis())
				.toJobParameters();

		try {

			JobExecution execution = jobLauncher.run(job, jobParameters);
			System.out.println("Exit Status : " + execution.getStatus());

		} catch (Exception e) {
			e.printStackTrace();
		}

		System.out.println("Done");
		context.close();
	}
}

Il suffit d’exécuter le programme ci-dessus et vous obtiendrez une sortie XML comme ci-dessous.

<?xml version="1.0" encoding="UTF-8"?><report><record id="1001"><dob>2013-07-29T00:00:00+05:30</dob><firstname>TOM</firstname><lastname>MOODY</lastname></record><record id="1002"><dob>2013-07-30T00:00:00+05:30</dob><firstname>JOHN</firstname><lastname>PARKER</lastname></record><record id="1003"><dob>2013-07-31T00:00:00+05:30</dob><firstname>HENRY</firstname><lastname>WILLIAMS</lastname></record></report>

C’est tout pour l’exemple de Spring Batch, vous pouvez télécharger le projet final depuis le lien ci-dessous.

Télécharger le Projet Exemple Spring Batch

Référence: Guide Officiel

Source:
https://www.digitalocean.com/community/tutorials/spring-batch-example