Java Batch Tutorial

V dnešním světě internet změnil způsob našeho života a jedním z hlavních důvodů je používání internetu pro většinu každodenních povinností. To vedlo ke vzniku obrovského množství dat, která jsou k dispozici ke zpracování.

Několik příkladů, kde se jedná o obrovské množství dat, je zpracování výplatních pásek, bankovních výpisů, výpočet úroků atd. Představte si tedy, že by se všechny tyto práce musely dělat ručně, trvalo by to celé věky.

Jak se to dělá v současné době? Odpovědí je dávkové zpracování.

Úvod

Dávkové zpracování se provádí nad hromadnými daty, bez ručního zásahu a dlouhodobě. Může být datově nebo výpočetně náročné. Dávkové úlohy mohou být spouštěny podle předem definovaného plánu nebo mohou být iniciovány na vyžádání. Také proto, že dávkové úlohy jsou obvykle dlouhotrvající úlohy, patří mezi běžné vlastnosti, které se u dávkových úloh vyskytují, neustálé kontroly a opětovné spuštění od určité poruchy.

1.1 Historie dávkového zpracování v jazyce Java

Dávkové zpracování pro platformu Java bylo zavedeno jako součást specifikace JSR 352, která je součástí platformy Java EE 7 a definuje programovací model pro dávkové aplikace plus běhové prostředí pro spouštění a správu dávkových úloh.

1.2 Architektura dávkového zpracování v jazyce Java

Následující schéma ukazuje základní komponenty pro dávkové zpracování.

Architektura pro dávkové zpracování v jazyce Java

Architektura pro dávkové aplikace řeší problémy dávkového zpracování, jako jsou úlohy, kroky, úložiště, vzory čtecího procesoru a zapisovacího procesoru, kusy, kontrolní body, paralelní zpracování, tok, opakování, sekvencování, rozdělení atd.

Pochopme tok architektury.

  • Úložiště úloh obsahuje úlohy, které je třeba spustit.
  • JobLauncher vytáhne úlohu z úložiště úloh.
  • Každá úloha obsahuje kroky. Kroky jsou ItemReader, ItemProcessor a ItemWriter.
  • Item Reader je ten, který načte data.
  • Item Process je ten, který zpracuje data na základě obchodní logiky.
  • Zapisovatel položek bude data zapisovat zpět do definovaného zdroje.

1.3 Komponenty dávkového zpracování.

Nyní se pokusíme podrobně pochopit komponenty dávkového zpracování.

  • Úloha: Úloha zahrnuje celý dávkový proces. Obsahuje jeden nebo více kroků. Úloha je sestavena pomocí jazyka specifikace úlohy (Job Specification Language, JSL), který určuje pořadí, v němž musí být kroky provedeny. V JSR 352 je JSL specifikován v souboru XML známém jako soubor XML úlohy. Úloha je v podstatě kontejner obsahující kroky.
  • Krok: Krok je objekt domény, který obsahuje nezávislou, sekvenční fázi úlohy. Krok obsahuje veškerou potřebnou logiku a data k provedení vlastního zpracování. Definice kroku zůstává podle specifikace dávky vágní, protože obsah kroku je čistě specifický pro aplikaci a může být tak složitý nebo jednoduchý, jak si vývojář přeje. Existují dva druhy kroků: chunk a task oriented.
  • Job Operator: Poskytuje rozhraní pro správu všech aspektů zpracování úloh, což zahrnuje operační příkazy, jako je spuštění, restart a zastavení, a také příkazy úložiště úloh, jako je načtení úloh a provedení kroků.
  • Úložiště úloh: Obsahuje informace o aktuálně spuštěných úlohách a historická data o úloze. JobOperator Poskytuje rozhraní API pro přístup k tomuto úložišti. JobRepository Může být implementováno pomocí, databáze nebo souborového systému.

Následující část pomůže pochopit některé běžné znaky dávkové architektury.

1.3 Kroky v úloze

Krok je nezávislá fáze úlohy. Jak bylo uvedeno výše, v Jobu existují dva typy kroků. Oba typy se pokusíme podrobněji pochopit níže.

1.3.1 Kroky orientované na chunk

Kroky orientované na chunk načtou a zpracují jednu položku najednou a výsledky seskupí do chunku. Výsledky jsou pak uloženy, když chunk dosáhne předem definované velikosti. Chunk-oriented processing zefektivňuje ukládání výsledků, pokud je soubor dat obrovský. Obsahuje tři části:

  • Čtečka položek čte vstupní data jedno po druhém ze zdroje dat, kterým může být databáze, plochý soubor, soubor protokolu atd.
  • Zpracovatel zpracuje data jedno po druhém na základě definované obchodní logiky.
  • Zápisovatel zapisuje data po částech. Velikost chunků je předdefinována a je konfigurovatelná

Součástí kroků chunků jsou kontrolní body, které poskytují frameworku informace o dokončení chunků. Pokud během zpracování chunku dojde k chybě, může se proces znovu spustit na základě posledního kontrolního bodu.

1.3.2 Kroky zaměřené na úlohy

Provádí jiné úlohy než zpracování položek ze zdroje dat. Což zahrnuje vytváření nebo odstraňování adresářů, přesouvání souborů, vytváření nebo odstraňování databázových tabulek atd. Kroky orientované na úlohy obvykle nejsou ve srovnání s kroky orientovanými na části dlouhotrvající.

V běžném scénáři se kroky orientované na úlohy používají po krocích orientovaných na části, kde je třeba provést úklid. Například jako výstup aplikace získáváme soubory protokolu. Kroky orientované na části se používají ke zpracování dat a získání smysluplných informací ze souborů protokolu.

Krok orientovaný na úlohy se pak používá k vyčištění starších souborů protokolu, které již nejsou potřeba.

