LoginSignup
2
1

More than 1 year has passed since last update.

Spring Batch初期構築 ~JobRepositoryの保存先設定から実行まで~

Last updated at Posted at 2022-08-04

はじめに

業務でバッチアプリケーションの開発を行うことになったため、まずは動くバッチアプリを作るぞ!ということで、
こちらのQiitaの記事を参考に進めていました。(以降、この記事内の工程をやった前提で進みます)
まずは実践、Spring Boot Batchの動かし方

しかし開発を進めたところ、JobRepositoryの保存先の設定や起動の箇所で躓いてしまい、かなりてこずったので、
本記事ではJobRepositoryの保存先の設定と実行手順について説明しようと思います。

※上記の記事は、基本的なSpring Batchについての概念も丁寧に説明されていますので、
公式リファレンスと一緒に参考されるとよいかと思います。

前提

今回紹介する内容は、基本的にこちらで解説されている内容 +α の部分になります。
まずは実践、Spring Boot Batchの動かし方
記事を読まれる方は実際にこちらを試し、同じ場所で詰まった...という時に参考いただければと思います。

また、私の場合はJobRepositoryの保存先としてh2ではなくMySQLにしていますので、こちらについてもご了承ください。
datasourceのymlのMySQLバージョンは後述。

バージョン

使用技術 バージョン
Spring Boot 2.7.2
spring-boot-starter-batch 2.7.2
Java 11
MySQL 8.0.30

見本にした記事と違うところとしては、JobRepositoryの保存先としてh2ではなくMySQLにしたことです。

依存関係

pom.xml
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
    </dependency>

プロパティの設定

application.yml
spring:
  datasource:
    url: "jdbc:mysql://localhost/[DB名]"
    username: 
    password: 
    driver-class-name: com.mysql.cj.jdbc.Driver

※username, passwordは構築した時に設定した値

MySQLなどの環境構築は記事がたくさんあるので、そちらを参考ください。

本編

ここから本編に入ります。

一度実行してみる

TaskletやJob, Stepの実装を終え、プロパティでJobRepositoryの保存先になるDBの情報を定義したところで一度実行してみます。
実行方法ですが、私はEclipseで行っていたので、Spring Boot アプリケーションの実行にて行いました。

java.lang.IllegalStateException: Failed to execute ApplicationRunner

~省略~

Caused by: org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?]; nested exception is java.sql.SQLSyntaxErrorException: Table 'upload_job_execution.batch_job_instance' doesn't exist

スタックトレースでは 「SQLを発行しようとしたけどテーブルが存在しませんよ。」 と出力されています。
当たり前ですね。まだテーブル作成のDDLを流していないからです。

ひとまずこの時点で、実行するとジョブやステップの処理が進み、JobRepositoryがプロパティで定義した保存先に格納しようと

ちなみにJobRepositoryとは?

簡単に言うと、バッチアプリケーション実行結果の情報を永続化させる機能を提供する仕組みのことです。

JobExecutionやStepExecutionなどのバッチアプリケーション実行結果や状態を管理するためのデータを管理、永続化する機能を提供する。

続き...

一般的なバッチアプリケーションはJavaプロセスを起動することで処理が開始し、処理の終了ともにJavaプロセスも終了させるケースが多い。 そのためこれらのデータはJavaプロセスを跨いで参照される可能性があることから、揮発性なメモリ上だけではなくデータベースなどの永続層へ格納する。 データベースに格納する場合は、JobExecutionやStepExecutionを格納するためのテーブルやシーケンスなどのデータベースオブジェクトが必要になる。
Spring Batch が提供するスキーマ情報を元にデータベースオブジェクトを生成する必要がある。

引用:Spring Batchのアーキテクチャ terasoluna-batch.github.io

JobRepositoryの保存先設定

スタックトレースで出力されていた通り、テーブルを用意します。
DDLはこちらで公開されているものを使ってみようとしました。

Spring Batch JobRepository用 DDL

が、古いバージョン用のため、カラムのDEFAULT値などが違うと怒られてしまいます。
なので、下記を参考にDDLを修正しました。

stackoverflow MySQL用のSpring Batch 3 アップグレードスクリプト

JobRepository DDL
CREATE TABLE BATCH_JOB_INSTANCE  (
	JOB_INSTANCE_ID BIGINT  NOT NULL PRIMARY KEY ,
	VERSION BIGINT ,
	JOB_NAME VARCHAR(100) NOT NULL,
	JOB_KEY VARCHAR(32) NOT NULL,
	constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY)
) ENGINE=InnoDB;

