Java Batch Tutorial

W dzisiejszym świecie Internet zmienił sposób w jaki żyjemy i jednym z głównych powodów jest użycie Internetu do większości codziennych zadań. To prowadzi do ogromnej ilości danych dostępnych do przetwarzania.

Some z przykładów, gdzie ogromne dane są zaangażowane są przetwarzania payslips, wyciągi bankowe, odsetki obliczeń, itp. Więc wyobraź sobie, jeśli wszystkie te zadania musiały być wykonane ręcznie, to zajmie wieki, aby zakończyć te zadania.

Jak to się robi w obecnym wieku? Odpowiedź jest Batch Processing.

Wprowadzenie

Przetwarzanie wsadowe jest wykonywane na danych masowych, bez ręcznej interwencji, i długotrwałe. Może być intensywne pod względem danych lub obliczeń. Zadania wsadowe mogą być uruchamiane według predefiniowanego harmonogramu lub mogą być inicjowane na żądanie. Ponadto, ponieważ zadania wsadowe są zazwyczaj zadaniami długotrwałymi, ciągłe kontrole i ponowne uruchamianie od pewnego niepowodzenia są wspólnymi cechami występującymi w zadaniach wsadowych.

1.1 Historia przetwarzania wsadowego Java

Przetwarzanie wsadowe dla platformy Java zostało wprowadzone jako część specyfikacji JSR 352, część platformy Java EE 7, definiuje model programowania dla aplikacji wsadowych plus runtime do uruchamiania i zarządzania zadaniami wsadowymi.

1.2 Architektura Java Batch

Niższy diagram pokazuje podstawowe komponenty dla przetwarzania wsadowego.

Architektura przetwarzania wsadowego w Javie

Architektura aplikacji wsadowych rozwiązuje problemy przetwarzania wsadowego, takie jak zadania, kroki, repozytoria, wzorce zapisu procesora czytnika, kawałki, punkty kontrolne, przetwarzanie równoległe, przepływ, ponawianie prób, sekwencjonowanie, partycjonowanie itp.

Zrozummy przepływ architektury.

  • Repozytorium zadań zawiera zadania, które muszą zostać uruchomione.
  • JobLauncher wyciąga zadanie z repozytorium zadań.
  • Każde zadanie zawiera kroki. Kroki te to ItemReader, ItemProcessor i ItemWriter.
  • Item Reader jest tym, który odczytuje dane.
  • Item Process jest tym, który przetwarza dane w oparciu o logikę biznesową.
  • Pisarz elementów zapisze dane z powrotem do zdefiniowanego źródła.

1.3 Komponenty przetwarzania wsadowego.

Postaramy się teraz szczegółowo zrozumieć komponenty przetwarzania wsadowego.

  • Zadanie: Zadanie obejmuje cały proces przetwarzania wsadowego. Zawiera ono jeden lub więcej kroków. Zadanie jest składane przy użyciu języka specyfikacji zadań (JSL), który określa kolejność, w jakiej kroki muszą być wykonywane. W JSR 352, JSL jest określony w pliku XML znanym jako plik XML zadania. Zadanie jest w zasadzie kontenerem przechowującym kroki.
  • Krok: Krok jest obiektem domeny, który zawiera niezależną, sekwencyjną fazę zadania. Krok zawiera całą niezbędną logikę i dane do wykonania rzeczywistego przetwarzania. Definicja kroku jest niejasna zgodnie ze specyfikacją partii, ponieważ zawartość kroku jest czysto zależna od aplikacji i może być tak złożona lub prosta, jak chce tego programista. Istnieją dwa rodzaje kroków: chunk i zorientowane na zadania.
  • Operator zadań: Zapewnia interfejs do zarządzania wszystkimi aspektami przetwarzania zadań, co obejmuje polecenia operacyjne, takie jak start, restart i stop, a także polecenia repozytorium zadań, takie jak pobieranie wykonań zadań i kroków.
  • Repozytorium zadań: Zawiera informacje o aktualnie uruchomionych zadaniach oraz dane historyczne dotyczące zadania. JobOperator zapewnia interfejsy API, aby uzyskać dostęp do tego repozytorium. Repozytorium JobRepository może być zaimplementowane przy użyciu, bazy danych lub systemu plików.

Poniższa sekcja pomoże w zrozumieniu niektórych typowych znaków architektury wsadowej.

