1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Batchを基本からまとめてみた【入門】

Last updated at Posted at 2024-08-27

Spring Bootの概要及び必要知識

(1)Maven

  • javaプログラムをコンパイル・テスト・パッケージング・デプロイを行うツール
  • 使用するJarライブラリ群の依存性を管理
  • pom.xmlという設定ファイルに、プロジェクトの情報、ライブラリ、プラグイン等を記述する

(2)Javaアノテーション

  • 『注釈』の意、アノテーション型
  • 補足として機能したり、フレームワークに意図を伝える(処理を指示)できる
  • フレームワークで用意されたアノテーションを使えるようになることが重要
  • アノテーションには引数を渡すことができる

(3)Dependency Injection

  • 一般的なjavaプログラムでは、あるクラスから他のクラスを呼び出す時にはnew(インスタンス化)する必要があり、これを依存性のある状態と呼ぶ
    - インスタンスの生成をSpring Boot(DIコンテナ)にまかせ、インスタンスの生成、参照の解決を自動で行ってもらう事を指す。依存性をDIコンテナが管理し、適用してくれる
    - @Autowaired というアノテーションを記述するだけで、Spring Boot管理下にある他のクラスを呼び出せる

Spring Batchの概要

  • 大規模なデータ処理を行う、バッチ処理向けのフレームワーク
  • Spring Frameworkの一部として提供され、Springの機能を利用して効率的にバッチ処理を開発出来る
  • javaでバッチを開発する場合のデファクトスタンダート

※ バッチ処理とは、あらかじめ一連の処理手順を登録しておき、自動的に処理を行う方式のこと

Spring Batch 全体概要

JobLauncher  ⇨  Job Repository ⇨ Database

      ⇩
      
Job (一連のステップの集合)  ⇨   Job Repository ⇨ Database
     
      ⇩
      
Step (バッチ処理の単位)    ⇨   Tasklet            
                        ⇨   Platform Transaction Manager ⇨ Database

      ⇩       
      
Chunk 『Item Reader』『Item Processor』『Item Writer』
  • JobLauncher : ジョブの実行を管理(ジョブを起動して、ジョブ全体の成功または失敗を判定)
  • Job : バッチ処理の単位(顧客データの取り込みや月次レポートの作成)
  • Step : ジョブ内で実行される処理の単位(処理内容を記述するタスクレッドとチャンクと呼び出す)
  • tasklet : タスクレッドはステップ内で実行される処理の最小単位(具体的な処理内容を記述)
  • Chunk : 『Item Reader』『Item Processor』『Item Writer』を組み合わせてデータ処理を行う
  • Item Reader : データを読み込む
  • Item Processor : 読み込んだデータを処理する
  • Item Writer : 処理を書き込む
  • Job Repository : ジョブの実行履歴を保存する
  • Platform Transaction Manager : トランザクションの境界を定義してロールバックコミットを制御する

Spring Batchを設定する

(1)プロジェクトを作成する

公式:spring initializr

① ブラウザを開き、(https://start.spring.io/) を入力し、開く
② 下記を入力し、Dependenciesで下記を選択し、『GENERATE』ボタンを押す

  • Project: Maven

  • Language: Java

  • SpringBoot: 3.2.9

  • jar

  • 21

  • Group: com.udemy

  • Artifact: hello-spring-batch

  • Name : hello-spring-batch

  • Description: Demo projects for Spring Boot

  • Package name: com.udemy.hello

Dependencies

  • Spring Batch
  • PostgreSQL Driver
  • Lombok

(2)Spring Batch設定Configクラスを作成する

①config/SpringConfig.javaを作成する

  • Spring Batchでは、@Configurationを付与をした設定用のコンフィグクラスを作成する
  • バッチ処理の全体構成・設定を記述する
config/SpringConfig.java

package com.udemy.hello_spring_batch_2.config;

import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

// @Configurationアノテーションを付与する
@Configuration
public class SpringConfig {

    // JobLauncher/JobRepository/PlatformTransactionManagerのクラス変数及びコンストラクタによる設定を記載する
    private final JobLauncher jobLauncher;
    private final JobRepository jobRepository;
    private final PlatformTransactionManager transactionManager;

    public SpringConfig(JobLauncher jobLauncher, JobRepository jobRepository,
            PlatformTransactionManager transactionManager) {
        this.jobLauncher = jobLauncher;
        this.jobRepository = jobRepository;
        this.transactionManager = transactionManager;

    }
}

(3)tasklet/HelloTasklet1.javaを作成する

①tasklet/HelloTasklet1.javaを作成する

  • Taskletインターフェースを実装し、Taskletを定義する

※Taskletとは、Step内で実行される処理を記述するためのインターフェースで、execute()メソッドを実装することで、具体的な処理を記述する

tasklet/HelloTasklet1.java
package com.udemy.hello_spring_batch_2.tasklet;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;

//@Componentアノテーションによって、Spring Beanとして登録される

@Component("Hello Tasklet1")
// StepScopeアノテーションによって、インスタンスをステップごとに生成・破棄する
@StepScope
// Tasklet インターフェイスexecuteメソッドを実装する
public class HelloTasklet1 implements Tasklet {

   @Override
   public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
       System.out.println("Hello Tasklet1!!!");
       // RepeatStatus.FINISHEDは、executeメソッドが正常終了したことを示す
       return RepeatStatus.FINISHED;
   }
}       

