CLOVER🍀

That was when it all began.

はじめてのSpring Batch

これは、なにをしたくて書いたもの?

Spring Batchについて、ちょっと見ておこうかな、ということで。

Spring Batch

Spring Batchのページはこちら。

https://spring.io/projects/spring-batch

ドキュメント。

Spring Batch - Reference Documentation

まずは、イントロダクションを読んでみましょう。

Spring Batch Introduction

Spring Batchが登場したバックグラウンドについて。

Spring Batch Introduction / Background

OSSや関連するコミュニティは、WebベースやMicroservicesベースのアーキテクチャーに大きく注目していますが、バッチ処理に関しては
そうでもありません。バッチには標準的で再利用可能なアーキテクチャーがないため、エンタープライズITでは1回限りの社内ソリューションが
多く作り出されています。

While open source software projects and associated communities have focused greater attention on web-based and microservices-based architecture frameworks, there has been a notable lack of focus on reusable architecture frameworks to accommodate Java-based batch processing needs, despite continued needs to handle such processing within enterprise IT environments. The lack of a standard, reusable batch architecture has resulted in the proliferation of many one-off, in-house solutions developed within client enterprise IT functions.

この状況に対して、SpringSourceとAccentureが協力してSpring Batchができたようです。

どういう時に使うのか?というところですが。

Spring Batch Introduction / Usage Scenarios

一般に、バッチプログラムは次のようなことを行います。

  • 多数のレコードをデータベース、ファイル、またはキューから読み取る
  • データに対して、なにか処理をする
  • フォーマットを変更し、データを書き戻す

Spring Batchは、この基本的なバッチの繰り返しを自動化したもので、以下のようなシーンで役に立ちます。

  • バッチ処理の定期的なコミット
  • 並行、並列バッチ処理
  • メッセージ駆動処理
  • 障害後の手動またはスケジュールされた再起動
  • 依存するステップの順次処理
  • レコードのスキップなど
  • バッチサイズが小さい場合の一括トランザクション

Spring Batchのアーキテクチャーはこちら。

Spring Batch Introduction / Spring Batch Architecture

なのですが、Spring Batchのドメインにおける用語を見た方がよいでしょうね。

The Domain Language of Batch

一般的なバッチの原則およびガイドラインとされるものが、こちらに書かれています。

Spring Batch Introduction / General Batch Principles and Guidelines

たとえば、以下のような内容です。

  • 可能な限り単純化し、単一のバッチアプリケーションで複雑な構造を作り出すことは避ける
  • データの保存場所と処理を近くに配置すること
  • IOを最小限に抑え、メモリを活用すること
  • バッチを2度実行しない
  • データの整合性については常に最悪の想定を行い、適切なチェックとレコードのバリデーションを行い、データの整合性を維持すること
  • 早い段階で、現実的な量のデータを用意してストレステストを実施すること

そして、バッチ処理戦略。

Spring Batch Introduction / Batch Processing Strategies

バッチの設計と実装は、以下の標準的な構成要素から行えるようにすべきです。

  • 変換アプリケーション
    • 外部システムによって提供または生成されたファイルの種類ごとに、処理に必要な標準フォーマットに変換する
  • バリデーションアプリケーション
    • すべての入力/出力レコードが正しく、一貫性があることを確認する
    • バリデーションはファイルヘッダーやトレーラー、チェックサム、バリデーションアルゴリズム、そしてレコードレベルでのクロスチェックで行う
  • 抽出アプリケーション
    • データベースやファイルからレコードを読み取り、事前定義されたルールに基づいてレコードを選択、ファイル出力を行う
  • 抽出/更新アプリケーション
    • データベースやファイルからレコードを読み取り、データベースを変更したりファイル出力を行う
  • 処理/更新アプリケーション
    • 入力となるトランザクションから抽出、検証を行い、処理を実行する
    • データベースから必要なデータを取得するとともに、データベースの更新や出力処理用のレコード作成を含む
  • 出力/フォーマットアプリケーション
    • ファイルを読み込み、データを再構成して、印刷や外部システム向けに出力ファイルを作成する

これらでビルディングブロックで構築できない場合は、シェルスクリプトなども作成する必要があります。

ユーティリティ的なステップとしては、ソート、分割、マージといったものがあります。

さらにオプションとして、以下についても書かれています。

  • バッチウィンドウでの処理
  • バッチとオンライン処理の並行性
    • バッチとオンライン処理との競合を踏まえた、楽観的ロックや悲観的ロックなどの戦略
  • 並列処理
  • パーティショニング
    • 並列化との組み合わせになる

Spring Batchに関する用語。

The Domain Language of Batch

  • Job … バッチプロセス全体のこと
    • JobInstance … 論理的なJobの実行単位。状態を持ち、中断したところから再開することもできる
    • JobParameters … Jobを開始するために与えるパラメーター
    • JobExecution … Job実行する単一の技術的な概念。JobInstanceは成功または失敗で終了する可能性があるが、実行が正常に完了しない限りはそのJobInstanceは完了したとはみなされない
  • Step … Jobのシーケンシャルなフェーズをカプセル化したもの。すべてのJobは、ひとつ以上のStepから構成される
    • StepExecution … あるStepの実行を表現したもの。Stepが実行される度に、新しいStepExecutionが作成される。また前のStepが失敗したなどの理由で、Stepが起動しない限りは作成されない
  • ExecutionContextフレームワークによって永続化される、キーと値のペアのこと。Jobの再実行を容易にすることに使われたりする
  • JobRepository … Job、JobExecution、Step、StepExecution、ExecutionContextなど、上記のステレオタイプの永続化メカニズム。JobLauncherに対するCRUD操作を提供する
  • JobLauncher … JobParametersを与えてJobを起動するためのシンプルなインターフェース
  • ItemReader … Stepへの入力となる、ひとつのItemを取得することを抽象化したもの。取得するItemがなくなった場合は、nullを返すことで表現する
  • ItemWriter … Stepの出力となる、バッチまたはItemのチャンクを抽象化したもの。次に受け取るItemの情報は知らず、現在の呼び出して渡されたItemの情報のみを知っている
  • ItemProcessor … Itemへのビジネス処理を抽象化したもの。ItemReaderがひとつItemを読み取りItemWriterが書き込むまでの間に、ItemProcessorは変換やその他のビジネス処理を行うアクセスポイントを提供する。Itemの処理中にそのItemが無効だと判定した場合は、nullを返すことでそのItemは書き出す必要がないことを表す

ちなみに、用語集もあります。

Glossary

Jobの構成や実行については、こちら。

Configuring and Running a Job

Stepの構成については、こちら。

Configuring a Step

ここで、Stepの構成にはチャンク指向のStepとTaskletというStepの2種類があることがわかります。

Taskletは初出ですが、executeというメソッドを持つTaskletインターフェースを使って実装されます。

チャンク指向のStepは、先述のItemReader、ItemWriter、そしてItemProcessorから構成されます。

そして、Job内のStepはフローを構成することができます。

Configuring a Step / Controlling Step Flow

ここまでが、基本的な構成要素ですね。

あとは、ItemReader、ItemWriter、そしてItemProcessorに関するドキュメントや、

ItemReaders and ItemWriters

Item processing

Spring Batchによる、実装済みのItemReaderとItemWriterの紹介もあります。

List of ItemReaders and ItemWriters

一般的なバッチのパターンや、トランザクションバッチ処理の組み合わせなどを見るとよいかなと思います。

Common Batch Patterns

Batch Processing and Transactions

Spring Integrationと組み合わせることもできるようです。

Spring Batch Integration

Spring BootとSpring Batch

“How-to” Guides / Batch Applications

Spring BootでSpring Batchを使う際には、多くの人が質問をするようです。

A number of questions often arise when people use Spring Batch from within a Spring Boot application.

また、Spring Batch向けのSpring Bootのプロパティはこれくらいになります。

  • spring.batch.jdbc.initialize-schema
  • spring.batch.jdbc.platform
  • spring.batch.jdbc.schema
  • spring.batch.jdbc.table-prefix
  • spring.batch.job.enabled
  • spring.batch.job.names

Application Properties / Integration Properties