CREATE TABLE BATCH_JOB_EXECUTION  (
	JOB_EXECUTION_ID BIGINT  NOT NULL PRIMARY KEY ,
	VERSION BIGINT  ,
	JOB_INSTANCE_ID BIGINT NOT NULL,
	CREATE_TIME DATETIME(6) NOT NULL,
	START_TIME DATETIME(6) DEFAULT NULL ,
	END_TIME DATETIME(6) DEFAULT NULL ,
	STATUS VARCHAR(10) ,
	EXIT_CODE VARCHAR(2500) DEFAULT NULL,
	EXIT_MESSAGE VARCHAR(2500) ,
	LAST_UPDATED DATETIME(6),
	JOB_CONFIGURATION_LOCATION varchar(2500) DEFAULT NULL,
	constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID)
	references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_JOB_EXECUTION_PARAMS  (
	JOB_EXECUTION_ID BIGINT NOT NULL ,
	TYPE_CD VARCHAR(6) NOT NULL ,
	KEY_NAME VARCHAR(100) NOT NULL ,
	STRING_VAL VARCHAR(250) ,
	DATE_VAL DATETIME(6) DEFAULT NULL ,
	LONG_VAL BIGINT ,
	DOUBLE_VAL DOUBLE PRECISION ,
	IDENTIFYING CHAR(1) NOT NULL ,
	constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_STEP_EXECUTION  (
	STEP_EXECUTION_ID BIGINT  NOT NULL PRIMARY KEY ,
	VERSION BIGINT NOT NULL,
	STEP_NAME VARCHAR(100) NOT NULL,
	JOB_EXECUTION_ID BIGINT NOT NULL,
	CREATE_TIME DATETIME(6) DEFAULT NULL,
	START_TIME DATETIME(6) DEFAULT NULL ,
	END_TIME DATETIME(6) DEFAULT NULL ,
	STATUS VARCHAR(10) ,
	COMMIT_COUNT BIGINT ,
	READ_COUNT BIGINT ,
	FILTER_COUNT BIGINT ,
	WRITE_COUNT BIGINT ,
	READ_SKIP_COUNT BIGINT ,
	WRITE_SKIP_COUNT BIGINT ,
	PROCESS_SKIP_COUNT BIGINT ,
	ROLLBACK_COUNT BIGINT ,
	EXIT_CODE VARCHAR(2500) DEFAULT NULL,
	EXIT_MESSAGE VARCHAR(2500) ,
	LAST_UPDATED DATETIME(6),
	constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT  (
	STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
	SHORT_CONTEXT VARCHAR(2500) NOT NULL,
	SERIALIZED_CONTEXT TEXT ,
	constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
	references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT  (
	JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
	SHORT_CONTEXT VARCHAR(2500) NOT NULL,
	SERIALIZED_CONTEXT TEXT ,
	constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

CREATE TABLE BATCH_STEP_EXECUTION_SEQ (
	ID BIGINT NOT NULL,
	UNIQUE_KEY CHAR(1) NOT NULL,
	constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_EXECUTION_SEQ (
	ID BIGINT NOT NULL,
	UNIQUE_KEY CHAR(1) NOT NULL,
	constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_SEQ (
	ID BIGINT NOT NULL,
	UNIQUE_KEY CHAR(1) NOT NULL,
	constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ);

バッチ処理実行

JobRepositoryの保存先となるテーブルの準備ができたので、実行すると成功しました。

この段階でエラーになる場合

Spring Batchのバージョン違いの理由でDDLの内容に不備がある場合があり得ますので、
スタックトレースの内容を読んでSQLException関連の内容であれば、対応することで実行できると思います。

ジョブの再実行

一度ジョブを実行し、成功するとテーブルにジョブの実行情報が格納されます。[1レコード目]
このまま開発を進めていく中で再度実行しようとすると、2レコード目のように
「すべてのステップがすでに完了しています」 とメッセージが格納されていることがわかります。
また、実行時には例外をスローします。
image.png

これを解決する方法としては、実行の度にテーブルを初期化することです。
開発段階では一旦このように、消してしまっても問題ありません。
ということで、実行の度にテーブルを初期化するよう設定します。

実行時のテーブル初期化設定

まず初期化用のSQLを/src/main/resources配下に配置します。ファイル名はなんでも良いです。
今回はjob_execution_init.sqlにしました。

job_execution_init.sql
DELETE FROM BATCH_STEP_EXECUTION_CONTEXT;
DELETE FROM BATCH_JOB_EXECUTION_CONTEXT;
DELETE FROM BATCH_JOB_EXECUTION_PARAMS;
DELETE FROM BATCH_STEP_EXECUTION;
DELETE FROM BATCH_JOB_EXECUTION;
DELETE FROM BATCH_JOB_INSTANCE;

他の記事によく記載されていた、Drop TABLEやtruncateは外部キー制約があるので消せない!
とエラーが出ましたので、DELETE文にしました。

次に、プロパティファイルに

  • mode: always
    alwaysとすることで、常にテーブル初期化処理を行います。
  • schema-locations
    初期化用のSQLの配置先を定義します。/src/main/resources配下であればこの書き方で一旦良いです。
application.yml
  # 実行の度にDB初期化
  sql:
    init:
      mode: always
      schema-locations:
      - classpath:job_execution_init.sql

・補足
spring.datasource.initialization-modeは現在、非推奨(廃止予定)になっています。

これで再度実行すると、
処理前にテーブル初期化 → バッチ処理 → JobRepositoryが実行情報を保存
の順に処理され、実行を繰り返し行えるようになります。

まとめ

JobRepositoryの保存先になるDBにはテーブルを作っておく。
DDLは最新でない可能性があることを認識しておく。
再度実行のために、テーブル内データの初期化処理を作成し、プロパティで設定しておく。

参考

Spring Batch 公式リファレンス
Spring Boot リファレンスドキュメント
JobRepository の設定
ジョブの状態管理 Entityやテーブル名の情報
初期化時のプロパティ設定など

2
1
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
2
1