②Taskletをバッチ設定クラスに定義する

  • 作成したTaskletはバッチ設定クラスに定義し、上位のStepから呼び出されるように準備する
config/SpringConfig.java
package com.udemy.hello_spring_batch_2.config;

import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

// @Configurationアノテーションを付与する
@Configuration
public class SpringConfig {

    // JobLauncher/JobRepository/PlatformTransactionManagerのクラス変数及びコンストラクタによる設定を記載する
    private final JobLauncher jobLauncher;
    private final JobRepository jobRepository;
    private final PlatformTransactionManager transactionManager;

    // @Autowiredアノテーション、@Qualifierアノテーションを使用して、『HelloTasklet1』と名前がついたBeanのインスタンスを自動で注入する
    @Autowired
    @Qualifier("HelloTasklet1")
    private Tasklet helloTasklet1;

    public SpringConfig(JobLauncher jobLauncher, JobRepository jobRepository,
            PlatformTransactionManager transactionManager) {
        this.jobLauncher = jobLauncher;
        this.jobRepository = jobRepository;
        this.transactionManager = transactionManager;

    }
}

③config/SpringConfig.javaにStepとjobを定義する

※ Jobとは、①バッチ処理の単位(顧客データ取込、月次レポート作成等)を表し、複数のStepをまとめて実行できる。②実行するステップの情報・バッチ処理にパラメータを設定した場合のパラメータチェックを設定できる。
※ Stepとは、Job内で実行される処理の単位を表し、Taskletもしくは、Chunkにおける各処理(読取・加工・書込)を設定する。

Spring Batchを実行する

  • 実行時にはSpring Batchの内部機能として、ジョブの実行状態・履歴等の情報がデータベースに格納される。

①application.propertiesにDB接続情報を記載する

application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/BatchDB?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.batch.jdbc.initialize-schema = ALWAYS

②tasklet/HelloTasklet1.javaにジョブに対してパラメータ(引数)を与える

  • JobExecutionContextは、Job実行中に共有されるデータストアで、Tasklet間で値を受け渡したい場合は、JobExecutionContextに値を保存し、別のTaskletで取得する。
tasklet/HelloTasklet1.java
package com.udemy.hello.tasklet;

import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component("HelloTasklet1")
@StepScope
@Slf4j
public class HelloTasklet1 implements Tasklet {
    // @Valueアノテーションを指定
    @Value("#{jobParameters['param1']}")
    private String param1;

    @Value("#{jobParameters['param2']}")
    private Integer param2;

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {

        log.info("Hello Tasklet1");
        log.info("param1={}", param1); // パラメータ埋め込み修正
        log.info("param2={}", param2); // パラメータ埋め込み修正

        // System.out.println("Hello Tasklet1");
        // System.out.println("param1=" + param1);
        // System.out.println("param2=" + param2);

        // JobExecutionContextを取得
        ExecutionContext jobContext = contribution.getStepExecution()
                .getJobExecution()
                .getExecutionContext();
        jobContext.put("jobKey1", "jobValue1");

        return RepeatStatus.FINISHED;
    }
}

③validator/HelloJobParametersValidator.javaを作成する

  • バラメータ検証を行うクラスを作成し、JobParametersValidatorインターフェイスを実装することで実現できる
validator/HelloJobParametersValidator.java
package com.udemy.hello.validator;

import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.JobParametersValidator;

public class HelloJobParametersValidator implements JobParametersValidator {

    @Override
    public void validate(JobParameters parameters)
            throws JobParametersInvalidException {
        // パラメータ1の値チェック
        String param1 = parameters.getString("param1");
        if (!param1.equals("DEV") &&
                !param1.equals("TEST") &&
                !param1.equals("PROD")) {
            throw new JobParametersInvalidException("param1:" + param1
                    + "DEV/TEST/PRODのいずれかを指定してください");
        }
        // パラメータ2の値チェック
        String param2 = parameters.getString("param2");
        // 数値であることをチェック
        try {
            Integer.parseInt(param2);
        } catch (Exception e) {
            throw new JobParametersInvalidException("param2=" + param2
                    + "paramsは数値を指定してください。");
        }
    }
}

④tasklet/HelloTasklet2.javaを作成する