長くなりましたね。ドキュメントを眺めるのはこれくらいにして、実際にSpring Batchを使っていってみましょう。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.2 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.2+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.2, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-109-generic", arch: "amd64", family: "unix"

また、データベースとしてはMySQLを使用し、172.17.0.2で動作しているものとします。

$ mysql --version
mysql  Ver 8.0.28 for Linux on x86_64 (MySQL Community Server - GPL)

お題

今回は、チャンクとTaskletでそれぞれひとつずつJobを作ってみることにします。

  • チャンク
    • 書籍データが格納されたCSVファイルを読み込む
    • 読み込んだ書籍データを、MySQLに登録する
  • Tasklet
    • チャンクで登録した書籍データを、ログ出力する

データベースアクセスにはJPAを使い、JobRepositoryの永続化先もMySQLとします。

プロジェクトを作成する

まずはSpring Bootプロジェクトを作成します。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=2.6.7 \
  -d javaVersion=17 \
  -d name=batch-example \
  -d groupId=org.littlewings \
  -d artifactId=batch-example \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=org.littlewings.spring.batch \
  -d dependencies=batch,data-jpa,mysql \
  -d baseDir=batch-example | tar zxvf -

依存関係には、Spring Batch、Spring Data JPAMySQL JDBC Driverとしています。

プロジェクト内に移動。

$ cd batch-example

Maven依存関係や、プラグインの設定。

        <properties>
                <java.version>17</java.version>
        </properties>
        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-batch</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-data-jpa</artifactId>
                </dependency>

                <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <scope>runtime</scope>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>org.springframework.batch</groupId>
                        <artifactId>spring-batch-test</artifactId>
                        <scope>test</scope>
                </dependency>
        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>

デフォルトで作成されているソースコードは、削除しておきます。

$ rm src/main/java/org/littlewings/spring/batch/BatchExampleApplication.java src/test/java/org/littlewings/spring/batch/BatchExampleApplicationTests.java

mainクラスの作成と下準備

最初に、mainクラスとどのジョブでも使いそうなものを作成、設定していきます。

src/main/java/org/littlewings/spring/batch/App.java

package org.littlewings.spring.batch;

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableBatchProcessing
public class App {
    public static void main(String... args) {
        SpringApplication.run(App.class, args);
    }
}

Spring Bootのドキュメントに習い、@EnableBatchProcessingアノテーションを付与してSpring Batchを有効にします。

“How-to” Guides / Batch Applications / Running Spring Batch Jobs on Startup

テーブル定義は、以下とします。

src/main/resources/schema.sql

create table if not exists book (
  isbn varchar(14),
  title varchar(100),
  price int,
  publish_date date,
  primary key(isbn)
);

JPAのエンティティクラス。

src/main/java/org/littlewings/spring/batch/entity/Book.java

package org.littlewings.spring.batch.entity;

import java.time.LocalDate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "book")
public class Book {
    @Id
    @Column(name = "isbn")
    String isbn;

    @Column(name = "title")
    String title;

    @Column(name = "price")
    Integer price;

    @Column(name = "publish_date")
    LocalDate publishDate;

    // getter/setterは省略
}

リポジトリー。Spring Data JPAに従い、JpaRepositoryインターフェースを拡張して用意。

src/main/java/org/littlewings/spring/batch/repository/BookRepository.java

package org.littlewings.spring.batch.repository;

import org.littlewings.spring.batch.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BookRepository extends JpaRepository<Book, String> {
}

Spring Bootの設定。

src/main/resources/application.properties

spring.datasource.url=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin
spring.datasource.username=kazuhira
spring.datasource.password=password

spring.sql.init.mode=always
spring.batch.jdbc.initialize-schema=always

データベース接続と、schema.sqlの実行(spring.sql.init.mode)および
Spring Batchのメタデータ用のテーブルも作成(spring.batch.jdbc.initialize-schema)するようにします。

取り込むCSVファイルは、ヘッダーなしのものと有りのものの2つを作成。

ヘッダーなし。

src/main/resources/book.csv

978-4798142470,Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発,4400,2016-07-21
978-4774182179,[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ,4180,2016-06-14
978-1492076988,Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications,6265,2021-03-23
978-1484237236,The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud,7361,2019-07-09
978-4798161488,MySQL徹底入門 第4版 MySQL 8.0対応,4180,2020-07-06
978-4797393118,基礎からのMySQL 第3版 (基礎からシリーズ),6038,2017-09-22
978-4873116389,実践ハイパフォーマンスMySQL 第3版,5280,2013-11-25
978-4295000198,やさしく学べるMySQL運用・管理入門【5.7対応】,2860,2016-12-15
978-4798147406,詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE),3960,2016-08-26
978-4774170206,MariaDB&MySQL全機能バイブル,3850,2014-12-18

ヘッダーあり。

src/main/resources/book_with_header.csv

isbn,title,price,publishDate
978-4798142470,Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発,4400,2016-07-21
978-4774182179,[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ,4180,2016-06-14
978-1492076988,Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications,6265,2021-03-23
978-1484237236,The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud,7361,2019-07-09
978-4798161488,MySQL徹底入門 第4版 MySQL 8.0対応,4180,2020-07-06
978-4797393118,基礎からのMySQL 第3版 (基礎からシリーズ),6038,2017-09-22
978-4873116389,実践ハイパフォーマンスMySQL 第3版,5280,2013-11-25
978-4295000198,やさしく学べるMySQL運用・管理入門【5.7対応】,2860,2016-12-15
978-4798147406,詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE),3960,2016-08-26
978-4774170206,MariaDB&MySQL全機能バイブル,3850,2014-12-18

チャンクStepを作成する

では、チャンク用のStepを持つJobから作成していきます。

今回のお題だと自分でItemReaderItemWriterを作成しなくてもよいのですが、まずは自分でも書いてみることにします。

ItemReader

src/main/java/org/littlewings/spring/batch/chunk/CsvFileItemReader.java

package org.littlewings.spring.batch.chunk;

import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import org.littlewings.spring.batch.entity.Book;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.file.DefaultBufferedReaderFactory;
import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream;
import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
import org.springframework.core.io.Resource;

public class CsvFileItemReader extends AbstractItemCountingItemStreamItemReader<Book> implements ResourceAwareItemReaderItemStream<Book> {
    Logger logger = LoggerFactory.getLogger(CsvFileItemReader.class);

    Resource resource;
    BufferedReader reader;

    DateTimeFormatter publishDateFormatter;

    @Override
    protected void doOpen() throws Exception {
        DefaultBufferedReaderFactory bufferedReaderFactory = new DefaultBufferedReaderFactory();
        reader = bufferedReaderFactory.create(resource, "UTF-8");
        publishDateFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd");
    }


    @Override
    protected Book doRead() throws Exception {
        String line = reader.readLine();

        if (line != null) {
            String[] tokens = line.split(",");
            Book book = new Book();
            book.setIsbn(tokens[0]);
            book.setTitle(tokens[1]);
            book.setPrice(Integer.parseInt(tokens[2]));
            book.setPublishDate(LocalDate.parse(tokens[3], publishDateFormatter));

            logger.info("[reader] read book:  isbn = {}, title = {}", book.getIsbn(), book.getTitle());

            return book;
        }

        logger.info("[reader] readed books");

        return null;
    }

    @Override
    protected void doClose() throws Exception {
        reader.close();
    }

    @Override
    public void setResource(Resource resource) {
        this.resource = resource;
    }
}

1行のCSV要素をBookクラスにマッピングする箇所は、愚直に作成しました。ヘッダーの読み飛ばしには対応していません。

ItemReaderの作成には、AbstractItemCountingItemStreamItemReaderクラスを継承すると良さそうです。

Abstract base class that provides basic restart capabilities by counting the number of items returned from an ItemReader.

List of ItemReaders and ItemWriters

また、ResourceAwareItemReaderItemStreamインターフェースを実装することで、Spring Batchの他のItemReaderと同じように
読み込み対象のファイルをResourceで表現するようにしておきます。

ちなみに、このItemReaderはクラスの定義時にはSpringのBeanとしては定義せず、Java ConfigでSpringのBeanとして登録します。

ItemProcessor。こちらは、ログ出力するだけにしました。

LoggingBookProcessor.java

package org.littlewings.spring.batch.chunk;