1.3 Kroki w pracy

Krok jest niezależną fazą pracy. Jak omówiono powyżej, istnieją dwa rodzaje kroków w zadaniu. Postaramy się zrozumieć oba typy szczegółowo poniżej.

1.3.1 Kroki zorientowane na chunk

Kroki typu chunk będą czytać i przetwarzać jeden element na raz i grupować wyniki w chunk. Wyniki są następnie przechowywane, gdy chunk osiągnie predefiniowany rozmiar. Przetwarzanie zorientowane na chunk sprawia, że przechowywanie wyników jest bardziej efektywne, gdy zbiór danych jest ogromny. Składa się ono z trzech części.

  • Czytnik elementów odczytuje dane wejściowe jeden po drugim ze źródła danych, którym może być baza danych, plik płaski, plik dziennika itp.
  • Procesor przetwarza dane jeden po drugim na podstawie zdefiniowanej logiki biznesowej.
  • Wydawca zapisuje dane w kawałkach. Rozmiar kawałków jest predefiniowany i jest konfigurowalny

Jako część kroków kawałków, istnieją punkty kontrolne, które dostarczają informacji do frameworka o ukończeniu kawałków. Jeśli podczas przetwarzania chunków wystąpi błąd, proces może zostać ponownie uruchomiony na podstawie ostatniego punktu kontrolnego.

1.3.2 Kroki zorientowane na zadania

Wykonuje zadania inne niż przetwarzanie elementów ze źródła danych. Obejmuje to tworzenie lub usuwanie katalogów, przenoszenie plików, tworzenie lub usuwanie tabel bazy danych itp. Kroki zadań nie są zazwyczaj długotrwałe w porównaniu z krokami chunk.

W normalnym scenariuszu, kroki zorientowane na zadania są używane po krokach zorientowanych na chunk, gdzie jest potrzebne czyszczenie. Na przykład, otrzymujemy pliki dziennika jako wyjście z aplikacji. Kroki chunk są używane do przetwarzania danych i uzyskania sensownych informacji z plików dziennika.

Krok zadaniowy jest następnie używany do czyszczenia starszych plików dziennika, które nie są już potrzebne.

1.3.3 Przetwarzanie równoległe

Zadania wsadowe często wykonują kosztowne operacje obliczeniowe i przetwarzają duże ilości danych. Aplikacje wsadowe mogą korzystać z przetwarzania równoległego w dwóch scenariuszach.

  • Kroki, które mają charakter niezależny, mogą być uruchamiane na różnych wątkach.
  • Kroki zorientowane na kawałki, w których przetwarzanie każdego elementu jest niezależne od wyników przetwarzania poprzednich elementów, mogą być uruchamiane na więcej niż jednym wątku.

Przetwarzanie wsadowe pomaga kończyć zadania i wykonywać operacje szybciej dla ogromnych danych.

Narzędzia i technologie

Przyjrzyjrzyjmy się technologiom i narzędziem użytym do budowy programu.

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

Struktura projektu

Struktura projektu będzie wyglądać tak, jak pokazano na poniższym obrazku.

Struktura projektu dla Java Batch

Powyższa struktura projektu jest przy użyciu Gradle. Projekt ten może być również utworzony przy użyciu mavena, a plik build.gralde zostanie zastąpiony plikiem pom.xml. Struktura projektu ulegnie nieznacznemu przesunięciu przy użyciu Mavena do budowania.

Cel programu

W ramach programu, spróbujemy stworzyć prostą aplikację wsadową java przy użyciu Spring Boot. Aplikacja ta będzie wykonywała następujące zadania.

  1. Odczyt: – Odczyt danych pracowników z pliku CSV.
  2. Przetwarzanie danych: – Przekształć dane pracowników na wszystkie duże litery.
  3. Zapisz: – Zapisz przetworzone dane pracowników z powrotem do bazy danych.

4.1 Gradle build

Używamy Gradle do budowania jako części programu. Plik build.gradle będzie wyglądał jak pokazano poniżej.

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

W powyższym pliku build.gradle plik apply plugin: 'java' mówi nam o pluginie, który musi być ustawiony. Dla nas jest to plugin Java.
repositories{}pozwala nam poznać repozytorium, z którego ma zostać pobrana zależność. My wybraliśmy mavenCentral aby pobrać słoiki zależności. Możemy użyć jcenter również do wyciągnięcia odpowiednich słoików zależności.

