Ich habe zusammengefasst, was ich in Spring Batch gelernt habe, also werde ich einen Artikel schreiben. In Bezug auf die Umgebungskonstruktion von Spring Batch habe ich sie basierend auf dem folgenden Artikel erstellt.
Als ich versuchte, es in der Umgebung auszuführen, die ich beim Erstellen der Umgebung mit Bezug auf verschiedene Sites erstellt hatte, war ich wütend, dass es keine Tabelle wie "BATCH_JOB_EXECUTION" gab. Es scheint notwendig zu sein, eine dedizierte Tabelle vorzubereiten, um Spring Batch auszuführen. Unten finden Sie die erforderlichen Tabellen.
Es scheint jedoch schwierig zu sein, diese selbst einzufügen. Daher bereitet der Frühling SQL für verschiedene Plattformen vor. Wenn Sie nach "spring batch schema-〇〇 (Plattformname) .sql" suchen, werden Sie einen Treffer finden. Ich habe postgresql verwendet, also habe ich "shema-postgresql.sql" verwendet. Ich bezog mich auf Folgendes.
・ [Schema-postgresql.sql](https://github.com/spring-projects/spring-batch/blob/master/spring-batch-core/src/main/resources/org/springframework/batch/core/schema -postgresql.sql)
Ich denke, dass Sie die App viele Male starten müssen, um den Betrieb der App zu überprüfen, aber ich denke, es ist schwierig, jedes Mal Daten zu erstellen. Daher werden wir jedes Mal die Tabelle initialisieren. Spring Boot bietet einen Mechanismus zum Initialisieren der Tabelle, wenn die Anwendung ausgeführt wird. Ich habe auf die folgende Seite verwiesen.
Es scheint, dass es automatisch gestartet wird, wenn Sie "schema-〇〇.sql" unter "src / main / resources" platzieren, das beim Erstellen des Spring Boot-Projekts erstellt wurde. Ich habe postgresql verwendet, also habe ich Folgendes getan:
application.properties
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
#spring.datasource.username=postgres
#spring.datasource.password=postgres
spring.datasource.initialization-mode=always
Es scheint, dass SpringBoot automatisch Beans für DataSource definiert und Ingenieure nur die DB-Einstellungen in application.properties beschreiben müssen.
Zusätzlich zu dem, was ich in "[Ich werde wütend, wenn es keine Tabelle für Spring Batch gibt](https://qiita.com/kyabetsuda/items/f011533621cff7f53c63# Ich werde wütend, wenn es keine Tabelle für Spring Batch gibt)" bestätigt habe, ist es wie folgt. SQL ist vorbereitet.
schema-all.sql
DROP TABLE IF EXISTS people;
CREATE TABLE people (
person_id SERIAL NOT NULL PRIMARY KEY,
first_name VARCHAR(20),
last_name VARCHAR(20)
);
-- Autogenerated: do not edit this file
DROP TABLE IF EXISTS BATCH_STEP_EXECUTION_CONTEXT;
DROP TABLE IF EXISTS BATCH_JOB_EXECUTION_CONTEXT;
DROP TABLE IF EXISTS BATCH_STEP_EXECUTION;
DROP TABLE IF EXISTS BATCH_JOB_EXECUTION_PARAMS;
DROP TABLE IF EXISTS BATCH_JOB_EXECUTION;
DROP TABLE IF EXISTS BATCH_JOB_INSTANCE;
DROP SEQUENCE IF EXISTS BATCH_STEP_EXECUTION_SEQ ;
DROP SEQUENCE IF EXISTS BATCH_JOB_EXECUTION_SEQ ;
DROP SEQUENCE IF EXISTS BATCH_JOB_SEQ ;
CREATE TABLE BATCH_JOB_INSTANCE (
JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY ,
VERSION BIGINT ,
JOB_NAME VARCHAR(100) NOT NULL,
JOB_KEY VARCHAR(32) NOT NULL,
constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY)
) ;
CREATE TABLE BATCH_JOB_EXECUTION (
JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY ,
VERSION BIGINT ,
JOB_INSTANCE_ID BIGINT NOT NULL,
CREATE_TIME TIMESTAMP NOT NULL,
START_TIME TIMESTAMP DEFAULT NULL ,
END_TIME TIMESTAMP DEFAULT NULL ,
STATUS VARCHAR(10) ,
EXIT_CODE VARCHAR(2500) ,
EXIT_MESSAGE VARCHAR(2500) ,
LAST_UPDATED TIMESTAMP,
JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL,
constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID)
references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
) ;
CREATE TABLE BATCH_JOB_EXECUTION_PARAMS (
JOB_EXECUTION_ID BIGINT NOT NULL ,
TYPE_CD VARCHAR(6) NOT NULL ,
KEY_NAME VARCHAR(100) NOT NULL ,
STRING_VAL VARCHAR(250) ,
DATE_VAL TIMESTAMP DEFAULT NULL ,
LONG_VAL BIGINT ,
DOUBLE_VAL DOUBLE PRECISION ,
IDENTIFYING CHAR(1) NOT NULL ,
constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;
CREATE TABLE BATCH_STEP_EXECUTION (
STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY ,
VERSION BIGINT NOT NULL,
STEP_NAME VARCHAR(100) NOT NULL,
JOB_EXECUTION_ID BIGINT NOT NULL,
START_TIME TIMESTAMP NOT NULL ,
END_TIME TIMESTAMP DEFAULT NULL ,
STATUS VARCHAR(10) ,
COMMIT_COUNT BIGINT ,
READ_COUNT BIGINT ,
FILTER_COUNT BIGINT ,
WRITE_COUNT BIGINT ,
READ_SKIP_COUNT BIGINT ,
WRITE_SKIP_COUNT BIGINT ,
PROCESS_SKIP_COUNT BIGINT ,
ROLLBACK_COUNT BIGINT ,
EXIT_CODE VARCHAR(2500) ,
EXIT_MESSAGE VARCHAR(2500) ,
LAST_UPDATED TIMESTAMP,
constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID)
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;
CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT (
STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
SHORT_CONTEXT VARCHAR(2500) NOT NULL,
SERIALIZED_CONTEXT TEXT ,
constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
) ;
CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT (
JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
SHORT_CONTEXT VARCHAR(2500) NOT NULL,
SERIALIZED_CONTEXT TEXT ,
constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;
CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ MAXVALUE 9223372036854775807 NO CYCLE;
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ MAXVALUE 9223372036854775807 NO CYCLE;
CREATE SEQUENCE BATCH_JOB_SEQ MAXVALUE 9223372036854775807 NO CYCLE;
Jetzt werden die für die Ausführung erforderlichen Tabellen nach jeder Ausführung initialisiert.
Spring Batch verwendet grundsätzlich die Klassen Reader, Processor und Writer für die Verarbeitung, aber als ich diese selbst definierte, trat eine Endlosschleife auf. Ich hatte viele Probleme, aber die folgende Seite war hilfreich
Spring Batch scheint sich zu wiederholen, bis ItemReader null zurückgibt. Daher muss ItemReader so konzipiert und implementiert werden, dass nach Abschluss der Verarbeitung null zurückgegeben wird. Es wurde auf diese Weise auf den folgenden Websites implementiert.
Das Folgende wird unter Bezugnahme auf die obige Site implementiert.
PersonItemReader.java
import java.util.List;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.beans.factory.annotation.Autowired;
public class PersonItemReader implements ItemReader<Person>{
private List<Person> people = null;
private int nextIndex;
private final PersonService service;
public PersonItemReader(PersonService service) {
this.service = service;
}
@Override
public Person read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
if (people == null) {
people = service.selectAll();
nextIndex = 0;
}
Person person = null;
if (nextIndex < people.size()) {
person = people.get(nextIndex);
nextIndex++;
}
return person;
}
}
Der definierte ItemReader ist eine Bean-Definition.
BatchConfiguration.java
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
...(Kürzung)...
@Autowired
PersonService personService;
@Bean
public PersonItemReader reader() {
return new PersonItemReader(personService);
}
...(Kürzung)...
}
In Spring Batch werden häufig Reader, Prozessor und Writer verwendet, es gibt jedoch für jede Methode allgemeine Implementierungsmethoden. Ich denke, es ist wie ein Designmuster. Unten finden Sie eine Beschreibung der Implementierungsmethode, die ich durchgeführt habe. Erstens ist die Reader-Klasse.
PersonItemReader.java
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
public class PersonItemReader implements ItemReader<List<Person>>{
private final PersonService service;
private final PersonCheckService checkService;
public PersonItemReaderForTest(PersonService service, PersonCheckService checkService) {
this.service = service;
this.checkService = checkService;
}
@Override
public List<Person> read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
List<Person> people = service.selectAll();
List<Person> ret = null;
for(Person person : people) {
if(checkService.check(person)) {
if(ret == null)
ret = new ArrayList<Person>();
ret.add(person);
}
}
return ret;
}
}
Die von der DB erhaltene wird in Form einer Liste an den Prozessor übergeben. Als nächstes kommt die Prozessorklasse.
PersonItemProcessor.java
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.item.ItemProcessor;
public class PersonItemProcessor implements ItemProcessor<List<Person>, List<Person>> {
@Override
public List<Person> process(final List<Person> people) throws Exception {
List<Person> transformedPeople = new ArrayList<Person>();
for(Person person : people) {
final String firstName = person.getFirstName().toUpperCase();
final String lastName = person.getLastName().toUpperCase();
final Person transformedPerson = new Person(firstName, lastName);
transformedPeople.add(transformedPerson);
}
return transformedPeople;
}
}
Die vom Reader übergebene Liste wird verarbeitet und eine neue Liste zurückgegeben. Als nächstes kommt die Writer-Klasse.
PersonItemWriter.java
import java.util.List;
import org.springframework.batch.item.ItemWriter;
public class PersonItemWriterForTest implements ItemWriter<Object>{
PersonService service;
public PersonItemWriterForTest(PersonService service) {
this.service = service;
}
@Override
public void write(List<? extends Object> items) throws Exception {
List<Person> people = (List<Person>) items.get(0);
for(Person person : people) {
service.updatePerson(person);
}
}
}
In der Writer-Klasse wird die vom Prozessor übergebene Liste in der Datenbank registriert. Bemerkenswert ist jedoch der folgende Code in der Writer-Klasse
List<Person> people = (List<Person>) items.get(0);
Um List
Das Folgende ist auf der obigen Seite geschrieben.
Typically, the design pattern is:
Reader -> reads something, returns ReadItem Processor -> ingests ReadItem, returns ProcessedItem Writer -> ingests List<ProcessedItem>
If your processor is returning List<Object>, then you need your Writer to expect List<List<Object>>.
In der Regel lautet das Entwurfsmuster Reader, Processor gibt ein einzelnes Element zurück und Writer verarbeitet eine Liste von Elementen. Da die Stapelverarbeitung durchgeführt wird, dachte ich, dass es normal wäre, mehrere von der Datenbank erfasste Daten in Form einer Liste an den Prozessor zu übergeben. In Writer scheint es jedoch so zu sein, dass der Prozessor das zurückgegebene Objekt empfangen kann, wenn es in der Liste gespeichert ist. Wenn Sie ein einzelnes Objekt mit Processor zurückgeben, ist es daher nicht erforderlich, die obige Verarbeitung von items.get (0) durchzuführen. Der Reader verfügt auch über eine Implementierungsmethode, die ein einzelnes Objekt zurückgibt, wie in "[Job-Schleifen unendlich](https://qiita.com/kyabetsuda/items/f011533621cff7f53c63#Job-Schleifen unendlich)" eingeführt. Klingt üblich.
Spring Batch bietet einen Mechanismus zum Testen von Jobs und Schritten. Verwenden Sie JobLauncherTestUtils zum Testen. Ich habe auf die folgende Seite verwiesen.
Definieren Sie zunächst eine Bean für die Verwendung von JobLauncherTestUtils.
BatchConfigurationForTest.java
import javax.sql.DataSource;
import org.springframework.batch.core.Job;
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.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
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.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
@Configuration
@EnableBatchProcessing
public class BatchConfigurationForTest {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public StepBuilderFactory stepBuilderFactory;
@Autowired
JobLauncher jobLauncher;
@Autowired
JobRepository jobRepository;
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://localhost:5432/ec");
// dataSource.setUsername(username);
// dataSource.setPassword(password);
return dataSource;
}
@Bean
PersonService personService() {
return new PersonService();
};
@Bean
public PersonItemReader reader() {
return new PersonItemReader(personService());
}
@Bean
public PersonItemProcessor processor() {
return new PersonItemProcessor();
}
@Bean
public JdbcBatchItemWriter<Person> writer(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<Person>()
.itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
.sql("INSERT INTO people (first_name, last_name) VALUES (:firstName, :lastName)")
.dataSource(dataSource)
.build();
}
@Bean
public NoWorkFoundStepExecutionListener noWorkFoundStepExecutionListener() {
return new NoWorkFoundStepExecutionListener();
}
@Bean
public Job importUserJob(Step step1) {
return jobBuilderFactory.get("importUserJob")
.incrementer(new RunIdIncrementer())
.flow(step1)
.end()
.build();
}
@Bean
public Step step1(NoWorkFoundStepExecutionListener listener, JdbcBatchItemWriter<Person> writer) {
return stepBuilderFactory.get("step1")
.<Person, Person> chunk(1)
.reader(reader())
.processor(processor())
.writer(writer)
.listener(listener)
.build();
}
@Bean
public JobLauncherTestUtils jobLauncherTestUtils() {
JobLauncherTestUtils utils = new JobLauncherTestUtils();
utils.setJob(importUserJob(step1(noWorkFoundStepExecutionListener(), writer(dataSource()))));
utils.setJobLauncher(jobLauncher);
utils.setJobRepository(jobRepository);
return utils;
}
}
JobLauncherTestUtils wird unten als Bean definiert. Das Obige ist eine neue Definition der Konfigurationsklasse zum Testen. Der Inhalt selbst ändert sich nicht wesentlich von der Konfigurationsklasse. In SpringBatch können Sie nach dem Schritt einen "Listener" festlegen und etwas tun, aber Sie können den Listener auch testen, sodass wir gemeinsam eine Bean definieren (die Lister-Klasse wird auf der obigen Referenzseite beschrieben). Es ist gewesen). Als nächstes kommt die Testklasse. Erstens ist die Klasse, die den Job testet.
JobTest.java
import static org.hamcrest.CoreMatchers.*;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=BatchConfigurationForTest.class)
public class JobTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Test
public void testJob() throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
Assert.assertThat("COMPLETED", is(jobExecution.getExitStatus().getExitCode()));
}
}
In der Testklasse ist das Bean-definierte JobLauncherTestUtils Autowired. Der Job wird von launchJob ausgeführt. Welcher Job ausgeführt werden soll, wird bei der Definition der Bean angegeben.
Als nächstes folgt die Klasse, die die Schritte testet.
StepTest.java
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.MetaDataInstanceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=BatchConfigurationForTest.class)
public class StepTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
NoWorkFoundStepExecutionListener tested;
@Test
public void testStep() {
JobExecution jobExecution = jobLauncherTestUtils.launchStep("step1");
Assert.assertThat("COMPLETED", is(jobExecution.getExitStatus().getExitCode()));
}
@Test
public void testAfterStep() {
StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution();
stepExecution.setExitStatus(ExitStatus.COMPLETED);
stepExecution.setReadCount(0);
ExitStatus exitStatus = tested.afterStep(stepExecution);
assertThat(ExitStatus.FAILED.getExitCode(), is(exitStatus.getExitCode()));
}
}
Übergeben Sie beim Testen eines Schritts den Namen des auszuführenden Schritts im Argument von launchStep (). testAfterStep () testet den Bean-definierten Listener. setReadCount () gibt die Anzahl der von der Reader-Klasse gelesenen Elemente an. Der auf der Referenzsite beschriebene NoWorkFoundStepExecutionListener wird implementiert, um ExisStatus.FAILED zurückzugeben, wenn getReadCount () == 0.
Dies ist das Ende der Spring Batch-Zusammenfassung.
Recommended Posts