import org.littlewings.spring.batch.entity.Book;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemProcessor;

public class LoggingBookProcessor implements ItemProcessor<Book, Book> {
    Logger logger = LoggerFactory.getLogger(LoggingBookProcessor.class);

    @Override
    public Book process(Book item) throws Exception {
        logger.info("[processor] process book: isbn = {}, title = {}", item.getIsbn(), item.getTitle());

        return item;
    }
}

ItemWriter

src/main/java/org/littlewings/spring/batch/chunk/BookJpaItemWriter.java

package org.littlewings.spring.batch.chunk;

import java.util.List;

import org.littlewings.spring.batch.entity.Book;
import org.littlewings.spring.batch.repository.BookRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.support.AbstractItemStreamItemWriter;
import org.springframework.beans.factory.annotation.Autowired;

public class BookJpaItemWriter extends AbstractItemStreamItemWriter<Book> {
    Logger logger = LoggerFactory.getLogger(BookJpaItemWriter.class);

    @Autowired
    BookRepository bookRepository;

    @Override
    public void write(List<? extends Book> items) throws Exception {
        logger.info("[writer] write items: size = {}", items.size());

        items.forEach(book -> {
            logger.info("[writer] write item: isbn = {}, title = {}", book.getIsbn(), book.getTitle());
            bookRepository.save(book);
        });
    }
}

先ほど作成した、Book用のリポジトリーを使ってデータをテーブルに保存します。

継承しているAbstractItemStreamItemWriterクラスは、ItemWriterおよびItemStreamを実装した抽象クラスで、今回は使用していませんが
openメソッドやcloseメソッドをオーバーライドして初期化、終了処理を実装することができます。

Abstract base class that combines the ItemStream and ItemWriter interfaces.

List of ItemReaders and ItemWriters

では、JobおよびStepの定義を行っていきます。

参照するのは、以下あたりですね。

Configuring and Running a Job

Configuring and Running a Job / Configuring a Job

Configuring a Step

Configuring a Step / Chunk-oriented Processing / Configuring a Step

今回は、こんな感じで作成。

src/main/java/org/littlewings/spring/batch/config/FileLoadToDatabaseJobConfig.java

package org.littlewings.spring.batch.config;

import org.littlewings.spring.batch.chunk.BookJpaItemWriter;
import org.littlewings.spring.batch.chunk.CsvFileItemReader;
import org.littlewings.spring.batch.chunk.LoggingBookProcessor;
import org.littlewings.spring.batch.entity.Book;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

@Configuration
public class FileLoadToDatabaseJobConfig {
    @Autowired
    JobBuilderFactory jobBuilderFactory;

    @Autowired
    StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job fileLoadToDatabaseJob() {
        return jobBuilderFactory
                .get("fileLoadToDatabaseJob")
                .incrementer(new RunIdIncrementer())
                .start(fileLoadToDatabaseStep())
                .build();
    }

    @Bean
    public Step fileLoadToDatabaseStep() {
        return stepBuilderFactory
                .get("fileLoadToDatabaseStep")
                .<Book, Book>chunk(3)
                .reader(csvFileItemReader(null))
                .processor(loggingBookProcessor())
                .writer(bookJpaItemWriter())
                .build();
    }

    @StepScope
    @Bean
    public CsvFileItemReader csvFileItemReader(@Value("#{jobParameters['filePath']}") String filePath) {
        Resource fileResource = new FileSystemResource(filePath);

        CsvFileItemReader itemReader = new CsvFileItemReader();
        itemReader.setResource(fileResource);
        itemReader.setName("csvFileItemReader");
        itemReader.setSaveState(false);
        return itemReader;
    }

    @StepScope
    @Bean
    public LoggingBookProcessor loggingBookProcessor() {
        return new LoggingBookProcessor();
    }

    @StepScope
    @Bean
    public BookJpaItemWriter bookJpaItemWriter() {
        return new BookJpaItemWriter();
    }
}

ここまでに作成した、ItemReaderItemProcessorItemWriterのBean定義。いずれも@StepScopeとしています。

    @StepScope
    @Bean
    public CsvFileItemReader csvFileItemReader(@Value("#{jobParameters['filePath']}") String filePath) {
        Resource fileResource = new FileSystemResource(filePath);

        CsvFileItemReader itemReader = new CsvFileItemReader();
        itemReader.setResource(fileResource);
        itemReader.setName("csvFileItemReader");
        itemReader.setSaveState(false);
        return itemReader;
    }

    @StepScope
    @Bean
    public LoggingBookProcessor loggingBookProcessor() {
        return new LoggingBookProcessor();
    }

    @StepScope
    @Bean
    public BookJpaItemWriter bookJpaItemWriter() {
        return new BookJpaItemWriter();
    }

読み込むファイルは、JobParameterとして指定することにしました。JobParameter(またはJobParameters)を使うには、
Beanが@StepScopdeでスコープ定義されている必要があります。

    public CsvFileItemReader csvFileItemReader(@Value("#{jobParameters['filePath']}") String filePath) {
        Resource fileResource = new FileSystemResource(filePath);

Stateというのは、コミットされる前までにどこまで読み取ったか(処理したか)を保存するもので、再起動時に続きから始められる
仕掛けになるものですが。今回はこういった考慮はしないことにするので、falseにしておきます。

        itemReader.setSaveState(false);

ItemReaders and ItemWriters / Preventing State Persistence

あとはJobとStepの定義。

    @Bean
    public Job fileLoadToDatabaseJob() {
        return jobBuilderFactory
                .get("fileLoadToDatabaseJob")
                .incrementer(new RunIdIncrementer())
                .start(fileLoadToDatabaseStep())
                .build();
    }

    @Bean
    public Step fileLoadToDatabaseStep() {
        return stepBuilderFactory
                .get("fileLoadToDatabaseStep")
                .<Book, Book>chunk(3)
                .reader(csvFileItemReader(null))
                .processor(loggingBookProcessor())
                .writer(bookJpaItemWriter())
                .build();
    }

読み込むファイル内のデータが10件しかないので、チャンクサイズは3にしています。

JobBuilderFactory#getStepBuilderFactory#getで指定している名前がJob名やStep名になるようなのですが、Bean名は別になるので
他と衝突しないようにBean定義のメソッド名を合わせるなり@Beanアノテーションで指定するなりした方がよいでしょう。

また、incrementerで指定しているRunIdIncrementerですが、これはJobParameterとしてrun.idを追加するものです。

RunIdIncrementer (Spring Batch 4.3.5 API)

run.idは自動でインクリメントされていくのですが、Spring BatchのJobは同じパラメーター指定では起動できなくなるので、
こちらを追加しておきます。

では、ここで1度動作確認しておきます。

パッケージング。

$ mvn package

起動。

$ java -Dspring.batch.job.names=fileLoadToDatabaseJob -jar target/batch-example-0.0.1-SNAPSHOT.jar filePath=src/main/resources/book.csv

Spring Batchでは、デフォルトですべてのJobを起動しようとします。これは、spring.batch.job.namesプロパティで起動するJobを
指摘できます(複数の場合はカンマ区切り)。

この時点ではひとつしかJobがないのですが、最初から指定しておきましょう。

ログは、こんな感じに出力されます。

2022-04-24 20:32:03.650  INFO 36238 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: [filePath=src/main/resources/book.csv]
2022-04-24 20:32:03.794  INFO 36238 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=fileLoadToDatabaseJob]] launched with the following parameters: [{run.id=1, filePath=src/main/resources/book.csv}]
2022-04-24 20:32:03.885  INFO 36238 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [fileLoadToDatabaseStep]
2022-04-24 20:32:04.006  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-4798142470, title = Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
2022-04-24 20:32:04.009  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-4774182179, title = [改訂新版]Spring入門 ――Java フレームワーク・より良い設計とアーキテクチャ
2022-04-24 20:32:04.009  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-1492076988, title = Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications
2022-04-24 20:32:04.014  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4798142470, title = Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
2022-04-24 20:32:04.014  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4774182179, title = [改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ
2022-04-24 20:32:04.014  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-1492076988, title = Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications
2022-04-24 20:32:04.015  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write items: size = 3
2022-04-24 20:32:04.016  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-4798142470, title = Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
2022-04-24 20:32:04.064  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-4774182179, title = [改訂新版]Spring入門 ――Java フレームワーク・より良い設計とアーキテクチャ
2022-04-24 20:32:04.066  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-1492076988, title = Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications
2022-04-24 20:32:04.111  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-1484237236, title = The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud
2022-04-24 20:32:04.111  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-4798161488, title = MySQL徹底入門 第4版 MySQL 8.0対応
2022-04-24 20:32:04.111  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-4797393118, title = 基礎からのMySQL 第3版 (基礎 からシリーズ)
2022-04-24 20:32:04.112  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-1484237236, title = The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud
2022-04-24 20:32:04.112  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4798161488, title = MySQL徹底入門 第4版 MySQL 8.0対応
2022-04-24 20:32:04.112  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4797393118, title = 基礎からのMySQL 第3版 (基礎からシリーズ)
2022-04-24 20:32:04.112  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write items: size = 3
2022-04-24 20:32:04.112  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-1484237236, title = The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud
2022-04-24 20:32:04.114  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-4798161488, title = MySQL徹底入門 第4版 MySQL 8.0対応
2022-04-24 20:32:04.116  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-4797393118, title = 基礎からのMySQL 第3版 (基礎 からシリーズ)
2022-04-24 20:32:04.145  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-4873116389, title = 実践ハイパフォーマンスMySQL 第3版
2022-04-24 20:32:04.145  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-4295000198, title = やさしく学べるMySQL運用・管 理入門【5.7対応】
2022-04-24 20:32:04.146  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-4798147406, title = 詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE)
2022-04-24 20:32:04.146  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4873116389, title = 実践ハイパフォーマンスMySQL 第3版
2022-04-24 20:32:04.146  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4295000198, title = やさしく学べるMySQL運用・管理入門【5.7対応】
2022-04-24 20:32:04.146  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4798147406, title = 詳解MySQL 5.7 止まらぬ 進化に乗り遅れないためのテクニカルガイド (NEXT ONE)
2022-04-24 20:32:04.147  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write items: size = 3
2022-04-24 20:32:04.147  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-4873116389, title = 実践ハイパフォーマンスMySQL 第3版
2022-04-24 20:32:04.149  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-4295000198, title = やさしく学べるMySQL運用・管 理入門【5.7対応】
2022-04-24 20:32:04.150  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-4798147406, title = 詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE)
2022-04-24 20:32:04.189  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] read book:  isbn = 978-4774170206, title = MariaDB&MySQL全機能バイブル
2022-04-24 20:32:04.189  INFO 36238 --- [           main] o.l.s.batch.chunk.CsvFileItemReader      : [reader] readed books
2022-04-24 20:32:04.189  INFO 36238 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4774170206, title = MariaDB&MySQL全機能バイブル
2022-04-24 20:32:04.189  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write items: size = 1
2022-04-24 20:32:04.190  INFO 36238 --- [           main] o.l.s.batch.chunk.BookJpaItemWriter      : [writer] write item: isbn = 978-4774170206, title = MariaDB&MySQL全機能バイブル
2022-04-24 20:32:04.251  INFO 36238 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [fileLoadToDatabaseStep] executed in 365ms
2022-04-24 20:32:04.311  INFO 36238 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=fileLoadToDatabaseJob]] completed with the following parameters: [{run.id=1, filePath=src/main/resources/book.csv}] and the following status: [COMPLETED] in 464ms

