Java Batch Tutorial

Nel mondo di oggi internet ha cambiato il modo in cui viviamo la nostra vita e uno dei motivi principali è l’uso di internet per la maggior parte delle faccende quotidiane. Questo porta ad un’enorme quantità di dati disponibili per l’elaborazione.

Alcuni esempi in cui sono coinvolti dati enormi sono l’elaborazione di buste paga, estratti conto, calcolo degli interessi, ecc. Quindi immaginate se tutti questi lavori dovessero essere fatti manualmente, ci vorrebbero secoli per finire questi lavori.

Come si fa nell’era attuale? La risposta è l’elaborazione in batch.

Introduzione

L’elaborazione in batch viene eseguita su dati di massa, senza intervento manuale, e a lungo termine. Potrebbe essere ad alta intensità di dati o di calcolo. I lavori in batch possono essere eseguiti su un programma predefinito o possono essere avviati su richiesta. Inoltre, dato che i lavori batch sono di solito lavori di lunga durata, i controlli costanti e il riavvio da un certo fallimento sono caratteristiche comuni trovate nei lavori batch.

1.1 Storia dell’elaborazione batch di Java

L’elaborazione batch per la piattaforma Java è stata introdotta come parte della specifica JSR 352, parte della piattaforma Java EE 7, definisce il modello di programmazione per le applicazioni batch più un runtime per eseguire e gestire i lavori batch.

1.2 Architettura di Java Batch

Il seguente diagramma mostra i componenti di base per l’elaborazione batch.

Architettura per l’elaborazione batch di Java

L’architettura per le applicazioni batch risolve i problemi dell’elaborazione batch come i lavori, i passi, i depositi, i modelli di scrittore del processore lettore, i chunks, i checkpoints, l’elaborazione parallela, il flusso, i tentativi, il sequenziamento, il partizionamento, ecc.

Comprendiamo il flusso dell’architettura.

  • Job repository contiene i lavori che devono essere eseguiti.
  • JobLauncher estrae un lavoro da Job repository.
  • Ogni lavoro contiene passi. I passi sono ItemReader, ItemProcessor e ItemWriter.
  • Item Reader è quello che legge i dati.
  • Item Process è quello che elaborerà i dati in base alla logica di business.
  • L’Item writer scriverà i dati all’origine definita.

1.3 Componenti dell’elaborazione batch.

Tentiamo ora di capire i componenti dell’elaborazione batch in dettaglio.

  • Job: Un lavoro comprende l’intero processo batch. Contiene uno o più passi. Un lavoro è messo insieme usando un Job Specification Language (JSL) che specifica l’ordine in cui i passi devono essere eseguiti. In JSR 352, il JSL è specificato in un file XML conosciuto come job XML file. Un lavoro è fondamentalmente un contenitore che contiene passi.
  • Passo: Un passo è un oggetto di dominio che contiene una fase indipendente e sequenziale del lavoro. Un passo contiene tutta la logica e i dati necessari per eseguire l’elaborazione effettiva. La definizione di un passo è mantenuta vaga secondo le specifiche del batch perché il contenuto di un passo è puramente specifico dell’applicazione e può essere complesso o semplice come vuole lo sviluppatore. Ci sono due tipi di passi: chunk e task oriented.
  • Job Operator: Fornisce un’interfaccia per gestire tutti gli aspetti dell’elaborazione del lavoro, che include i comandi operativi, come start, restart e stop, così come i comandi del job repository, come il recupero delle esecuzioni del lavoro e dei passi.
  • Job Repository: Contiene informazioni sui lavori attualmente in esecuzione e dati storici sul lavoro. JobOperator fornisce API per accedere a questo repository. Un JobRepositorypotrebbe essere implementato usando un database o un file system.

La seguente sezione aiuterà a capire alcuni caratteri comuni di un’architettura batch.

1.3 Passi in un lavoro