tasklet/HelloTasklet2.java
package com.udemy.hello.tasklet;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;

@Component("HelloTasklet2")
@StepScope
@Slf4j
public class HelloTasklet2 implements Tasklet {

    @Value("#{JobExecutionContext['jobKey1']}")
    private String jobValue1;

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {

        log.info("Hello Tasklet2");
        log.info("jobValue1={}" + jobValue1);

        // System.out.println("Hello Tasklet2");
        // System.out.println("jobValue1=" + jobValue1);

        return RepeatStatus.FINISHED;
    }
}

⑤resourcesにlogback.xmlを作成する

  • Lombokとは、Javaのコードを簡潔にし、生産性を高め、可読性を高めるオープンソースのライブラリ
  • @Dataアノテーションで、Getter/Setterを書かなくても使用できる
  • @Slf4jのアノテーションをつけることでログ出力の実装が可能となる

Chunkを作成する

  • Chunkとは、ItemReader、ItemProcessor、ItemWriterを組み合わせて、データ処理を行う単位で、ItemReaderからデータを読み込み、ItemProcessorで処理し、ItemWriterで書き込む

  • ItemProcessorとは、ItemReaderから受け取ったデータ項目を処理し、ItemWriterを渡す

  • ItemWriterとは、ItemProcessorから受け取った処理結果を書き込む

①chunk/HelloReader.javaを作成する

chunk/HelloReader.java
package com.udemy.hello.chunk;

import org.springframework.batch.core.configuration.annotation.StepScope;
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.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@StepScope
@Slf4j
public class HelloReader implements ItemReader<String> {

    private int intIndex = 0;

    @Override
    public String read() throws Exception,
            UnexpectedInputException, ParseException,
            NonTransientResourceException {
        String[] readDataList = { "associate", "consultant", "manager",
                "director", "president", null };

        log.info("Read Data={}", readDataList[intIndex]);

        return readDataList[intIndex++];
    }
}

②chunk/HelloProcessor.javaを作成する

chunk/HelloProcessor.javaを作成する
package com.udemy.hello.chunk;

import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@StepScope
@Slf4j
public class HelloProcessor implements ItemProcessor<String, String> {

    @Override
    public String process(String item) throws Exception {

        item = item.toUpperCase();
        log.info("Processor item={}", item);
        return item;
    }
}

③chunk/HelloWriter.javaを作成する

chunk/HelloWriter.java
package com.udemy.hello.chunk;

import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.Chunk;
import org.springframework.batch.item.ItemWriter;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@StepScope
@Slf4j
public class HelloWriter implements ItemWriter<String> {

    @Override
    public void write(Chunk<? extends String> chunk)
            throws Exception {
        log.info("Writer chunk={}", chunk);
        log.info("-----------------");
    }

}

EventListenerを作成する

①listener/HelloJobExecutionListener.javaを作成する

listener/HelloJobExecutionListener.java
package com.udemy.hello.listener;

import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class HelloJobExecutionListener implements JobExecutionListener {

    @Override
    public void beforeJob(JobExecution jobExecution) {
        log.info("Job スタート at:{}", jobExecution.getStartTime());
    }

    @Override
    public void afterJob(JobExecution jobExecution) {
        log.info("Job 終了 at:{}", jobExecution.getEndTime());
    }
}

CSVファイルを読み込みDBへ登録するバッチを作成する

(1)プロジェクトを作成する

公式:spring initializr