ItemReader(チャンクサイズ分繰り返し) → ItemProcessor(チャンクサイズ分繰り返し) → ItemWriter(チャンクサイズのデータを一気に
引き渡し)を取得したチャンクの数だけ繰り返すという挙動になっています。

このあたりの呼び出しは、以下を見ると良さそうです。

https://github.com/spring-projects/spring-batch/blob/4.3.5/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java#L64-L95

https://github.com/spring-projects/spring-batch/blob/4.3.5/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java#L71

https://github.com/spring-projects/spring-batch/blob/4.3.5/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java#L210

https://github.com/spring-projects/spring-batch/blob/4.3.5/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java#L217

run.idも指定されていますね。

2022-04-24 20:32:03.794  INFO 36238 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=fileLoadToDatabaseJob]] launched with the following parameters: [{run.id=1, filePath=src/main/resources/book.csv}]

データベースの方は、Jobを起動するまではなにもテーブルがありませんが

mysql> use practice;
Database changed
mysql> show tables;
Empty set (0.00 sec)

Job実行後は以下のようになっています。

mysql> show tables;
+------------------------------+
| Tables_in_practice           |
+------------------------------+
| BATCH_JOB_EXECUTION          |
| BATCH_JOB_EXECUTION_CONTEXT  |
| BATCH_JOB_EXECUTION_PARAMS   |
| BATCH_JOB_EXECUTION_SEQ      |
| BATCH_JOB_INSTANCE           |
| BATCH_JOB_SEQ                |
| BATCH_STEP_EXECUTION         |
| BATCH_STEP_EXECUTION_CONTEXT |
| BATCH_STEP_EXECUTION_SEQ     |
| book                         |
+------------------------------+
10 rows in set (0.00 sec)

データが入っていることの確認。

mysql> select * from book;
+----------------+---------------------------------------------------------------------------------------------------------+-------+--------------+
| isbn           | title                                                                                                   | price | publish_date |
+----------------+---------------------------------------------------------------------------------------------------------+-------+--------------+
| 978-1484237236 | The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud                       |  7361 | 2019-07-09   |
| 978-1492076988 | Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications                         |  6265 | 2021-03-23   |
| 978-4295000198 | やさしく学べるMySQL運用・管理入門【5.7対応】                                                            |  2860 | 2016-12-15   |
| 978-4774170206 | MariaDB&MySQL全機能バイブル                                                                             |  3850 | 2014-12-18   |
| 978-4774182179 | [改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ                                 |  4180 | 2016-06-14   |
| 978-4797393118 | 基礎からのMySQL 第3版 (基礎からシリーズ)                                                                |  6038 | 2017-09-22   |
| 978-4798142470 | Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発                                           |  4400 | 2016-07-21   |
| 978-4798147406 | 詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE)                               |  3960 | 2016-08-26   |
| 978-4798161488 | MySQL徹底入門 第4版 MySQL 8.0対応                                                                       |  4180 | 2020-07-06   |
| 978-4873116389 | 実践ハイパフォーマンスMySQL 第3版                                                                       |  5280 | 2013-11-25   |
+----------------+---------------------------------------------------------------------------------------------------------+-------+--------------+
10 rows in set (0.01 sec)

他のテーブルの内容は、こんな感じになっています。

mysql> select * from BATCH_JOB_EXECUTION;
+------------------+---------+-----------------+----------------------------+----------------------------+----------------------------+-----------+-----------+--------------+----------------------------+----------------------------+
| JOB_EXECUTION_ID | VERSION | JOB_INSTANCE_ID | CREATE_TIME                | START_TIME                 | END_TIME                   | STATUS    | EXIT_CODE | EXIT_MESSAGE | LAST_UPDATED               | JOB_CONFIGURATION_LOCATION |
+------------------+---------+-----------------+----------------------------+----------------------------+----------------------------+-----------+-----------+--------------+----------------------------+----------------------------+
|                1 |       2 |               1 | 2022-04-24 20:32:03.720000 | 2022-04-24 20:32:03.814000 | 2022-04-24 20:32:04.278000 | COMPLETED | COMPLETED |              | 2022-04-24 20:32:04.279000 | NULL                       |
+------------------+---------+-----------------+----------------------------+----------------------------+----------------------------+-----------+-----------+--------------+----------------------------+----------------------------+
1 row in set (0.00 sec)

mysql> select * from BATCH_JOB_EXECUTION_CONTEXT;
+------------------+--------------------------------+--------------------+
| JOB_EXECUTION_ID | SHORT_CONTEXT                  | SERIALIZED_CONTEXT |
+------------------+--------------------------------+--------------------+
|                1 | {"@class":"java.util.HashMap"} | NULL               |
+------------------+--------------------------------+--------------------+
1 row in set (0.00 sec)

