Java Batch Tutorial

No mundo de hoje a internet mudou a forma como vivemos as nossas vidas e uma das principais razões para isso é o uso da internet para a maioria das tarefas diárias. Isto levou a uma enorme quantidade de dados disponíveis para processamento.

Alguns dos exemplos em que enormes dados estão envolvidos são o processamento de folhas de pagamento, extratos bancários, cálculo de juros, etc. Então imagine que se todos estes trabalhos tivessem que ser feitos manualmente, demoraria uma eternidade para terminar estes trabalhos.

Como é feito na idade atual? A resposta é Processamento em Lote.

Introdução

O processamento em lote é realizado em dados em massa, sem intervenção manual, e de longa duração. Pode ser de dados ou de computação intensiva. Os trabalhos em lote podem ser executados na programação pré-definida ou podem ser iniciados sob demanda. Além disso, como os trabalhos em lote são geralmente trabalhos de longa execução, verificações constantes e reinicialização a partir de uma determinada falha são características comuns encontradas em trabalhos em lote.

1.1 Histórico do processamento em lote Java

O processamento em lote da plataforma Java foi introduzido como parte da especificação JSR 352, parte da plataforma Java EE 7, define o modelo de programação para aplicações em lote mais um tempo de execução para executar e gerenciar trabalhos em lote.

1.2 Arquitetura do processamento em lote Java

O diagrama abaixo mostra os componentes básicos para o processamento em lote.

Arquitectura para processamento em lote Java

A arquitectura para aplicações em lote resolve problemas de processamento em lote como trabalhos, passos, repositórios, padrões de escrita do processador do leitor, pedaços, pontos de verificação, processamento paralelo, fluxo, repetições, sequenciação, particionamento, etc.

Deixe entender o fluxo da arquitetura.

  • Repositório de trabalhos contém os trabalhos que precisam ser executados.
  • JobLauncherPuxa um trabalho do repositório de trabalhos.
  • Cada trabalho contém passos. Os passos são ItemReader, ItemProcessor e ItemWriter.
  • Item Reader é aquele que lê os dados.
  • Item Process é aquele que processará os dados baseado na lógica do negócio.
  • O Item writer irá escrever os dados de volta para a fonte definida.

1.3 Batch Processing Components.

Tentamos agora entender os componentes de processamento em lote em detalhes.

  • Job: Um trabalho compreende todo o processo de processamento em lote. Ele contém uma ou mais etapas. Um job é montado usando uma linguagem de especificação do job (JSL) que especifica a ordem em que os passos devem ser executados. No JSR 352, JSL é especificado em um arquivo XML conhecido como o arquivo XML do job. Um job é basicamente um container contendo os passos.
  • Passo: Um passo é um objeto de domínio que contém uma fase independente e seqüencial do job. Uma etapa contém toda a lógica e dados necessários para realizar o processamento real. A definição de uma etapa é mantida vaga de acordo com a especificação do lote, porque o conteúdo de uma etapa é puramente específico da aplicação e pode ser tão complexo ou simples quanto o desenvolvedor desejar. Existem dois tipos de etapas: parte e tarefa orientada.
  • Job Operator: Ele fornece uma interface para gerenciar todos os aspectos do processamento de tarefas, que inclui comandos operacionais, como iniciar, reiniciar e parar, assim como comandos de repositório de tarefas, como a recuperação de execuções de tarefas e passos.
  • Repositório de tarefas: Contém informações sobre jobs atualmente em execução e dados históricos sobre o job. JobOperator fornece APIs para acessar este repositório. Um JobRepository pode ser implementado usando, um banco de dados ou um sistema de arquivos.

A seção seguinte ajudará a entender alguns caracteres comuns de uma arquitetura batch.

1.3 Passos em Job

Um Passo é uma fase independente de um Job. Como discutido acima, existem dois tipos de passos em um Trabalho. Vamos tentar entender os dois tipos em detalhes abaixo.

1.3.1 Passos em Chunk-Oriented Steps