Un passo è una fase indipendente di un lavoro. Come discusso sopra, ci sono due tipi di passi in un lavoro. Cercheremo di capire entrambi i tipi in dettaglio qui sotto.

1.3.1 Passi orientati al chunk

I passi chunk leggono ed elaborano un elemento alla volta e raggruppano i risultati in un chunk. I risultati vengono poi memorizzati quando il chunk raggiunge una dimensione predefinita. L’elaborazione orientata ai chunk rende la memorizzazione dei risultati più efficiente quando il set di dati è enorme. Contiene tre parti.

  • Il lettore di elementi legge gli input uno dopo l’altro da una fonte di dati che può essere un database, un file piatto, un file di log, ecc.
  • Il processore elaborerà i dati uno per uno in base alla logica di business definita.
  • Uno scrittore scrive i dati in chunk. La dimensione del chunk è predefinita ed è configurabile

Come parte dei passi chunk, ci sono dei checkpoint che forniscono informazioni al framework per il completamento dei chunks. Se c’è un errore durante l’elaborazione di un chunk il processo può ripartire in base all’ultimo checkpoint.

1.3.2 Task-Oriented Steps

Esegue compiti diversi dall’elaborazione di elementi da una fonte di dati. Il che include la creazione o la rimozione di directory, lo spostamento di file, la creazione o la cancellazione di tabelle di database, ecc. I passi task non sono solitamente di lunga durata rispetto ai passi chunk.

In uno scenario normale, i passi task-oriented sono usati dopo i passi chunk-oriented dove c’è bisogno di una pulizia. Per esempio, otteniamo file di log come output di un’applicazione. I passi chunk sono usati per elaborare i dati e ottenere informazioni significative dai file di log.

Il passo task è quindi usato per pulire i vecchi file di log che non sono più necessari.

1.3.3 Elaborazione parallela

I lavori batch spesso eseguono operazioni di calcolo costose ed elaborano grandi quantità di dati. Le applicazioni batch possono beneficiare dell’elaborazione parallela in due scenari.

  • I passi che sono indipendenti in natura possono essere eseguiti su diversi thread.
  • I passi orientati ai pezzi dove l’elaborazione di ogni elemento è indipendente dai risultati dell’elaborazione degli elementi precedenti possono essere eseguiti su più di un thread.

L’elaborazione per lotti aiuta a finire i compiti e ad eseguire le operazioni più velocemente per dati enormi.

Strumenti e tecnologie

Guardiamo le tecnologie e gli strumenti usati per costruire il programma.

  • Eclipse Oxygen.2 Release (4.7.2)
  • Java – versione 9.0.4
  • Gradle- 4.3
  • Spring boot – 2.0.1-Release
  • HSQL Database

Struttura del progetto

La struttura del progetto sarà come mostrato nell’immagine sottostante.

Struttura del progetto per Java Batch

La struttura del progetto qui sopra sta usando Gradle. Questo progetto può anche essere creato usando maven e il build.gralde sarà sostituito dal file pom.xml. La struttura del progetto differirà leggermente con l’uso di Maven per la compilazione.

Un obiettivo del programma

Come parte del programma, proveremo a creare una semplice applicazione batch java usando spring boot. Questa applicazione eseguirà i seguenti compiti.

  1. Leggere: – Leggere i dati dei dipendenti da un file CSV.
  2. Elaborare i dati: – Converte i dati dei dipendenti in tutte le maiuscole.
  3. Scrivi: – Scrivere i dati dei dipendenti elaborati di nuovo nel database.

4.1 Gradle build

Stiamo usando Gradle per la build come parte del programma. Il file build.gradle sarà come mostrato qui sotto.

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")}

Nel file build.gradle di cui sopra apply plugin: 'java' ci dice il plugin che deve essere impostato. Per noi, è il plugin Java.
repositories{} ci dice il repository da cui la dipendenza deve essere estratta. Noi abbiamo scelto mavenCentral per estrarre i vasi della dipendenza. Possiamo usare jcenter anche per estrarre i rispettivi jar di dipendenza.

