Benvenuti all’esempio di Spring Batch. Spring Batch è un modulo del framework spring per l’esecuzione di lavori batch. Possiamo utilizzare spring batch per processare una serie di lavori.
Esempio di Spring Batch
Prima di passare al programma di esempio di spring batch, otteniamo qualche idea sui termini di 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.
- Read-Process-Write consiste fondamentalmente nel leggere da una fonte come Database, CSV, ecc., quindi processare i dati e scriverli su una fonte come Database, CSV, XML, ecc.
- Tasklet significa svolgere un singolo compito o operazione come la pulizia delle connessioni, la liberazione delle risorse dopo che il processo è terminato.
- Read-Process-Write e i tasklet possono essere concatenati insieme per eseguire un lavoro.
Esempio di Spring Batch
Consideriamo un esempio pratico per l’implementazione di spring batch. Considereremo il seguente scenario per scopi di implementazione. Un file CSV contenente dati deve essere convertito in XML insieme ai dati e i tag saranno nominati dopo il nome della colonna. Di seguito sono riportati gli strumenti e le librerie importanti utilizzate per l’esempio di spring batch.
- Apache Maven 3.5.0 – per la gestione del progetto e delle dipendenze.
- Eclipse Oxygen Release 4.7.0 – IDE per la creazione di applicazioni spring batch maven.
- Java 1.8
- Spring Core 4.3.12.RELEASE
- Spring OXM 4.3.12.RELEASE
- Spring JDBC 4.3.12.RELEASE
- Spring Batch 3.0.8.RELEASE
- MySQL Java Driver 5.1.25 – utilizzato in base alla tua installazione di MySQL. Questo è necessario per le tabelle dei metadati di Spring Batch.
Struttura della directory degli esempi di Spring Batch
L’immagine seguente illustra tutti i componenti del nostro progetto di esempio di Spring Batch.
Dipendenze Maven di Spring Batch
Di seguito è riportato il contenuto del file pom.xml con tutte le dipendenze richieste per il nostro progetto di esempio di 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>
Elaborazione Batch di File CSV in Spring
Ecco il contenuto del nostro file CSV di esempio per l’elaborazione batch di Spring.
1001,Tom,Moody, 29/7/2013
1002,John,Parker, 30/7/2013
1003,Henry,Williams, 31/7/2013
Configurazione del Lavoro Batch di Spring
Dobbiamo definire un bean di Spring e il lavoro batch di Spring in un file di configurazione. Di seguito è riportato il contenuto del file job-batch-demo.xml
, che è la parte più importante del progetto 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>
- Stiamo utilizzando
FlatFileItemReader
per leggere il file CSV,CustomItemProcessor
per elaborare i dati e scrivere su un file XML utilizzandoStaxEventItemWriter
. batch:job
– Questo tag definisce il lavoro che vogliamo creare. La proprietà Id specifica l’ID del lavoro. Possiamo definire più lavori in un singolo file XML.batch:step
– Questo tag viene utilizzato per definire i diversi passaggi di un lavoro batch di Spring.- Due diversi tipi di stile di elaborazione sono offerti dal framework Spring Batch, che sono “TaskletStep Oriented” e “Chunk Oriented”. Lo stile Chunk Oriented utilizzato in questo esempio si riferisce alla lettura dei dati uno per uno e alla creazione di ‘chunk’ che verranno scritti, all’interno di un limite di transazione.
- reader: bean Spring utilizzato per la lettura dei dati. Abbiamo utilizzato il bean
csvFileItemReader
in questo esempio, che è un’istanza diFlatFileItemReader
. - processor: questa è la classe utilizzata per elaborare i dati. Abbiamo utilizzato
CustomItemProcessor
in questo esempio. - writer: bean utilizzato per scrivere i dati in un file XML.
- commit-interval: questa proprietà definisce la dimensione del chunk che verrà confermato una volta completata l’elaborazione. Fondamentalmente significa che l’ItemReader leggerà i dati uno per uno e l’ItemProcessor li elaborerà nello stesso modo, ma l’ItemWriter scriverà i dati solo quando raggiunge la dimensione del commit-interval.
- Tre importanti interfacce utilizzate in questo progetto sono
ItemReader
,ItemProcessor
eItemWriter
del pacchettoorg.springframework.batch.item
.
Classe Modello Spring Batch
Prima di tutto stiamo leggendo il file CSV in un oggetto Java e poi utilizzando JAXB per scriverlo in un file XML. Di seguito è riportata la nostra classe modello con le annotazioni JAXB richieste.
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
+ "]";
}
}
Si noti che i campi della classe modello devono essere gli stessi definiti nella configurazione del mapper di spring batch, cioè property name="names" value="id,firstname,lastname,dob"
nel nostro caso.
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));
// formato predefinito 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
Ora, come definito nella configurazione del lavoro, un itemProcessor verrà eseguito prima di itemWriter. Abbiamo creato una classe CustomItemProcessor.java
per lo stesso scopo.
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;
}
}
Possiamo manipolare i dati nell’implementazione dell’ItemProcessor, come si può vedere sto convertendo i valori del nome e del cognome in maiuscolo.
File di configurazione Spring
Nel nostro file di configurazione per il batch di primavera, abbiamo importato altri due file di configurazione – context.xml
e 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 – Il JobRepository è responsabile di memorizzare ogni oggetto Java nella sua corretta tabella di metadati per il batch di primavera.
- transactionManager – Questo è responsabile di effettuare il commit della transazione una volta che la dimensione dell’intervallo di commit e i dati elaborati sono uguali.
- jobLauncher – Questa è la parte centrale del batch di primavera. Questa interfaccia contiene il metodo run che viene utilizzato per avviare il job.
<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 utilizza alcune tabelle di metadati per memorizzare le informazioni dei job di batch. Possiamo farle creare dalle configurazioni del batch di primavera, ma è consigliabile farlo manualmente eseguendo i file SQL, come si può vedere nel codice commentato sopra. Dal punto di vista della sicurezza, è meglio non concedere l’accesso all’esecuzione di DDL all’utente del database del batch di primavera.
Tabelle di Spring Batch
Le tabelle di Spring Batch corrispondono molto da vicino agli oggetti di dominio che li rappresentano in Java. Ad esempio – JobInstance, JobExecution, JobParameters e StepExecution vengono mappati rispettivamente su BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_JOB_EXECUTION_PARAMS e BATCH_STEP_EXECUTION. ExecutionContext viene mappato sia su BATCH_JOB_EXECUTION_CONTEXT che su BATCH_STEP_EXECUTION_CONTEXT. Il JobRepository è responsabile di salvare e memorizzare ogni oggetto Java nella sua tabella corretta. Di seguito sono riportati i dettagli di ciascuna tabella dei metadati.
- Batch_job_instance: La tabella BATCH_JOB_INSTANCE contiene tutte le informazioni rilevanti per un JobInstance.
- Batch_job_execution_params: La tabella BATCH_JOB_EXECUTION_PARAMS contiene tutte le informazioni rilevanti per l’oggetto JobParameters.
- Batch_job_execution: La tabella BATCH_JOB_EXECUTION contiene dati rilevanti per l’oggetto JobExecution. Viene aggiunta una nuova riga ogni volta che viene eseguito un Job.
- Batch_step_execution: La tabella BATCH_STEP_EXECUTION contiene tutte le informazioni rilevanti per l’oggetto StepExecution.
- Batch_job_execution_context: La tabella BATCH_JOB_EXECUTION_CONTEXT contiene dati rilevanti per il contesto di esecuzione di un Job. Esiste esattamente un contesto di esecuzione di lavoro per ogni JobExecution e contiene tutti i dati del livello di lavoro necessari per quella particolare esecuzione del lavoro. Questi dati rappresentano tipicamente lo stato che deve essere recuperato dopo un fallimento in modo che un JobInstance possa riavviarsi da dove aveva fallito.
- Contesto_di_esecuzione_passo_batch: La tabella BATCH_STEP_EXECUTION_CONTEXT contiene dati rilevanti per il Contesto di esecuzione di un passo. C’è esattamente un ExecutionContext per ogni StepExecution e contiene tutti i dati che devono essere persistiti per una particolare esecuzione del passo. Questi dati rappresentano tipicamente lo stato che deve essere recuperato dopo un fallimento in modo che un’istanza del lavoro possa riavviarsi da dove è fallita.
- Batch_job_execution_seq: Questa tabella contiene la sequenza di esecuzione dei dati del lavoro.
- Batch_step_execution_seq: Questa tabella contiene i dati per la sequenza di esecuzione del passo.
- Batch_job_seq: Questa tabella contiene i dati per la sequenza del lavoro nel caso in cui abbiamo più lavori otterremo più righe.
Programma di test di Spring Batch
Il nostro progetto di esempio Spring Batch è pronto, l’ultimo passo è scrivere una classe di test per eseguirlo come un programma 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();
}
}
Basta eseguire il programma sopra e otterrai un output xml come indicato di seguito.
<?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>
Questo è tutto per l’esempio di Spring Batch, puoi scaricare il progetto finale dal link sottostante.
Scarica il Progetto di Esempio di Spring Batch
Riferimento: Guida Ufficiale
Source:
https://www.digitalocean.com/community/tutorials/spring-batch-example