Bem-vindo ao Exemplo do Spring Batch. Spring Batch é um módulo do framework Spring para execução de trabalhos em lote. Podemos usar o Spring Batch para processar uma série de trabalhos.
Exemplo do Spring Batch
Antes de passarmos pelo programa de exemplo do Spring Batch, vamos entender algumas terminologias do 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.
- A leitura-processamento-escrita basicamente lê de uma fonte como banco de dados, CSV, etc., processa os dados e os escreve em uma fonte como banco de dados, CSV, XML, etc.
- Tasklet significa realizar uma única tarefa ou operação, como limpar conexões, liberar recursos após o processamento ser concluído.
- A leitura-processamento-escrita e tasklets podem ser encadeados para executar um trabalho.
Exemplo do Spring Batch
Vamos considerar um exemplo prático para a implementação do Spring Batch. Vamos considerar o seguinte cenário para fins de implementação. Um arquivo CSV contendo dados precisa ser convertido em XML juntamente com os dados e as tags serão nomeadas após o nome da coluna. Abaixo estão as ferramentas e bibliotecas importantes usadas para o exemplo do Spring Batch.
- Apache Maven 3.5.0 – para construção de projetos e gerenciamento de dependências.
- Eclipse Oxygen Release 4.7.0 – IDE para criar aplicação spring batch com 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 – use com base na instalação do MySQL. Isso é necessário para as tabelas de metadados do Spring Batch.
Estrutura de Diretórios de Exemplo do Spring Batch
A imagem abaixo ilustra todos os componentes em nosso projeto de exemplo Spring Batch.
Dependências do Maven do Spring Batch
Abaixo está o conteúdo do arquivo pom.xml com todas as dependências necessárias para nosso projeto de exemplo 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>
Processamento de Lotes Spring para Arquivo de Entrada CSV
Aqui está o conteúdo do nosso arquivo CSV de amostra para o processamento de lotes Spring.
1001,Tom,Moody, 29/7/2013
1002,John,Parker, 30/7/2013
1003,Henry,Williams, 31/7/2013
Configuração de Trabalho Spring Batch
Precisamos definir um bean do Spring e um trabalho de lotes Spring em um arquivo de configuração. Abaixo está o conteúdo do arquivo job-batch-demo.xml
, é a parte mais importante do projeto de lotes Spring.
<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>
- Estamos usando o
FlatFileItemReader
para ler o arquivo CSV,CustomItemProcessor
para processar os dados e escrever no arquivo XML usandoStaxEventItemWriter
. batch:job
– Esta tag define o trabalho que queremos criar. A propriedade Id especifica o ID do trabalho. Podemos definir vários trabalhos em um único arquivo XML.batch:step
– Esta tag é usada para definir diferentes etapas de um trabalho de lotes Spring.- Existem dois tipos diferentes de estilos de processamento oferecidos pelo Framework Spring Batch, que são “Orientado a Tarefas (Tasklet)” e “Orientado a Chunks”. O estilo Orientado a Chunks é usado neste exemplo para ler os dados um por um e criar ‘chunks’ que serão gravados dentro de um limite de transação.
- leitor: bean Spring usado para ler os dados. Nós usamos o bean
csvFileItemReader
neste exemplo, que é uma instância deFlatFileItemReader
. - processador: esta é a classe usada para processar os dados. Nós usamos
CustomItemProcessor
neste exemplo. - escritor: bean usado para escrever dados em um arquivo XML.
- commit-interval: Esta propriedade define o tamanho do chunk que será confirmado uma vez que o processamento esteja concluído. Basicamente, isso significa que o ItemReader lerá os dados um por um e o ItemProcessor também os processará da mesma forma, mas o ItemWriter só escreverá os dados quando o tamanho do commit-interval for atingido.
- Três interfaces importantes que são usadas neste projeto são
ItemReader
,ItemProcessor
eItemWriter
do pacoteorg.springframework.batch.item
.
Classe de Modelo do Spring Batch
Primeiro, estamos lendo um arquivo CSV em um objeto Java e, em seguida, usando JAXB para gravá-lo em um arquivo XML. Abaixo está nossa classe modelo com as anotações JAXB necessárias.
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
+ "]";
}
}
Observe que os campos da classe modelo devem ser os mesmos definidos na configuração de mapeamento do Spring Batch, ou seja, property name="names" value="id,firstname,lastname,dob"
no nosso 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 padrão 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
Agora, conforme definido na configuração do job, um itemProcessor será executado antes do itemWriter. Criamos uma classe CustomItemProcessor.java
para isso.
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;
}
}
Podemos manipular os dados na implementação do ItemProcessor, como você pode ver que estou convertendo os valores do nome e sobrenome para letras maiúsculas.
Arquivos de Configuração do Spring
No nosso ficheiro de configuração do lote de primavera, importámos dois ficheiros de configuração adicionais – 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 – O JobRepository é responsável por armazenar cada objeto Java na sua tabela de metadados correta para o lote de primavera.
- transactionManager – isto é responsável por confirmar a transação uma vez que o tamanho do intervalo de confirmação e os dados processados são iguais.
- jobLauncher – Este é o coração do lote de primavera. Esta interface contém o método run que é usado para desencadear o trabalho.
<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>
O Spring Batch usa algumas tabelas de metadados para armazenar informações sobre os trabalhos em lote. Podemos obtê-las criadas a partir das configurações do lote de primavera, mas é aconselhável fazê-lo manualmente executando os ficheiros SQL, como pode ver no código comentado acima. Do ponto de vista da segurança, é melhor não dar acesso à execução de DDL ao utilizador da base de dados do lote de primavera.
Tabelas do Spring Batch
As tabelas do Spring Batch correspondem muito bem aos objetos de domínio que os representam em Java. Por exemplo – JobInstance, JobExecution, JobParameters e StepExecution mapeiam para BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_JOB_EXECUTION_PARAMS e BATCH_STEP_EXECUTION, respectivamente. ExecutionContext mapeia tanto para BATCH_JOB_EXECUTION_CONTEXT quanto para BATCH_STEP_EXECUTION_CONTEXT. O JobRepository é responsável por salvar e armazenar cada objeto Java em sua tabela correta. Abaixo estão os detalhes de cada tabela de metadados.
- Batch_job_instance: A tabela BATCH_JOB_INSTANCE contém todas as informações relevantes para um JobInstance.
- Batch_job_execution_params: A tabela BATCH_JOB_EXECUTION_PARAMS contém todas as informações relevantes para o objeto JobParameters.
- Batch_job_execution: A tabela BATCH_JOB_EXECUTION contém dados relevantes para o objeto JobExecution. Uma nova linha é adicionada toda vez que um Job é executado.
- Batch_step_execution: A tabela BATCH_STEP_EXECUTION contém todas as informações relevantes para o objeto StepExecution.
- Batch_job_execution_context: A tabela BATCH_JOB_EXECUTION_CONTEXT contém dados relevantes para o ExecutionContext de um Job. Existe exatamente um Job ExecutionContext para cada JobExecution, e ele contém todos os dados de nível de job necessários para essa execução específica do job. Esses dados normalmente representam o estado que deve ser recuperado após uma falha para que uma JobInstance possa reiniciar a partir do ponto em que falhou.
- Contexto_de_Execução_de_Etapa_em_Lote: A tabela BATCH_STEP_EXECUTION_CONTEXT contém dados relevantes para o ExecutionContext de uma Etapa. Há exatamente um ExecutionContext para cada StepExecution, e ele contém todos os dados que precisam ser persistidos para uma execução de etapa específica. Esses dados normalmente representam o estado que deve ser recuperado após uma falha para que uma JobInstance possa ser reiniciada de onde falhou.
- Sequência_de_Execução_de_Job_em_Lote: Esta tabela contém a sequência de execução dos dados do job.
- Sequência_de_Execução_de_Etapa_em_Lote: Esta tabela contém os dados da sequência para a execução da etapa.
- Sequência_de_Job_em_Lote: Esta tabela contém os dados da sequência do job, no caso de termos vários jobs, obteremos várias linhas.
Programa de Teste do Spring Batch
Nosso projeto de exemplo do Spring Batch está pronto, o último passo é escrever uma classe de teste para executá-lo como um programa 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 executar o programa acima e você obterá um XML de saída como abaixo.
<?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>
Isso é tudo para o exemplo do Spring Batch, você pode baixar o projeto final no link abaixo.
Baixar Projeto de Exemplo do Spring Batch
Referência: Guia Oficial
Source:
https://www.digitalocean.com/community/tutorials/spring-batch-example