Exemplo do Spring Batch

Bem-vindo ao Exemplo do Spring Batch. O Spring Batch é um módulo do framework Spring para a 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 para o programa de exemplo do Spring Batch, vamos ter uma ideia sobre as 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.
  • Read-Process-Write 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 limpeza de conexões, liberando recursos após o processamento ser concluído.
  • Read-Process-Write e tasklets podem ser encadeados para executar um trabalho.

Exemplo do Spring Batch

Vamos considerar um exemplo de trabalho 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 junto com os dados e as tags serão nomeadas conforme o nome da coluna. Abaixo estão as ferramentas e bibliotecas importantes usadas para o exemplo do Spring Batch.

  1. Apache Maven 3.5.0 – para construção de projeto e gerenciamento de dependências.
  2. Eclipse Oxygen Release 4.7.0 – IDE para criação de aplicação spring batch com 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 – usado de acordo com sua 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 do 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 do 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 Lote de Arquivos CSV com Spring

Aqui está o conteúdo do nosso arquivo CSV de amostra para processamento de lote com Spring.

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

Configuração do Trabalho de Lote do Spring

Precisamos definir um bean do Spring e um trabalho de lote do Spring em um arquivo de configuração. Abaixo está o conteúdo do arquivo job-batch-demo.xml, que é a parte mais importante do projeto de lote do 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>
  1. Estamos utilizando FlatFileItemReader para ler o arquivo CSV, CustomItemProcessor para processar os dados e escrever em um arquivo XML usando StaxEventItemWriter.
  2. 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.
  3. batch:step – Esta tag é usada para definir diferentes etapas de um trabalho de lote do Spring.
  4. Existem dois tipos diferentes de estilos de processamento oferecidos pelo Framework Spring Batch, que são “Orientado a Tarefa (TaskletStep)” e “Orientado a Chunk”. O estilo Orientado a Chunk é usado neste exemplo para ler os dados um por um e criar ‘chunks’ que serão escritos, dentro de um limite de transação.
  5. leitor: feijão spring usado para ler os dados. Nós usamos o feijão csvFileItemReader neste exemplo, que é uma instância de FlatFileItemReader.
  6. processador: esta é a classe que é usada para processar os dados. Nós usamos CustomItemProcessor neste exemplo.
  7. escritor: feijão usado para escrever dados em um arquivo xml.
  8. 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 irá ler os dados um por um e o ItemProcessor também irá processá-lo da mesma forma, mas o ItemWriter só irá escrever os dados quando o tamanho for igual ao commit-interval.
  9. Três interfaces importantes que são usadas como parte deste projeto são ItemReader, ItemProcessor e ItemWriter do pacote org.springframework.batch.item.

Classe do Modelo Spring Batch

Primeiramente, estamos lendo um arquivo CSV para um objeto Java e, em seguida, utilizando o JAXB para escrevê-lo em um arquivo XML. Abaixo está nossa classe de 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 iguais aos definidos na configuração do mapeador de lote do Spring, ou seja, property name="names" value="id,firstname,lastname,dob" no nosso caso.

FieldSetMapper do Spring Batch

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;

	}

}

Processador de Item do Spring Batch

Agora, conforme definido na configuração do trabalho, 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 dados na implementação do ItemProcessor, como você pode ver, estou convertendo os valores do primeiro nome e sobrenome para maiúsculas.

Arquivos de Configuração do Spring

No nosso arquivo de configuração do Spring Batch, importamos dois arquivos 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 em sua tabela de metadados correta para o Spring Batch.
  • transactionManager – este é responsável por confirmar a transação assim que o tamanho do intervalo de confirmação e os dados processados forem iguais.
  • jobLauncher – Este é o coração do Spring Batch. Esta interface contém o método run que é usado para acionar o 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>

O Spring Batch usa algumas tabelas de metadados para armazenar informações sobre os jobs em lote. Podemos criá-las a partir das configurações do Spring Batch, mas é aconselhável fazê-lo manualmente executando os arquivos SQL, como você pode ver no código comentado acima. Do ponto de vista da segurança, é melhor não conceder acesso à execução de DDL ao usuário do banco de dados do Spring Batch.

Tabelas do Spring Batch

As tabelas do Spring Batch correspondem muito de perto 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.

  1. Batch_job_instance: A tabela BATCH_JOB_INSTANCE contém todas as informações relevantes para uma JobInstance.
  2. Batch_job_execution_params: A tabela BATCH_JOB_EXECUTION_PARAMS contém todas as informações relevantes para o objeto JobParameters.
  3. 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.
  4. Batch_step_execution: A tabela BATCH_STEP_EXECUTION contém todas as informações relevantes para o objeto StepExecution.
  5. Batch_job_execution_context: A tabela BATCH_JOB_EXECUTION_CONTEXT contém dados relevantes para o ExecutionContext de um Job. Existe exatamente um ExecutionContext de Job para cada JobExecution, e ele contém todos os dados de nível de trabalho necessários para aquela execução de trabalho específica. Esses dados normalmente representam o estado que deve ser recuperado após uma falha, para que uma JobInstance possa ser reiniciada a partir de onde falhou.
  6. Contexto_de_Execução_de_Etapa_em_Lote: A tabela BATCH_STEP_EXECUTION_CONTEXT contém dados relevantes para o Contexto de Execução 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 precisa ser recuperado após uma falha para que uma JobInstance possa ser reiniciada de onde falhou.
  7. Sequência_de_Execução_de_Job_em_Lote: Esta tabela contém a sequência de execução dos jobs.
  8. Sequência_de_Execução_de_Etapa_em_Lote: Esta tabela contém os dados para a sequência de execução da etapa.
  9. Sequência_de_Job_em_Lote: Esta tabela contém os dados para a sequência de jobs, caso tenhamos múltiplos jobs, obteremos várias linhas.

Programa de Teste Spring Batch

Nosso projeto de exemplo do Spring Batch está pronto, o passo final é 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