Chunk steps irão ler e processar um item de cada vez e agrupar os resultados em um trecho. Os resultados são então armazenados quando o pedaço atinge um tamanho pré-definido. O processamento orientado a pedaços torna o armazenamento de resultados mais eficiente quando o conjunto de dados é enorme. Ele contém três partes.

  • O leitor de item lê a entrada um após o outro de uma fonte de dados que pode ser um banco de dados, arquivo plano, arquivo de log, etc.
  • O processador irá processar os dados um a um baseado na lógica de negócios definida.
  • Um gravador escreve os dados em pedaços. O tamanho do pedaço é predefinido e é configurável

Como parte dos passos do pedaço, existem pontos de verificação que fornecem informações para a estrutura para a conclusão dos pedaços. Se houver um erro durante o processamento de um trecho, o processo pode ser reiniciado com base no último ponto de verificação.

1.3.2 Etapas orientadas a tarefas

Executa tarefas que não sejam itens de processamento de uma fonte de dados. O que inclui a criação ou remoção de diretórios, movimentação de arquivos, criação ou exclusão de tabelas de banco de dados, etc. As etapas da tarefa não são normalmente de longa duração em comparação com as etapas de um trecho.

Em um cenário normal, as etapas orientadas a tarefas são usadas após as etapas orientadas a trechos onde há uma limpeza necessária. Por exemplo, recebemos arquivos de log como uma saída de uma aplicação. Os passos de um trecho são usados para processar os dados e obter informações significativas dos arquivos de log.

O passo da tarefa é então usado para limpar arquivos de log mais antigos que não são mais necessários.

1.3.3 Processamento Paralelo

Os trabalhos em lote geralmente executam operações computacionais caras e processam grandes quantidades de dados. Aplicações em lote podem se beneficiar do processamento paralelo em dois cenários.

  • Passos que são independentes por natureza podem ser executados em diferentes threads.
  • Passos orientados para o processamento de cada item, onde o processamento de cada item é independente dos resultados do processamento de itens anteriores podem ser executados em mais de um thread.

O processamento em lote ajuda a terminar tarefas e executar operações mais rapidamente para grandes dados.

Ferramentas e tecnologias

Deixe-nos ver as tecnologias e ferramentas usadas para construir o programa.

  • Eclipse Oxygen.2 Release (4.7.2)
  • Java – versão 9.0.4
  • Gradle- 4.3
  • Spring boot – 2.0.1-Release
  • Base de dadosHSQL

Estrutura do projeto

A estrutura do projeto terá o aspecto mostrado na imagem abaixo.

Estrutura do projeto para Java Batch

A estrutura do projeto acima está usando Gradle. Este projeto também pode ser criado usando maven e o build.gralde será substituído pelo arquivo pom.xml. A estrutura do projeto será ligeiramente adiada com o uso do Maven para o build.

Um objetivo do programa

Como parte do programa, tentaremos criar uma simples aplicação java batch usando spring boot. Esta aplicação irá executar as seguintes tarefas.

  1. Ler: – Ler dados de empregados de um arquivo CSV.
  2. Processar os dados: – Converter os dados dos funcionários em todas as maiúsculas.
  3. Escrever: – Escreva os dados processados do empregado de volta no banco de dados.

4.1 Gradle build

Estamos usando o Gradle para o build como parte do programa. O arquivo build.gradle aparecerá como mostrado abaixo.

build.gradle

buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.1.RELEASE") }}apply plugin: 'java'apply plugin: 'eclipse'apply plugin: 'idea'apply plugin: 'org.springframework.boot'apply plugin: 'io.spring.dependency-management'bootJar { baseName = 'java-batch' version = '1.0'}repositories { mavenCentral()}sourceCompatibility = 1.8targetCompatibility = 1.8dependencies { compile("org.springframework.boot:spring-boot-starter-batch") compile("org.hsqldb:hsqldb") testCompile("junit:junit")}

No acima build.gradle arquivo apply plugin: 'java' nos diz o plugin que precisa ser configurado. Para nós, é o plugin Java.
repositories{} que nos permite saber o repositório do qual a dependência deve ser tirada. Nós escolhemos mavenCentral para puxar os frascos de dependência. Podemos usar jcenter também para puxar os respectivos frascos de dependência.