mysql> select * from BATCH_JOB_EXECUTION_PARAMS;
+------------------+---------+----------+-----------------------------+----------------------------+----------+------------+-------------+
| JOB_EXECUTION_ID | TYPE_CD | KEY_NAME | STRING_VAL                  | DATE_VAL                   | LONG_VAL | DOUBLE_VAL | IDENTIFYING |
+------------------+---------+----------+-----------------------------+----------------------------+----------+------------+-------------+
|                1 | LONG    | run.id   |                             | 1970-01-01 09:00:00.000000 |        1 |          0 | Y           |
|                1 | STRING  | filePath | src/main/resources/book.csv | 1970-01-01 09:00:00.000000 |        0 |          0 | Y           |
+------------------+---------+----------+-----------------------------+----------------------------+----------+------------+-------------+
2 rows in set (0.00 sec)

mysql> select * from BATCH_JOB_EXECUTION_SEQ;
+----+------------+
| ID | UNIQUE_KEY |
+----+------------+
|  1 | 0          |
+----+------------+
1 row in set (0.00 sec)

mysql> select * from BATCH_JOB_INSTANCE;
+-----------------+---------+-----------------------+----------------------------------+
| JOB_INSTANCE_ID | VERSION | JOB_NAME              | JOB_KEY                          |
+-----------------+---------+-----------------------+----------------------------------+
|               1 |       0 | fileLoadToDatabaseJob | 2247a3263300ca04598c73ac33b0fb7e |
+-----------------+---------+-----------------------+----------------------------------+
1 row in set (0.00 sec)

mysql> select * from BATCH_JOB_SEQ;
+----+------------+
| ID | UNIQUE_KEY |
+----+------------+
|  1 | 0          |
+----+------------+
1 row in set (0.00 sec)

mysql> select * from BATCH_STEP_EXECUTION;
+-------------------+---------+------------------------+------------------+----------------------------+----------------------------+-----------+--------------+------------+--------------+-------------+-----------------+------------------+--------------------+----------------+-----------+--------------+----------------------------+
| STEP_EXECUTION_ID | VERSION | STEP_NAME              | JOB_EXECUTION_ID | START_TIME                 | END_TIME                   | STATUS    | COMMIT_COUNT | READ_COUNT | FILTER_COUNT | WRITE_COUNT | READ_SKIP_COUNT | WRITE_SKIP_COUNT | PROCESS_SKIP_COUNT | ROLLBACK_COUNT | EXIT_CODE | EXIT_MESSAGE | LAST_UPDATED               |
+-------------------+---------+------------------------+------------------+----------------------------+----------------------------+-----------+--------------+------------+--------------+-------------+-----------------+------------------+--------------------+----------------+-----------+--------------+----------------------------+
|                 1 |       6 | fileLoadToDatabaseStep |                1 | 2022-04-24 20:32:03.885000 | 2022-04-24 20:32:04.250000 | COMPLETED |            4 |         10 |            0 |          10 |               0 |                0 |                  0 |              0 | COMPLETED |              | 2022-04-24 20:32:04.252000 |
+-------------------+---------+------------------------+------------------+----------------------------+----------------------------+-----------+--------------+------------+--------------+-------------+-----------------+------------------+--------------------+----------------+-----------+--------------+----------------------------+
1 row in set (0.00 sec)

mysql> select * from BATCH_STEP_EXECUTION_CONTEXT;
+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
| STEP_EXECUTION_ID | SHORT_CONTEXT                                                                                                                                                                                 | SERIALIZED_CONTEXT |
+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
|                 1 | {"@class":"java.util.HashMap","batch.taskletType":"org.springframework.batch.core.step.item.ChunkOrientedTasklet","batch.stepType":"org.springframework.batch.core.step.tasklet.TaskletStep"} | NULL               |
+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
1 row in set (0.00 sec)

mysql> select * from BATCH_STEP_EXECUTION_SEQ;
+----+------------+
| ID | UNIQUE_KEY |
+----+------------+
|  1 | 0          |
+----+------------+
1 row in set (0.01 sec)

RunIdIncrementerを使用しているので、同じ起動パラメーターでもう1度Jobを起動できます。

$ java -Dspring.batch.job.names=fileLoadToDatabaseJob -jar target/batch-example-0.0.1-SNAPSHOT.jar filePath=src/main/resources/book.csv

この時、run.idは2になっています。

2022-04-24 20:33:56.256  INFO 36352 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=fileLoadToDatabaseJob]] launched with the following parameters: [{run.id=2, filePath=src/main/resources/book.csv}]

もし、RunIdIncrementerを削除して

    @Bean
    public Job fileLoadToDatabaseJob() {
        return jobBuilderFactory
                .get("fileLoadToDatabaseJob")
                // .incrementer(new RunIdIncrementer())
                .start(fileLoadToDatabaseStep())
                .build();
    }

同じJobParameter定義で起動しようとすると、すでに完了済みということでエラーになります。

Caused by: org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException: A job instance already exists and is complete for parameters={filePath=src/main/resources/book.csv}.  If you want to run this job again, change the parameters.

今回はこちらの定義ではないですが、JobParameterをそもそも使っていない場合は、完了したJobは以下の様に空振りします。

2022-04-24 21:09:56.925  INFO 38225 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Step already complete or not restartable, so no action to execute: StepExecution: id=8, version=6, name=fileLoadToDatabaseStep, status=COMPLETED, exitStatus=COMPLETED, readCount=10, filterCount=0, writeCount=10 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=4, rollbackCount=0, exitDescription=

Spring Batchが提供するItemReader、ItemWriterを使う

先ほどは、ItemReaderおよびItemWriterを自分で作成しました。

ですが、Spring BatchがItemReaderItemWriterの実装をいくつか提供しているので、こちらを使用すれば自分でItemReaderItemWriter
実装せずにJobを構築できたりもします。

List of ItemReaders and ItemWriters

今回であれば、FlatFileItemReaderJpaItemWriterが使えますね。

ItemReaders and ItemWriters / Flat Files

FlatFileItemReader (Spring Batch 4.3.5 API)

JpaItemWriter (Spring Batch 4.3.5 API)

JobおよびStepの定義はこちら。

src/main/java/org/littlewings/spring/batch/config/FileLoadToDatabaseSimplyJobConfig.java

package org.littlewings.spring.batch.config;

import java.time.format.DateTimeFormatter;
import javax.persistence.EntityManagerFactory;

import org.littlewings.spring.batch.chunk.LoggingBookProcessor;
import org.littlewings.spring.batch.entity.Book;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.database.JpaItemWriter;
import org.springframework.batch.item.database.builder.JpaItemWriterBuilder;
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.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
import org.springframework.format.support.DefaultFormattingConversionService;

@Configuration
public class FileLoadToDatabaseSimplyJobConfig {
    @Autowired
    JobBuilderFactory jobBuilderFactory;

    @Autowired
    StepBuilderFactory stepBuilderFactory;

    @Autowired
    EntityManagerFactory entityManagerFactory;

    @Bean
    public Job fileLoadToDatabaseSimplyJob() {
        return jobBuilderFactory
                .get("fileLoadToDatabaseSimplyJob")
                .incrementer(new RunIdIncrementer())
                .start(fileLoadToDatabaseSimplyStep())
                .build();
    }

    @Bean
    public Step fileLoadToDatabaseSimplyStep() {
        return stepBuilderFactory
                .get("fileLoadToDatabaseSimplyStep")
                .<Book, Book>chunk(3)
                .reader(flatFileBookItemReader(null, false))
                .processor(simpleLoggingBookProcessor())
                .writer(jpaBookItemWriter())
                .build();
    }

    @StepScope
    @Bean
    public FlatFileItemReader<Book> flatFileBookItemReader(
            @Value("#{jobParameters['filePath']}") String filePath,
            @Value("#{jobParameters['hasHeader'] ?: true}") boolean hasHeader
    ) {
        Resource fileResource = new FileSystemResource(filePath);

        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
        DateTimeFormatterRegistrar dateTimeFormatterRegistrar = new DateTimeFormatterRegistrar();
        dateTimeFormatterRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("uuuu-MM-dd"));
        dateTimeFormatterRegistrar.registerFormatters(conversionService);

        BeanWrapperFieldSetMapper<Book> fieldSetMapper = new BeanWrapperFieldSetMapper<>();
        fieldSetMapper.setConversionService(conversionService);
        fieldSetMapper.setTargetType(Book.class);

