Exemplo de Lote de Primavera

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.

  1. Apache Maven 3.5.0 – para construção de projetos e gerenciamento de dependências.
  2. Eclipse Oxygen Release 4.7.0 – IDE para criar 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 – 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>
  1. Estamos usando o FlatFileItemReader para ler o arquivo CSV, CustomItemProcessor para processar os dados e escrever no 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 lotes Spring.
  4. 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.
  5. leitor: bean Spring usado para ler os dados. Nós usamos o bean csvFileItemReader neste exemplo, que é uma instância de FlatFileItemReader.
  6. processador: esta é a classe usada para processar os dados. Nós usamos CustomItemProcessor neste exemplo.
  7. escritor: bean 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 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.
  9. Três interfaces importantes que são usadas neste projeto são ItemReader, ItemProcessor e ItemWriter do pacote org.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.

  1. Batch_job_instance: A tabela BATCH_JOB_INSTANCE contém todas as informações relevantes para um 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 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.
  6. 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.
  7. Sequência_de_Execução_de_Job_em_Lote: Esta tabela contém a sequência de execução dos dados do job.
  8. Sequência_de_Execução_de_Etapa_em_Lote: Esta tabela contém os dados da sequência para a execução da etapa.
  9. 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