① ブラウザを開き、(https://start.spring.io/) を入力し、開く
② 下記を入力し、Dependenciesで下記を選択し、『GENERATE』ボタンを押す

  • Project: Maven

  • Language: Java

  • SpringBoot: 3.2.9

  • jar

  • 21

  • Group: com.udemy

  • Artifact: hello-spring-batch

  • Name : hello-spring-batch

  • Description: Demo projects for Spring Boot

  • Package name: com.udemy.hello

Dependencies

  • Spring Batch
  • PostgreSQL Driver
  • lombock

CSVファイルを読み込む

①src/main/resoucesにinputData/employee.csvを作成する

src/main/resouces/inputData/employee.csv
従業員コード,従業員名,ジョブタイトル,上司,入社日
5000,山田 太郎,manager,5003,2001/01/01
5001,佐藤 二郎,salesman,5000,2002/05/02
5002,田中 三郎,associate,5001,2010/09/20
5003,鈴木 一郎,srmgr,,2020/01/01

②resouces/application.propertiesにDB接続文字列を追加する

resouces/application.properties
# csvパスのエントリを追加する
csv.path=inputData/employee.csv

spring.datasource.url=jdbc:mysql://localhost:3306/BatchDB?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.batch.jdbc.initialize-schema = ALWAYS

③resouces/logback.xmlを追加する

resouces/logback.xml
<configuration>
  
  <property name="logDir" value="C:\pleiades-2023\workspace\hello-spring-batch\log\" />
  <property name="fileName" value="hellolog" />

  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${logDir}${fileName}.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>10</maxHistory>
    </rollingPolicy>
    
    <encoder>
      <pattern>%d %level %m %n</pattern>
    </encoder>
   </appender>

  <root level="INFO">
    <appender-ref ref="FILE" />
  </root>
  
</configuration>

CSVデータを保持するクラスを作成する

①src/main/resoucesにmodel/Employee.javaを作成する

src/main/resouces/model/Employee.java

package com.udemy.hello.model;

import java.util.Date;
import lombok.Data

public class Employee {

    private Integer empNumber;
    private String empName;
    private String jobTitle;
    private Integer mgrNumber;
    private Date hireDate;
}

②configCvs/SpringConfig.javaを作成する

configCvs/SpringConfig.java
package com.udemy.hello.configCsv;

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.core.step.builder.StepBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.job.builder.JobBuilder;

import javax.sql.DataSource;

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 com.udemy.hello.model.Employee;

@Configuration
public class SpringConfig {
    private final JobLauncher jobLauncher;
    private final JobRepository jobRepository;
    private final PlatformTransactionManager transactionManager;

    @Value("${csv.path}")
    private Resource inputCSV;

    @Autowired
    private DataSource dataSource;

    private static final String INSERT_EMP_SQL = "INSERT INTO employee (empNumber, empName, jobTitle, mgrNumber, hireDate) "
            +
            "VALUES (empNumber, empName, jobTitle, mgrNumber, hireDate)";

    public SpringConfig(JobLauncher jobLauncher, JobRepository jobRepository,
            PlatformTransactionManager transactionManager) {
        this.jobLauncher = jobLauncher;
        this.jobRepository = jobRepository;
        this.transactionManager = transactionManager;
    }

    @Bean
    @StepScope
    public FlatFileItemReader<Employee> csvItemReader() {

        FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();
        reader.setResouce(inputCSV);

        reader.setLinesToSkip(1);
        reader.setEncoding(StandardCharsets.UTF_8.name());

        BeanWrapperFieldSetMapper<Employee> beanWrapperFieldSetMapper = new BeanWrapperFieldSetMapper<Employee>();
        beanWrapperFieldSetMapper.setTargetType(Employee.class);

        DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
        String[] csvTitleArray = new String[] { "EmpNumber", "EmpName", "JobTitle", "mgrNumber", "HireDate" };
        tokenizer.setNames(csvTitleArray);

        DefaultLineMapper<Employee> lineMapper = new DefaultLineMapper<Employee>();
        lineMapper.setFieldSetMapper(beanWrapperFieldSetMapper);
        lineMapper.setLineTokenizer(tokenizer);

        reader.setLineMapper(lineMapper);
        return reader;
    }

    @Autowired
    @Qualifier("empItemProcessor")
    public ItemProcessor<Employee, Employee> empItemProcessor;

    @Bean
    @StepScope
    public JdbcBatchItemWriter<Employee> jdbcBatchItemWriter() {
        BeanPropertyItemSqlParameterSourceProvider<Employee> provider = new BeanPropertyItemSqlParameterSourceProvider<Employee>();

        JdbcBatchItemWriter<Employee> writer = new JdbcBatchItemWriter<Employee>();
        writer.setDataSource(dataSource);
        writer.setItemSqlParameterSourceProvider(provider);
        writer.setSql(INSERT_EMP_SQL);
        return writer;
    }

    @Bean
    public Step chunkStep1() {
        return new StepBuilder("EmpImportStep1", jobRepository)
                .<Employee, Employee>chunk(1, transactionManager)
                .reader(csvItemReader())
                .processor(empItemProcessor)
                .writer(jdbcItemWriter())
                .build();
    }

    @Bean
    public Job chunkJob() {
        return new JobBuilder("chunkJob", jobRepository)
                .incrementer(new RunIdIncrementer())
                .start(chunkStep1())
                .build();
    }
}

③chuckCsv/EmpItemProcessor.javaを作成する

chuckCsv/EmpItemProcessor.java
package com.udemy.hello.chuckCsv;

import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;

import com.udemy.hello.model.Employee;

import lombok.extern.slf4j.Slf4j;

@Component("EmpItemProcessor")
@StepScope
@Slf4j
public class EmpItemProcessor implements ItemProcessor<Employee, Employee> {

    @Override
    public Employee process(Employee item) throws Exception {
        log.info("Proceess...{}", item);
        item.setJobTitle(item.getJobTitle().toUpperCase());
        return item;
    }
}

Rest APIからSpring Batchを実行する

参考サイト

【ゼロからスタート】Spring Batchで始めるJavaバッチ開発入門トレーニング

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?