dependencies {} tag é usado para fornecer os detalhes necessários do arquivo do frasco que deve ser puxado para o projeto. apply plugin: 'org.springframework.boot' este plugin é usado para especificar um projeto de spring-boot. boot jar{} especificará as propriedades do jarro que será gerado a partir do build.

4.2 Amostra de arquivo de dados

Para fornecer dados para a fase de leitura, usaremos um arquivo CSV contendo dados do funcionário.

O arquivo terá a aparência abaixo.

Amostra de arquivo CSV

John,FosterJoe,ToyJustin,TaylorJane,ClarkJohn,Steve

O arquivo de dados de amostra contém o primeiro e último nome do funcionário. Utilizaremos os mesmos dados para processamento e depois inserção na base de dados.

4.3 scripts SQL

Estamos a utilizar a base de dados HSQL que é uma base de dados baseada em memória. O script será como mostrado abaixo.

SQL script

DROP TABLE employee IF EXISTS;CREATE TABLE employee ( person_id BIGINT IDENTITY NOT NULL PRIMARY KEY, first_name VARCHAR(20), last_name VARCHAR(20));

Spring Boot roda schema-@@platform@@.sql automaticamente quando ele é iniciado. -all é o padrão para todas as plataformas. Então a criação da tabela acontecerá por conta própria quando a aplicação for iniciada e estará disponível até que a aplicação esteja em funcionamento.

4.4 Model Class

Vamos criar uma classe Employee.java como a classe model. A classe aparecerá como mostrado abaixo.

Classe Modelo para o Programa