        try {
            fieldSetMapper.afterPropertiesSet();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return new FlatFileItemReaderBuilder<Book>()
                .name("flatFileBookItemReader")
                .resource(fileResource)
                .encoding("UTF-8")
                .delimited()
                .names(new String[]{"isbn", "title", "price", "publishDate"})
                .linesToSkip(hasHeader ? 1 : 0)
                .fieldSetMapper(fieldSetMapper)
                .saveState(false)
                .build();
    }

    @StepScope
    @Bean
    public LoggingBookProcessor simpleLoggingBookProcessor() {
        return new LoggingBookProcessor();
    }

    @StepScope
    @Bean
    public JpaItemWriter<Book> jpaBookItemWriter() {
        return new JpaItemWriterBuilder<Book>()
                .entityManagerFactory(entityManagerFactory)
                .build();
    }
}

ItemProcessorの方は、先ほどのものをそのまま使っています。

FlatFileItemReaderの構築はこちら。

    @StepScope
    @Bean
    public FlatFileItemReader<Book> flatFileBookItemReader(
            @Value("#{jobParameters['filePath']}") String filePath,
            @Value("#{jobParameters['hasHeader'] ?: true}") boolean hasHeader
    ) {
        Resource fileResource = new FileSystemResource(filePath);

        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
        DateTimeFormatterRegistrar dateTimeFormatterRegistrar = new DateTimeFormatterRegistrar();
        dateTimeFormatterRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("uuuu-MM-dd"));
        dateTimeFormatterRegistrar.registerFormatters(conversionService);

        BeanWrapperFieldSetMapper<Book> fieldSetMapper = new BeanWrapperFieldSetMapper<>();
        fieldSetMapper.setConversionService(conversionService);
        fieldSetMapper.setTargetType(Book.class);

        try {
            fieldSetMapper.afterPropertiesSet();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return new FlatFileItemReaderBuilder<Book>()
                .name("flatFileBookItemReader")
                .resource(fileResource)
                .encoding("UTF-8")
                .delimited()
                .names(new String[]{"isbn", "title", "price", "publishDate"})
                .linesToSkip(hasHeader ? 1 : 0)
                .fieldSetMapper(fieldSetMapper)
                .saveState(false)
                .build();
    }

FlatFileItemReaderは、FlatFileItemReaderBuilderで構築します。

        return new FlatFileItemReaderBuilder<Book>()
                .name("flatFileBookItemReader")
                .resource(fileResource)
                .encoding("UTF-8")
                .delimited()
                .names(new String[]{"isbn", "title", "price", "publishDate"})
                .linesToSkip(hasHeader ? 1 : 0)
                .fieldSetMapper(fieldSetMapper)
                .saveState(false)
                .build();

namesで各項目の名前を定義しておき、BeanWrapperFieldSetMapperBookマッピングします。BookのプロパティにLocalDate
使用したものもあるので、DefaultFormattingConversionServiceDateTimeFormatterRegistrarを使って日付をパースできるように
しています。

        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
        DateTimeFormatterRegistrar dateTimeFormatterRegistrar = new DateTimeFormatterRegistrar();
        dateTimeFormatterRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("uuuu-MM-dd"));
        dateTimeFormatterRegistrar.registerFormatters(conversionService);

        BeanWrapperFieldSetMapper<Book> fieldSetMapper = new BeanWrapperFieldSetMapper<>();
        fieldSetMapper.setConversionService(conversionService);
        fieldSetMapper.setTargetType(Book.class);

読み込むCSVファイルのヘッダーの有無は、JobParameterで指定できるようにしました。

    public FlatFileItemReader<Book> flatFileBookItemReader(
            @Value("#{jobParameters['filePath']}") String filePath,
            @Value("#{jobParameters['hasHeader'] ?: true}") boolean hasHeader
    ) {

デフォルトでヘッダー有りにしています。

JpaItemWriterは、JpaItemWriterBuilderで構築します。この時、EntityManagerFactoryが必要になります。

    @StepScope
    @Bean
    public JpaItemWriter<Book> jpaBookItemWriter() {
        return new JpaItemWriterBuilder<Book>()
                .entityManagerFactory(entityManagerFactory)
                .build();
    }

アプリケーションができたので、実行してみましょう。

最初に、データを削除しておきます。

mysql> truncate table book;
Query OK, 0 rows affected (0.34 sec)

mysql> select * from book;
Empty set (0.00 sec)

パッケージングして

$ mvn package

実行してみます。先ほどとはspring.batch.job.namesに指定するJob名を変更します。

$ java -Dspring.batch.job.names=fileLoadToDatabaseSimplyJob -jar target/batch-example-0.0.1-SNAPSHOT.jar filePath=src/main/resources/book_with_header.csv

ログ。

2022-04-24 20:48:35.470  INFO 37031 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: [filePath=src/main/resources/book_with_header.csv]
2022-04-24 20:48:35.682  INFO 37031 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=fileLoadToDatabaseSimplyJob]] launched with the following parameters: [{run.id=1, filePath=src/main/resources/book_with_header.csv}]
2022-04-24 20:48:35.817  INFO 37031 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [fileLoadToDatabaseSimplyStep]
2022-04-24 20:48:35.981  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4798142470, title = Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
2022-04-24 20:48:35.982  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4774182179, title = [改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ
2022-04-24 20:48:35.982  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-1492076988, title = Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications
2022-04-24 20:48:36.078  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-1484237236, title = The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud
2022-04-24 20:48:36.078  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4798161488, title = MySQL徹底入門 第4版 MySQL 8.0対応
2022-04-24 20:48:36.078  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4797393118, title = 基礎からのMySQL 第3版 (基礎からシリーズ)
2022-04-24 20:48:36.118  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4873116389, title = 実践ハイパフォーマンスMySQL 第3版
2022-04-24 20:48:36.118  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4295000198, title = やさしく学べるMySQL運用・管理入門【5.7対応】
2022-04-24 20:48:36.118  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4798147406, title = 詳解MySQL 5.7 止まらぬ 進化に乗り遅れないためのテクニカルガイド (NEXT ONE)
2022-04-24 20:48:36.160  INFO 37031 --- [           main] o.l.s.batch.chunk.LoggingBookProcessor   : [processor] process book: isbn = 978-4774170206, title = MariaDB&MySQL全機能バイブル
2022-04-24 20:48:36.208  INFO 37031 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [fileLoadToDatabaseSimplyStep] executed in 391ms
2022-04-24 20:48:36.298  INFO 37031 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=fileLoadToDatabaseSimplyJob]] completed with the following parameters: [{run.id=1, filePath=src/main/resources/book_with_header.csv}] and the following status: [COMPLETED] in 514ms

データが入りました。

mysql> select * from book;
+----------------+---------------------------------------------------------------------------------------------------------+-------+--------------+
| isbn           | title                                                                                                   | price | publish_date |
+----------------+---------------------------------------------------------------------------------------------------------+-------+--------------+
| 978-1484237236 | The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud                       |  7361 | 2019-07-09   |
| 978-1492076988 | Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications                         |  6265 | 2021-03-23   |
| 978-4295000198 | やさしく学べるMySQL運用・管理入門【5.7対応】                                                            |  2860 | 2016-12-15   |
| 978-4774170206 | MariaDB&MySQL全機能バイブル                                                                             |  3850 | 2014-12-18   |
| 978-4774182179 | [改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ                                 |  4180 | 2016-06-14   |
| 978-4797393118 | 基礎からのMySQL 第3版 (基礎からシリーズ)                                                                |  6038 | 2017-09-22   |
| 978-4798142470 | Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発                                           |  4400 | 2016-07-21   |
| 978-4798147406 | 詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE)                               |  3960 | 2016-08-26   |
| 978-4798161488 | MySQL徹底入門 第4版 MySQL 8.0対応                                                                       |  4180 | 2020-07-06   |
| 978-4873116389 | 実践ハイパフォーマンスMySQL 第3版                                                                       |  5280 | 2013-11-25   |
+----------------+---------------------------------------------------------------------------------------------------------+-------+--------------+
10 rows in set (0.00 sec)

今回のJobでヘッダーなしのファイルを読む時は、JobParameterであるhasHeaderfalseにします。