1.3.3 Paralelní zpracování

Dávkové úlohy často provádějí nákladné výpočetní operace a zpracovávají velké množství dat. Dávkové aplikace mohou využívat paralelní zpracování ve dvou scénářích.

  • Kroky, které jsou svou povahou nezávislé, mohou běžet v různých vláknech.
  • Kroky orientované na části, kde zpracování každé položky je nezávislé na výsledcích zpracování předchozích položek, mohou běžet ve více vláknech.

Dávkové zpracování pomáhá rychleji dokončit úlohy a provádět operace pro obrovská data.

Nástroje a technologie

Podívejme se na technologie a nástroje použité při sestavování programu.

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

Struktura projektu

Struktura projektu bude vypadat podle obrázku níže.

Struktura projektu pro Java Batch

Výše uvedená struktura projektu využívá Gradle. Tento projekt lze také vytvořit pomocí Mavenu a soubor build.gralde se nahradí souborem pom.xml. Struktura projektu se mírně odsune s použitím Mavenu pro sestavení.

Cíl programu

V rámci programu se pokusíme vytvořit jednoduchou dávkovou aplikaci java pomocí spring boot. Tato aplikace bude provádět následující úlohy.

  1. Čtení: – Čtení dat o zaměstnancích ze souboru CSV.
  2. Zpracování dat: –
  3. Převede data zaměstnanců na všechna velká písmena.
  4. Zapíše: –

4.1 Gradle build

Pro sestavení používáme Gradle jako součást programu. Soubor build.gradle bude vypadat podle následujícího obrázku.

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

V uvedeném souboru build.gradle apply plugin: 'java' nám říká, jaký zásuvný modul je třeba nastavit. Pro nás je to plugin Java.
repositories{} nám sděluje repozitář, ze kterého má být závislost stažena. My jsme zvolili mavenCentral pro stažení závislosti jars. Pro natažení příslušných závislých jarů můžeme použít také jcenter.

dependencies {} tag slouží k zadání potřebných údajů o jar souborech, které mají být pro projekt nataženy. apply plugin: 'org.springframework.boot' tento zásuvný modul se používá pro zadání projektu spring-boot. boot jar{} určí vlastnosti jar, který se vygeneruje ze sestavení.

4.2 Vzorový datový soubor

Pro poskytnutí dat pro fázi čtení použijeme soubor CSV obsahující údaje o zaměstnancích.

Soubor bude vypadat podle následujícího obrázku.

Vzorový soubor CSV

John,FosterJoe,ToyJustin,TaylorJane,ClarkJohn,Steve

Vzorový datový soubor obsahuje jméno a příjmení zaměstnance. Stejná data použijeme pro zpracování a následné vložení do databáze.

4.3 SQL skripty

Používáme databázi HSQL, což je paměťová databáze. Skript bude vypadat podle následujícího obrázku.

SQL skript

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 se spouští schema-@@platform@@.sql automaticky při startu. -all Je to výchozí nastavení pro všechny platformy. Vytvoření tabulky tedy proběhne samo při spuštění aplikace a bude k dispozici, dokud nebude aplikace spuštěna.

4.4 Modelová třída

Vytvoříme třídu Employee.java jako modelovou třídu. Třída bude vypadat podle obrázku níže.

Třída modelu pro program

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 slouží k přepsání výchozí implementace metody toString().

4.5 Konfigurační třída

Vytvoříme třídu BatchConfiguration.java, která bude konfigurační třídou pro dávkové zpracování. Soubor java bude vypadat podle následujícího obrázku.

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 anotace slouží k povolení dávkového zpracování.
JobBuilderFactory je továrna, která slouží k sestavení úlohy.
StepBuilderFactory slouží k vytvoření kroku.
Metoda step1() má vlastnost chunk(). Jedná se o vlastnost, která se používá pro rozdělení vstupu na definovanou velikost. Pro nás je tato velikost 10.

4.6 Zpracovatel položek

Zpracovatel položek je rozhraní, které bude zodpovědné za zpracování dat. Toto rozhraní budeme implementovat v EmployeeItemProcessor.java. Třída java bude vypadat podle následujícího obrázku.

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

V metodě process() budeme získávat data a budeme je transformovat na velký název.

4.7 Třída JobExecutionSupportListener

JobExecutionListenerSupport je rozhraní, které bude oznamovat dokončení úlohy. Součástí rozhraní je metoda afterJob. Tato metoda slouží k oznamování dokončení úlohy.

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

V této metodě získáme data z databáze po dokončení úlohy a výsledek vypíšeme na konzolu, abychom ověřili zpracování, které bylo s daty provedeno.

4.8 Aplikační třída

Vytvoříme aplikační třídu, která bude obsahovat hlavní metodu zodpovědnou za spouštění dávkového programu v jazyce Java. Třída bude vypadat podle následujícího obrázku.

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

@SpringBootApplicationje anotace, která slouží k určení programu jako spring boot programu.

Output

Spustíme aplikaci jako javovou aplikaci. Na konzole získáme následující výstup.

Výstup programu JavaBatch

Výstup je velmi přehledně dostupný. Úloha začíná importUserJob, poté se spustí krok-1, kde se převádí načtená data na velká písmena.

Po zpracování kroku vidíme na konzoli výsledek s velkými písmeny.

Shrnutí

V tomto tutoriálu jsme se naučili následující věci:

  1. Dávka Java obsahuje úlohy, které mohou obsahovat více kroků.
  2. Každý krok je kombinací čtení, zpracování, zápisu.
  3. Při zpracování můžeme data rozdělit na různé velikosti.

7. Stáhněte si projekt Eclipse

Tento výukový kurz byl určen pro JavaBatch se SpringBootem.