package com.batch;public class Employee { private String lastName; private String firstName; public Employee() { } public Employee(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @Override public String toString() { return "firstName: " + firstName + ", lastName: " + lastName; }}

@Override é usada para sobrepor a implementação padrão do método toString().

4.5 Classe de Configuração

Vamos criar uma classe BatchConfiguration.java que será a classe de configuração para processamento em lote. O arquivo java será como mostrado abaixo.

BatchConfiguration.java

package com.batch.config;import javax.sql.DataSource;import org.springframework.batch.core.Job;import org.springframework.batch.core.JobExecutionListener;import org.springframework.batch.core.Step;import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;import org.springframework.batch.core.launch.support.RunIdIncrementer;import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;import org.springframework.batch.item.database.JdbcBatchItemWriter;import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;import org.springframework.batch.item.file.FlatFileItemReader;import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;import org.springframework.batch.item.file.mapping.DefaultLineMapper;import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.ClassPathResource;import org.springframework.jdbc.core.JdbcTemplate;import com.batch.Employee;import com.batch.processor.EmployeeItemProcessor;@Configuration@EnableBatchProcessingpublic class BatchConfiguration { @Autowired public JobBuilderFactory jobBuilderFactory; @Autowired public StepBuilderFactory stepBuilderFactory; // tag::readerwriterprocessor @Bean public FlatFileItemReader reader() { return new FlatFileItemReaderBuilder() .name("EmployeeItemReader") .resource(new ClassPathResource("sample-data.csv")) .delimited() .names(new String{"firstName", "lastName"}) .fieldSetMapper(new BeanWrapperFieldSetMapper() {{ setTargetType(Employee.class); }}) .build(); } @Bean public EmployeeItemProcessor processor() { return new EmployeeItemProcessor(); } @Bean public JdbcBatchItemWriter writer(DataSource dataSource) { return new JdbcBatchItemWriterBuilder() .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) .sql("INSERT INTO employee (first_name, last_name) VALUES (:firstName, :lastName)") .dataSource(dataSource) .build(); } // end::readerwriterprocessor // tag::jobstep @Bean public Job importUserJob(JobCompletionNotificationListener listener, Step step1) { return jobBuilderFactory.get("importUserJob") .incrementer(new RunIdIncrementer()) .listener(listener) .flow(step1) .end() .build(); } @Bean public Step step1(JdbcBatchItemWriter writer) { return stepBuilderFactory.get("step1") .<Employee, Employee> chunk(10) .reader(reader()) .processor(processor()) .writer(writer) .build(); } // end::jobstep}

@EnableBatchProcessing a anotação é usada para habilitar o processamento em lote.
JobBuilderFactory é a fábrica que é usada para construir um trabalho.
StepBuilderFactory é usada para a criação do passo.
O método step1() tem uma propriedade chunk(). Esta é a propriedade usada para a entrada em um tamanho definido. Para nós, o tamanho é 10.

4.6 Item Processador

Processador Item é uma interface que será responsável pelo processamento dos dados. Vamos implementar a interface em EmployeeItemProcessor.java. A classe java aparecerá como mostrado abaixo.

EmployeeItemProcessor.java

package com.batch.processor;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.batch.item.ItemProcessor;import com.batch.Employee;public class EmployeeItemProcessor implements ItemProcessor<Employee, Employee> { private static final Logger log = LoggerFactory.getLogger(EmployeeItemProcessor.class); @Override public Employee process(Employee emp) throws Exception { final String firstName = emp.getFirstName().toUpperCase(); final String lastName = emp.getLastName().toUpperCase(); final Employee transformedEmployee = new Employee(firstName, lastName); log.info("Converting (" + emp + ") into (" + transformedEmployee + ")"); return transformedEmployee; }}

No método process() estaremos obtendo os dados e os transformaremos no nome em maiúsculas.

4.7 JobExecutionSupportListener class

JobExecutionListenerSupport é a interface que irá notificar quando o trabalho for concluído. Como parte da interface, nós temos afterJob método. Este método é usado para postar a conclusão do trabalho.

JobCompletionNotificationListener.java

package com.batch.config;import java.util.List;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.batch.core.BatchStatus;import org.springframework.batch.core.JobExecution;import org.springframework.batch.core.listener.JobExecutionListenerSupport;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;import org.springframework.stereotype.Component;import com.batch.Employee;@Componentpublic class JobCompletionNotificationListener extends JobExecutionListenerSupport {private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class);private final JdbcTemplate jdbcTemplate;@Autowiredpublic JobCompletionNotificationListener(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Overridepublic void afterJob(JobExecution jobExecution) {RowMapper rowMapper = (rs, rowNum) -> {Employee e = new Employee();e.setFirstName(rs.getString(1));e.setLastName(rs.getString(2));return e;};if(jobExecution.getStatus() == BatchStatus.COMPLETED) {log.info("!!! JOB FINISHED! Time to verify the results");List empList= jdbcTemplate.query("SELECT first_name, last_name FROM employee",rowMapper);log.info("Size of List "+empList.size());for (Employee emp: empList) {log.info("Found: "+emp.getFirstName()+" "+emp.getLastName());}}}}

Neste método, estamos obtendo os dados da base de dados após a conclusão do trabalho e estamos imprimindo o resultado no console para verificar o processamento que foi realizado nos dados.

4.8 Classe de aplicação

Criaremos uma classe de aplicação que conterá o principal método responsável por acionar o programa em lote java. A classe aparecerá como mostrado abaixo.

Application.java

package com.batch;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class Application { public static void main(String args) throws Exception { SpringApplication.run(Application.class, args); }}

@SpringBootApplication é a anotação usada para especificar um programa como um programa de inicialização por mola.

Output

Deixamos executar a aplicação como uma aplicação Java. Vamos obter a seguinte saída no console.

A saída do programa JavaBatch

O fluxo de trabalho do programa batch está muito claramente disponível na saída. O Job começa com importUserJob, depois inicia a execução do passo 1 onde converte os dados lidos em maiúsculas.

Pós-processamento do passo, podemos ver o resultado em maiúsculas no console.

Resumo

Neste tutorial, aprendemos as seguintes coisas:

  1. Java batch contém Jobs que podem conter múltiplos passos.
  2. Cada passo é uma combinação de leitura, processar, escrever.
  3. Possibilitamos de dividir os dados em diferentes tamanhos para processamento.

7. Baixe o projeto Eclipse

Este foi um tutorial para JavaBatch com SpringBoot.