J'ai résumé ce que j'ai appris dans Spring Batch, je vais donc écrire un article. En ce qui concerne la construction de l'environnement de Spring Batch, je l'ai fait sur la base de l'article suivant.
Quand j'ai essayé de l'exécuter dans l'environnement que j'ai construit lorsque j'ai construit l'environnement en référence à divers sites, j'étais en colère qu'il n'y avait pas de table telle que "BATCH_JOB_EXECUTION". Il semble qu'il soit nécessaire de préparer une table dédiée pour exécuter Spring Batch. Voir ci-dessous pour les tableaux requis.
Cependant, il semble difficile de les insérer vous-même. Par conséquent, Spring prépare SQL pour diverses plates-formes. Si vous recherchez "spring batch schema-〇〇 (nom de la plateforme) .sql", vous trouverez un hit. J'ai utilisé postgresql, donc j'ai utilisé "shema-postgresql.sql". J'ai évoqué ce qui suit.
・ [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)
Je pense que vous devrez démarrer l'application plusieurs fois pour vérifier le fonctionnement de l'application, mais je pense qu'il est difficile de créer des données à chaque fois. Par conséquent, nous répondrons en initialisant la table à chaque fois. Spring Boot fournit un mécanisme pour initialiser la table lorsque l'application est exécutée. Je me suis référé au site suivant.
Il semble que si vous placez "schema-〇〇.sql" sous "src / main / resources" créé lors de la création du projet Spring Boot, il démarrera automatiquement. J'ai utilisé postgresql, donc j'ai fait ce qui suit:
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
Il semble que SpringBoot définit automatiquement un bean pour DataSource et que l'ingénieur n'a besoin que de décrire le paramètre DB dans application.properties.
En plus de ce que j'ai confirmé dans "[Je me fâche s'il n'y a pas de table pour Spring Batch](https://qiita.com/kyabetsuda/items/f011533621cff7f53c63# je me fâche s'il n'y a pas de table pour Spring Batch)", c'est comme suit. SQL est préparé.
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;
Désormais, les tables nécessaires à l'exécution sont initialisées après chaque exécution.
Spring Batch utilise essentiellement les classes Reader, Processor et Writer pour le traitement, mais lorsque je les ai définis moi-même, une boucle infinie s'est produite. J'ai eu beaucoup de problèmes, mais le site suivant a été utile
Spring Batch semble boucler jusqu'à ce que ItemReader renvoie null. Par conséquent, il semble qu'ItemReader doit être conçu et implémenté pour que null soit retourné lorsque le traitement est terminé. Il a été mis en œuvre de cette façon sur les sites suivants.
Ce qui suit est implémenté en référence au site ci-dessus.
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;
}
}
Le ItemReader défini est une définition de Bean.
BatchConfiguration.java
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
...(réduction)...
@Autowired
PersonService personService;
@Bean
public PersonItemReader reader() {
return new PersonItemReader(personService);
}
...(réduction)...
}
Dans Spring Batch, il est courant d'utiliser Reader, Processor et Writer, mais il existe des méthodes d'implémentation générales pour chacun. Je pense que c'est comme un modèle de conception. Vous trouverez ci-dessous une description de la méthode de mise en œuvre que j'ai effectuée. Le premier est la classe Reader.
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;
}
}
Celui obtenu de la base de données est passé au processeur sous forme de liste. Vient ensuite la classe Processor.
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;
}
}
La liste transmise par Reader est traitée et une nouvelle liste est renvoyée. Vient ensuite la classe Writer.
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);
}
}
}
Dans la classe Writer, la liste transmise par le processeur est enregistrée dans la base de données. Mais il convient de noter le code suivant dans la classe Writer
List<Person> people = (List<Person>) items.get(0);
Pour obtenir List
Ce qui suit est écrit sur le site ci-dessus.
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>>.
En règle générale, le modèle de conception est Reader, le processeur renvoie un seul élément et Writer traite une liste d'éléments. Étant donné que le traitement par lots est effectué, j'ai pensé qu'il serait normal de transmettre plusieurs données acquises de la base de données au processeur sous forme de liste. Cependant, dans Writer, il semble que le processeur puisse recevoir l'objet retourné tel qu'il est stocké dans la liste. Par conséquent, en renvoyant un seul objet avec Processor, il n'est pas nécessaire d'effectuer le traitement ci-dessus de items.get (0). Reader a également une méthode d'implémentation qui renvoie un seul objet comme présenté dans "[Le travail se boucle infiniment](https://qiita.com/kyabetsuda/items/f011533621cff7f53c63#Job boucle infiniment)". Cela semble commun.
Spring Batch fournit un mécanisme pour tester les tâches et les étapes. Utilisez JobLauncherTestUtils pour tester. Je me suis référé au site suivant.
Tout d'abord, définissez un bean pour utiliser 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 est défini comme un bean en bas. Ce qui précède est une nouvelle définition de la classe Configuration pour les tests. Le contenu lui-même ne change pas beaucoup de la classe Configuration. Dans SpringBatch, vous pouvez définir un "écouteur" après l'étape et faire quelque chose, mais vous pouvez également tester l'écouteur, nous définissons donc un bean ensemble (la classe lister est décrite dans le site de référence ci-dessus) Ça a été). Vient ensuite la classe de test. La première est la classe qui teste le travail.
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()));
}
}
Dans la classe de test, le JobLauncherTestUtils défini par Bean est Autowired. Le travail est exécuté par launchJob. La tâche à exécuter est spécifiée lorsque le bean est défini.
Vient ensuite la classe qui teste les étapes.
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()));
}
}
Lors du test d'une étape, passez le nom de l'étape à exécuter dans l'argument de launchStep (). testAfterStep () teste l'écouteur défini par le bean. setReadCount () représente le nombre d'éléments lus par la classe Reader. Le NoWorkFoundStepExecutionListener décrit sur le site de référence est implémenté pour renvoyer ExisStatus.FAILED lorsque getReadCount () == 0.
C'est la fin du résumé de Spring Batch.
Recommended Posts