dependencies {} tag è usato per fornire i dettagli dei file jar necessari che dovrebbero essere estratti per il progetto. apply plugin: 'org.springframework.boot' questo plugin è usato per specificare un progetto spring-boot. boot jar{} specificherà le proprietà del jar che verrà generato dalla compilazione.

4.2 File dati di esempio

Per fornire i dati per la fase di lettura, useremo un file CSV contenente i dati dei dipendenti.

Il file sarà come mostrato qui sotto.

File CSV di esempio

John,FosterJoe,ToyJustin,TaylorJane,ClarkJohn,Steve

Il file dati di esempio contiene il nome e cognome del dipendente. Useremo gli stessi dati per l’elaborazione e poi l’inserimento nel database.

4.3 Script SQL

Stiamo usando il database HSQL che è un database basato sulla memoria. Lo script sarà come mostrato qui sotto.

Script SQL

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 esegue schema-@@platform@@.sql automaticamente quando si avvia. -all è l’impostazione predefinita per tutte le piattaforme. Quindi la creazione della tabella avverrà da sola all’avvio dell’applicazione e sarà disponibile fino a quando l’applicazione sarà attiva e funzionante.

4.4 Classe modello

Creeremo una classe Employee.java come classe modello. La classe avrà l’aspetto mostrato qui sotto.

Classe modello per il programma

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 è usata per sovrascrivere l’implementazione di default del metodo toString().

4.5 Classe di configurazione

Creeremo una classe BatchConfiguration.javache sarà la classe di configurazione per l’elaborazione batch. Il file java sarà come mostrato qui sotto.

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 l’annotazione è usata per abilitare l’elaborazione batch.
JobBuilderFactory è la fabbrica che è usata per costruire un lavoro.
StepBuilderFactory è usata per la creazione dei passi.
Il metodo step1() ha una proprietà chunk(). Questa è la proprietà usata per il chunking dell’input in una dimensione definita. Per noi, la dimensione è 10.

4.6 Item Processor

Item processor è un’interfaccia che sarà responsabile dell’elaborazione dei dati. Implementeremo l’interfaccia in EmployeeItemProcessor.java. La classe java sarà come mostrato qui sotto.

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; }}

Nel metodo process() prenderemo i dati e li trasformeremo nel nome maiuscolo.

4.7 Classe JobExecutionSupportListener

JobExecutionListenerSupport è l’interfaccia che notifica quando il lavoro è completato. Come parte dell’interfaccia, abbiamo il metodo afterJob. Questo metodo è usato per postare il completamento del lavoro.

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());}}}}

In questo metodo, stiamo ottenendo i dati dal database dopo il completamento del lavoro e stiamo stampando il risultato sulla console per verificare l’elaborazione che è stata eseguita sui dati.

4.8 Classe applicazione

Creeremo una classe applicazione che conterrà il metodo principale responsabile dell’attivazione del programma batch java. La classe sarà come mostrato qui sotto.

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 è l’annotazione usata per specificare un programma come un programma spring boot.

Output

Eseguiamo l’applicazione come un’applicazione Java. Otterremo il seguente output sulla console.

L’output del programma JavaBatch

Il flusso di lavoro del programma batch è molto chiaramente disponibile nell’output. Il lavoro inizia con importUserJob, poi inizia l’esecuzione del passo-1 dove converte i dati letti in maiuscolo.

Post-elaborazione del passo, possiamo vedere il risultato maiuscolo sulla console.

Sommario

In questo tutorial, abbiamo imparato le seguenti cose:

  1. Java batch contiene lavori che possono contenere più passi.
  2. Ogni passo è una combinazione di lettura, elaborazione, scrittura.
  3. Possiamo spezzettare i dati in diverse dimensioni per l’elaborazione.

7. Scarica il progetto Eclipse

Questo era un tutorial per JavaBatch con SpringBoot.