$ java -Dspring.batch.job.names=fileLoadToDatabaseSimplyJob -jar target/batch-example-0.0.1-SNAPSHOT.jar filePath=src/main/resources/book.csv hasHeader=false

Taskletを作成する

最後に、Taskletを作成してみます。

今回は、ログ出力のみなのでこんな感じで作成。

src/main/java/org/littlewings/spring/batch/tasklet/LoggingBookTasklet.java

package org.littlewings.spring.batch.tasklet;

import java.time.format.DateTimeFormatter;

import org.littlewings.spring.batch.repository.BookRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.StepContribution;
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.Autowired;
import org.springframework.data.domain.Sort;

public class LoggingBookTasklet implements Tasklet {
    Logger logger = LoggerFactory.getLogger(LoggingBookTasklet.class);

    @Autowired
    BookRepository bookRepository;

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        DateTimeFormatter publishDateFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd");

        bookRepository
                .findAll(Sort.by(Sort.Direction.DESC, "price"))
                .forEach(book -> logger.info(
                        "[tasklet] isbn = {}, title = {}, price = {}, publishDate = {}",
                        book.getIsbn(),
                        book.getTitle(),
                        book.getPrice(),
                        book.getPublishDate().format(publishDateFormatter)
                ));

        return RepeatStatus.FINISHED;
    }
}

JobおよびStep定義。

src/main/java/org/littlewings/spring/batch/config/LoggingDatabaseJobConfig.java

package org.littlewings.spring.batch.config;

import org.littlewings.spring.batch.tasklet.LoggingBookTasklet;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LoggingDatabaseJobConfig {
    @Autowired
    JobBuilderFactory jobBuilderFactory;

    @Autowired
    StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job loggingDatabaseJog() {
        return jobBuilderFactory
                .get("loggingDatabaseJob")
                .incrementer(new RunIdIncrementer())
                .start(loggingDatabaseStep())
                .build();
    }

    @Bean
    public Step loggingDatabaseStep() {
        return stepBuilderFactory
                .get("loggingDatabaseStep")
                .tasklet(loggingBookTasklet())
                .build();
    }

    @StepScope
    @Bean
    public LoggingBookTasklet loggingBookTasklet() {
        return new LoggingBookTasklet();
    }
}

特徴的なところは、Stepを構築する際にtaskletを指定していることですね。

    @Bean
    public Step loggingDatabaseStep() {
        return stepBuilderFactory
                .get("loggingDatabaseStep")
                .tasklet(loggingBookTasklet())
                .build();
    }

パッケージングして

$ mvn package

実行。

$ java -Dspring.batch.job.names=loggingDatabaseJob -jar target/batch-example-0.0.1-SNAPSHOT.jar

ログ。

2022-04-24 21:00:08.593  INFO 37435 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=loggingDatabaseJob]] launched with the following parameters: [{run.id=1}]
2022-04-24 21:00:08.697  INFO 37435 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [loggingDatabaseStep]
2022-04-24 21:00:08.990  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-1484237236, title = The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud, price = 7361, publishDate = 2019-07-09
2022-04-24 21:00:08.990  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-1492076988, title = Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications, price = 6265, publishDate = 2021-03-23
2022-04-24 21:00:08.990  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-4797393118, title = 基礎からのMySQL 第3版 (基礎からシリーズ), price = 6038, publishDate = 2017-09-22
2022-04-24 21:00:08.990  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-4873116389, title = 実践ハイパフォーマンスMySQL 第3版, price = 5280, publishDate = 2013-11-25
2022-04-24 21:00:08.990  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-4798142470, title = Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発, price = 4400, publishDate = 2016-07-21
2022-04-24 21:00:08.991  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-4774182179, title = [改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ, price = 4180, publishDate = 2016-06-14
2022-04-24 21:00:08.991  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-4798161488, title = MySQL徹底入門 第4版 MySQL 8.0対応, price = 4180, publishDate = 2020-07-06
2022-04-24 21:00:08.991  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-4798147406, title = 詳解MySQL 5.7 止まらぬ進化に乗り遅れな いためのテクニカルガイド (NEXT ONE), price = 3960, publishDate = 2016-08-26
2022-04-24 21:00:08.991  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-4774170206, title = MariaDB&MySQL全機能バイブル, price = 3850, publishDate = 2014-12-18
2022-04-24 21:00:08.991  INFO 37435 --- [           main] o.l.s.batch.tasklet.LoggingBookTasklet   : [tasklet] isbn = 978-4295000198, title = やさしく学べるMySQL運用・管理入門【5.7 対応】, price = 2860, publishDate = 2016-12-15
2022-04-24 21:00:09.041  INFO 37435 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [loggingDatabaseStep] executed in 343ms
2022-04-24 21:00:09.101  INFO 37435 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=loggingDatabaseJob]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED] in 454ms

これで、Taskletの方もOKですね。

JobRepositoryを作成するSQLについて

今回、spring.batch.jdbc.initialize-schemaalwaysにしていたのでSpring BatchのJobRepositoryで使うテーブルを自動で作成して
くれていましたが、そのSQLは以下のディレクトリにあるようです。

https://github.com/spring-projects/spring-batch/tree/4.3.5/spring-batch-core/src/main/resources/org/springframework/batch/core

ちなみに、ここまで実行した後の各テーブルの状態は、こんな感じになりました。

mysql> select * from BATCH_JOB_EXECUTION;
+------------------+---------+-----------------+----------------------------+----------------------------+----------------------------+-----------+-----------+--------------+----------------------------+----------------------------+
| JOB_EXECUTION_ID | VERSION | JOB_INSTANCE_ID | CREATE_TIME                | START_TIME                 | END_TIME                   | STATUS    | EXIT_CODE | EXIT_MESSAGE | LAST_UPDATED               | JOB_CONFIGURATION_LOCATION |
+------------------+---------+-----------------+----------------------------+----------------------------+----------------------------+-----------+-----------+--------------+----------------------------+----------------------------+
|                1 |       2 |               1 | 2022-04-24 20:32:03.720000 | 2022-04-24 20:32:03.814000 | 2022-04-24 20:32:04.278000 | COMPLETED | COMPLETED |              | 2022-04-24 20:32:04.279000 | NULL                       |
|                2 |       2 |               2 | 2022-04-24 20:33:56.195000 | 2022-04-24 20:33:56.274000 | 2022-04-24 20:33:56.720000 | COMPLETED | COMPLETED |              | 2022-04-24 20:33:56.720000 | NULL                       |
|                3 |       2 |               3 | 2022-04-24 20:48:35.586000 | 2022-04-24 20:48:35.731000 | 2022-04-24 20:48:36.245000 | COMPLETED | COMPLETED |              | 2022-04-24 20:48:36.246000 | NULL                       |
|                4 |       2 |               4 | 2022-04-24 20:49:52.658000 | 2022-04-24 20:49:52.761000 | 2022-04-24 20:49:53.241000 | COMPLETED | COMPLETED |              | 2022-04-24 20:49:53.242000 | NULL                       |
|                5 |       2 |               5 | 2022-04-24 21:00:08.514000 | 2022-04-24 21:00:08.618000 | 2022-04-24 21:00:09.072000 | COMPLETED | COMPLETED |              | 2022-04-24 21:00:09.073000 | NULL                       |
+------------------+---------+-----------------+----------------------------+----------------------------+----------------------------+-----------+-----------+--------------+----------------------------+----------------------------+
5 rows in set (0.00 sec)

mysql> select * from BATCH_JOB_EXECUTION_CONTEXT;
+------------------+--------------------------------+--------------------+
| JOB_EXECUTION_ID | SHORT_CONTEXT                  | SERIALIZED_CONTEXT |
+------------------+--------------------------------+--------------------+
|                1 | {"@class":"java.util.HashMap"} | NULL               |
|                2 | {"@class":"java.util.HashMap"} | NULL               |
|                3 | {"@class":"java.util.HashMap"} | NULL               |
|                4 | {"@class":"java.util.HashMap"} | NULL               |
|                5 | {"@class":"java.util.HashMap"} | NULL               |
+------------------+--------------------------------+--------------------+
5 rows in set (0.01 sec)