dependencies {} tag jest używany do dostarczenia niezbędnych szczegółów plików jar, które powinny być wyciągnięte dla projektu. apply plugin: 'org.springframework.boot' ten plugin jest używany do określania projektu spring-boot. boot jar{} określi właściwości słoika, który zostanie wygenerowany z kompilacji.

4.2 Przykładowy plik danych

W celu dostarczenia danych dla fazy odczytu, użyjemy pliku CSV zawierającego dane pracowników.

Plik będzie wyglądał jak pokazano poniżej.

Przykładowy plik CSV

John,FosterJoe,ToyJustin,TaylorJane,ClarkJohn,Steve

Przykładowy plik danych zawiera imię i nazwisko pracownika. Te same dane wykorzystamy do przetworzenia, a następnie wstawienia do bazy danych.

4.3 Skrypty SQL

Używamy bazy HSQL, która jest bazą danych opartą na pamięci. Skrypt będzie wyglądał tak, jak pokazano poniżej.

Skrypt 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 uruchamia schema-@@platform@@.sql automatycznie podczas startu. -all jest domyślne dla wszystkich platform. Tak więc tworzenie tabeli nastąpi samoistnie podczas uruchamiania aplikacji i będzie ona dostępna do momentu uruchomienia aplikacji.

4.4 Klasa modelu

Powstanie klasa Employee.java jako klasa modelu. Klasa będzie wyglądała jak na rysunku poniżej.

Klasa Model dla programu

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 służy do nadpisywania domyślnej implementacji metody toString().

4.5 Klasa Konfiguracyjna

Utworzymy klasę BatchConfiguration.java, która będzie klasą konfiguracyjną dla przetwarzania wsadowego. Plik java będzie wyglądał tak, jak pokazano poniżej.

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 adnotacja jest używana do włączania przetwarzania wsadowego.
JobBuilderFactory jest fabryką, która jest używana do budowania zadania.
StepBuilderFactory jest używana do tworzenia kroków.
Metoda step1() ma właściwość chunk(). Jest to właściwość używana do dzielenia danych wejściowych na określony rozmiar. Dla nas rozmiar ten wynosi 10.

4.6 Item Processor

Item processor to interfejs, który będzie odpowiedzialny za przetwarzanie danych. Interfejs ten zaimplementujemy w EmployeeItemProcessor.java. Klasa java będzie wyglądała jak na rysunku poniżej.

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

W metodzie process() będziemy pobierać dane i będziemy je przekształcać na nazwy pisane dużymi literami.

4.7 Klasa JobExecutionSupportListener

JobExecutionListenerSupport to interfejs, który będzie powiadamiał o zakończeniu zadania. Jako część interfejsu, mamy metodę afterJob. Metoda ta jest używana do wysyłania powiadomienia o zakończeniu zadania.

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

W tej metodzie pobieramy dane z bazy danych po zakończeniu zadania i drukujemy wynik na konsoli, aby zweryfikować przetwarzanie, które zostało wykonane na danych.

4.8 Klasa aplikacji

Utworzymy klasę aplikacji, która będzie zawierała główną metodę odpowiedzialną za uruchomienie programu wsadowego java. Klasa będzie wyglądała jak na rysunku poniżej.

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

@SpringBootApplicationjest adnotacją używaną do określenia programu jako programu Spring Boot.

Wyjście

Wykonajmy aplikację jako aplikację Java. Otrzymamy następujące wyjście na konsoli.

Wyjście programu JavaBatch

Przebieg pracy programu wsadowego jest bardzo wyraźnie dostępny na wyjściu. Zadanie rozpoczyna się od importUserJob, następnie rozpoczyna się wykonanie kroku 1, gdzie konwertuje odczytane dane na duże litery.

Po przetworzeniu kroku, możemy zobaczyć wynik dużych liter na konsoli.

Podsumowanie

W tym samouczku, nauczyliśmy się następujących rzeczy:

  1. Java batch zawiera zadania, które mogą zawierać wiele kroków.
  2. Każdy krok jest kombinacją odczytu, przetwarzania, zapisu.
  3. Możemy podzielić dane na różne rozmiary do przetwarzania.

7. Pobierz projekt Eclipse

To był samouczek dla JavaBatch z SpringBoot.

W tym tutorialu dowiedzieliśmy się następujących rzeczy