mysql> select * from BATCH_JOB_EXECUTION_PARAMS;
+------------------+---------+-----------+-----------------------------------------+----------------------------+----------+------------+-------------+
| JOB_EXECUTION_ID | TYPE_CD | KEY_NAME  | STRING_VAL                              | DATE_VAL                   | LONG_VAL | DOUBLE_VAL | IDENTIFYING |
+------------------+---------+-----------+-----------------------------------------+----------------------------+----------+------------+-------------+
|                1 | LONG    | run.id    |                                         | 1970-01-01 09:00:00.000000 |        1 |          0 | Y           |
|                1 | STRING  | filePath  | src/main/resources/book.csv             | 1970-01-01 09:00:00.000000 |        0 |          0 | Y           |
|                2 | LONG    | run.id    |                                         | 1970-01-01 09:00:00.000000 |        2 |          0 | Y           |
|                2 | STRING  | filePath  | src/main/resources/book.csv             | 1970-01-01 09:00:00.000000 |        0 |          0 | Y           |
|                3 | LONG    | run.id    |                                         | 1970-01-01 09:00:00.000000 |        1 |          0 | Y           |
|                3 | STRING  | filePath  | src/main/resources/book_with_header.csv | 1970-01-01 09:00:00.000000 |        0 |          0 | Y           |
|                4 | LONG    | run.id    |                                         | 1970-01-01 09:00:00.000000 |        2 |          0 | Y           |
|                4 | STRING  | hasHeader | false                                   | 1970-01-01 09:00:00.000000 |        0 |          0 | Y           |
|                4 | STRING  | filePath  | src/main/resources/book.csv             | 1970-01-01 09:00:00.000000 |        0 |          0 | Y           |
|                5 | LONG    | run.id    |                                         | 1970-01-01 09:00:00.000000 |        1 |          0 | Y           |
+------------------+---------+-----------+-----------------------------------------+----------------------------+----------+------------+-------------+
10 rows in set (0.00 sec)

mysql> select * from BATCH_JOB_EXECUTION_SEQ;
+----+------------+
| ID | UNIQUE_KEY |
+----+------------+
|  5 | 0          |
+----+------------+
1 row in set (0.00 sec)

mysql> select * from BATCH_JOB_INSTANCE;
+-----------------+---------+-----------------------------+----------------------------------+
| JOB_INSTANCE_ID | VERSION | JOB_NAME                    | JOB_KEY                          |
+-----------------+---------+-----------------------------+----------------------------------+
|               1 |       0 | fileLoadToDatabaseJob       | 2247a3263300ca04598c73ac33b0fb7e |
|               2 |       0 | fileLoadToDatabaseJob       | 1062f6824a3419e049c6d95f6e5e99c2 |
|               3 |       0 | fileLoadToDatabaseSimplyJob | 22f79c44e978cc2d028116a60ed9fdd5 |
|               4 |       0 | fileLoadToDatabaseSimplyJob | 27f07cdb7c5117f846c1f6eec03d26de |
|               5 |       0 | loggingDatabaseJob          | 853d3449e311f40366811cbefb3d93d7 |
+-----------------+---------+-----------------------------+----------------------------------+
5 rows in set (0.00 sec)

mysql> select * from BATCH_JOB_SEQ;
+----+------------+
| ID | UNIQUE_KEY |
+----+------------+
|  5 | 0          |
+----+------------+
1 row in set (0.00 sec)

mysql> select * from BATCH_STEP_EXECUTION;
+-------------------+---------+------------------------------+------------------+----------------------------+----------------------------+-----------+--------------+------------+--------------+-------------+-----------------+------------------+--------------------+----------------+-----------+--------------+----------------------------+
| STEP_EXECUTION_ID | VERSION | STEP_NAME                    | JOB_EXECUTION_ID | START_TIME                 | END_TIME                   | STATUS    | COMMIT_COUNT | READ_COUNT | FILTER_COUNT | WRITE_COUNT | READ_SKIP_COUNT | WRITE_SKIP_COUNT | PROCESS_SKIP_COUNT | ROLLBACK_COUNT | EXIT_CODE | EXIT_MESSAGE | LAST_UPDATED               |
+-------------------+---------+------------------------------+------------------+----------------------------+----------------------------+-----------+--------------+------------+--------------+-------------+-----------------+------------------+--------------------+----------------+-----------+--------------+----------------------------+
|                 1 |       6 | fileLoadToDatabaseStep       |                1 | 2022-04-24 20:32:03.885000 | 2022-04-24 20:32:04.250000 | COMPLETED |            4 |         10 |            0 |          10 |               0 |                0 |                  0 |              0 | COMPLETED |              | 2022-04-24 20:32:04.252000 |
|                 2 |       6 | fileLoadToDatabaseStep       |                2 | 2022-04-24 20:33:56.364000 | 2022-04-24 20:33:56.690000 | COMPLETED |            4 |         10 |            0 |          10 |               0 |                0 |                  0 |              0 | COMPLETED |              | 2022-04-24 20:33:56.692000 |
|                 3 |       6 | fileLoadToDatabaseSimplyStep |                3 | 2022-04-24 20:48:35.817000 | 2022-04-24 20:48:36.208000 | COMPLETED |            4 |         10 |            0 |          10 |               0 |                0 |                  0 |              0 | COMPLETED |              | 2022-04-24 20:48:36.209000 |
|                 4 |       6 | fileLoadToDatabaseSimplyStep |                4 | 2022-04-24 20:49:52.851000 | 2022-04-24 20:49:53.207000 | COMPLETED |            4 |         10 |            0 |          10 |               0 |                0 |                  0 |              0 | COMPLETED |              | 2022-04-24 20:49:53.208000 |
|                 5 |       3 | loggingDatabaseStep          |                5 | 2022-04-24 21:00:08.698000 | 2022-04-24 21:00:09.041000 | COMPLETED |            1 |          0 |            0 |           0 |               0 |                0 |                  0 |              0 | COMPLETED |              | 2022-04-24 21:00:09.042000 |
+-------------------+---------+------------------------------+------------------+----------------------------+----------------------------+-----------+--------------+------------+--------------+-------------+-----------------+------------------+--------------------+----------------+-----------+--------------+----------------------------+
5 rows in set (0.00 sec)

mysql> select * from BATCH_STEP_EXECUTION_CONTEXT;
+-------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
| STEP_EXECUTION_ID | SHORT_CONTEXT                                                                                                                                                                                                            | SERIALIZED_CONTEXT |
+-------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
|                 1 | {"@class":"java.util.HashMap","batch.taskletType":"org.springframework.batch.core.step.item.ChunkOrientedTasklet","batch.stepType":"org.springframework.batch.core.step.tasklet.TaskletStep"}                            | NULL               |
|                 2 | {"@class":"java.util.HashMap","batch.taskletType":"org.springframework.batch.core.step.item.ChunkOrientedTasklet","batch.stepType":"org.springframework.batch.core.step.tasklet.TaskletStep"}                            | NULL               |
|                 3 | {"@class":"java.util.HashMap","batch.taskletType":"org.springframework.batch.core.step.item.ChunkOrientedTasklet","batch.stepType":"org.springframework.batch.core.step.tasklet.TaskletStep"}                            | NULL               |
|                 4 | {"@class":"java.util.HashMap","batch.taskletType":"org.springframework.batch.core.step.item.ChunkOrientedTasklet","batch.stepType":"org.springframework.batch.core.step.tasklet.TaskletStep"}                            | NULL               |
|                 5 | {"@class":"java.util.HashMap","batch.taskletType":"org.littlewings.spring.batch.tasklet.LoggingBookTasklet$$EnhancerBySpringCGLIB$$7acedf7a","batch.stepType":"org.springframework.batch.core.step.tasklet.TaskletStep"} | NULL               |
+-------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+
5 rows in set (0.00 sec)

mysql> select * from BATCH_STEP_EXECUTION_SEQ;
+----+------------+
| ID | UNIQUE_KEY |
+----+------------+
|  5 | 0          |
+----+------------+
1 row in set (0.00 sec)

まとめ

Spring Batchを初めて使ってみました。

jBatchを使ったり、情報は見たことがあったのでざっくりと知っているつもりでしたが、実際にソースコードを書いて動かそうとすると
いろいろハマりまして…。

今回試してみて、少しは感覚がわかったかなと思います。