前提
チュートリアルの進め方で説明しているとおり、
データベースアクセスでデータ入出力を行うジョブ、ファイルアクセスでデータ入出力を行うジョブに対して、
本ジョブの実装を追加していく形式とする。 |
概要
入力データの妥当性検証(以降、入力チェックと呼ぶ)を行うジョブを作成する。
なお、詳細についてはMacchinetta Batch 2.x 開発ガイドラインの入力チェックを参照。
作成するアプリケーションの説明の 背景、処理概要、業務仕様を以下に再掲する。
背景
とある量販店では、会員に対してポイントカードを発行している。
会員には「ゴールド会員」「一般会員」の会員種別が存在し、会員種別に応じたサービスを提供している。
今回そのサービスの一環として、月内に商品を購入した会員のうち、
会員種別が「ゴールド会員」の場合は100ポイント、「一般会員」の場合は10ポイントを月末に加算することにした。
処理概要
会員種別に応じてポイント加算を行うアプリケーションを
月次バッチ処理としてMacchinetta Batch 2.xを使用して実装する。
入力データにポイントの上限値を超えるデータが存在するか妥当性検証を行う処理を追加実装する。
業務仕様
業務仕様は以下のとおり。
-
入力データのポイントが1,000,000ポイントを超過していないことをチェックする
-
チェックエラーとなる場合は、処理を異常終了する(例外ハンドリングは行わない)
-
-
商品購入フラグが"1"(処理対象)の場合に、会員種別に応じてポイントを加算する
-
会員種別が"G"(ゴールド会員)の場合は100ポイント、"N"(一般会員)の場合は10ポイント加算する
-
-
商品購入フラグはポイント加算後に"0"(初期状態)に更新する
-
ポイントの上限値は1,000,000ポイントとする
-
ポイント加算後に1,000,000ポイントを超えた場合は、1,000,000ポイントに補正する
テーブル仕様
入出力リソースとなる会員情報テーブルの仕様は以下のとおり。
前提のとおりデータベースアクセスするジョブの場合の説明となるため、ファイルアクセスするジョブの場合の
入出力のリソース仕様はファイル仕様を参照。
No | 属性名 | カラム名 | PK | データ型 | 桁数 | 説明 |
---|---|---|---|---|---|---|
1 |
会員番号 |
id |
CHAR |
8 |
会員を一意に示す8桁固定の番号を表す。 |
|
2 |
会員種別 |
type |
- |
CHAR |
1 |
会員の種別を以下のとおり表す。 |
3 |
商品購入フラグ |
status |
- |
CHAR |
1 |
月内に商品を買ったかどうかを表す。 |
4 |
ポイント |
point |
- |
INT |
7 |
会員の保有するポイントを表す。 |
ジョブの概要
ここで作成する入力チェックを行うジョブの概要を把握するために、 処理フローおよび処理シーケンスを以下に示す。
前提のとおりデータベースアクセスするジョブの場合の説明となるため、 ファイルアクセスするジョブの場合の処理フローおよび処理シーケンスとは異なる部分があるため留意する。
入力チェックは、単項目チェック、相関項目チェックに分類されるが、ここでは単項目チェックのみを扱う。
単項目チェックは、Bean Validationを利用する。
詳細は入力チェックの分類を参照。
- 処理フロー概要
-
処理フローの概要を以下に示す。
- チャンクモデルの場合の処理シーケンス
-
チャンクモデルの場合の処理シーケンスを説明する。
本ジョブは異常系データを利用することを前提として説明しているため、 このシーケンス図は入力チェックでエラー(異常終了)となった場合を示している。
入力チェックが正常の場合、入力チェック以降の処理シーケンスはデータベースアクセスのシーケンス図 (ジョブの概要を参照)と同じである。
チャンクモデルの場合、入力チェックはItemProcessor
にデータが渡されたタイミングで行う。
橙色のオブジェクトは今回実装するクラスを表す。
-
ジョブからステップが実行される。
-
ステップは、リソースをオープンする。
-
MyBatisCursorItemReader
は、member_infoテーブルから会員情報をすべて取得(select文の発行)する。-
入力データがなくなるまで、以降の処理を繰り返す。
-
チャンク単位で、フレームワークトランザクションを開始する。
-
チャンクサイズに達するまで4から12までの処理を繰り返す。
-
-
ステップは、
MyBatisCursorItemReader
から入力データを1件取得する。 -
MyBatisCursorItemReader
は、member_infoテーブルから入力データを1件取得する。 -
member_infoテーブルは、
MyBatisCursorItemReader
に入力データを返却する。 -
MyBatisCursorItemReader
は、ステップに入力データを返却する。 -
ステップは、
PointAddItemProcessor
で入力データに対して処理を行う。 -
PointAddItemProcessor
は、SpringValidator
に入力チェック処理を依頼する。 -
SpringValidator
は、入力チェックルールに基づき入力チェックを行い、チェックエラーの場合は例外(ValidationException)をスローする。 -
PointAddItemProcessor
は、入力データを読み込んでポイント加算処理を行う。 -
PointAddItemProcessor
は、ステップに処理結果を返却する。 -
ステップは、チャンクサイズ分のデータを
MyBatisBatchItemWriter
で出力する。 -
MyBatisBatchItemWriter
は、member_infoテーブルに対して会員情報の更新(update文の発行)を行う。-
4から14までの処理過程で例外が発生すると、以降の処理を行う。
-
-
ステップはフレームワークトランザクションをロールバックする。
-
ステップはジョブに終了コード(ここでは異常終了:255)を返却する。
- タスクレットモデルの場合の処理シーケンス
-
タスクレットモデルの場合の処理シーケンスについて説明する。
本ジョブは異常系データを利用することを前提として説明しているため、 このシーケンス図は入力チェックでエラー(異常終了)となった場合を示している。
入力チェックが正常の場合、入力チェック以降の処理シーケンスはデータベースアクセスのシーケンス図 (ジョブの概要を参照)と同じである。
タスクレットモデルの場合、入力チェックはTasklet#execute()
にて任意のタイミングで行う。
ここでは、データを取得した直後に行っている。
橙色のオブジェクトは今回実装するクラスを表す。
-
ジョブからステップが実行される。
-
ステップはフレームワークトランザクションを開始する。
-
-
ステップは
PointAddTasklet
を実行する。 -
PointAddTasklet
は、リソースをオープンする。 -
MyBatisCursorItemReader
は、member_infoテーブルから会員情報をすべて取得(select文の発行)する。-
入力データがなくなるまで5から13までの処理を繰り返す。
-
一定件数に達するまで5から11までの処理を繰り返す。
-
-
PointAddTasklet
は、MyBatisCursorItemReader
から入力データを1件取得する。 -
MyBatisCursorItemReader
は、member_infoテーブルから入力データを1件取得する。 -
member_infoテーブルは、
MyBatisCursorItemReader
に入力データを返却する。 -
MyBatisCursorItemReader
は、タスクレットに入力データを返却する。 -
PointAddTasklet
は、SpringValidator
に入力チェック処理を依頼する。 -
SpringValidator
は、入力チェックルールに基づき入力チェックを行い、チェックエラーの場合は例外(ValidationException)をスローする。 -
PointAddTasklet
は、入力データを読み込んでポイント加算処理を行う。 -
PointAddTasklet
は、一定件数分のデータをMyBatisBatchItemWriter
で出力する。 -
MyBatisBatchItemWriter
は、member_infoテーブルに対して会員情報の更新(update文の発行)を行う。-
2から13までの処理過程で例外が発生すると、以降の処理を行う。
-
-
PointAddTasklet
はステップへ例外(ここではValidationException)をスローする。 -
ステップはフレームワークトランザクションをロールバックする。
-
ステップはジョブに終了コード(ここでは異常終了:255)を返却する。
入力チェック処理を実装するための設定
入力チェックにはHibernate Validatorを使用する。ブランクプロジェクトには既に設定済みであるが、 ライブラリの依存関係にHibernate Validatorの定義、およびBean定義が必要となる。 依存ライブラリの設定例(pom.xml)
src/main/resources/META-INF/spring/launch-context.xml
|
以降で、チャンクモデル、タスクレットモデルそれぞれの実装方法を説明する。
チャンクモデルでの実装
チャンクモデルで入力チェックを行うジョブの作成から実行までを以下の手順で実施する。
入力チェックルールの定義
入力チェックを行うために、DTOクラスのチェック対象のフィールドにBean Validationのアノテーションを付与する。
入力チェック用のアノテーションについては、Macchinetta Server 1.x 開発ガイドラインのBean Validationのチェックルール
およびHibernate Validatorのチェックルールを参照。
チャンクモデル/タスクレットモデルで共通して利用するため、既に実施している場合は読み飛ばしてよい。
ここでは、ポイントが1,000,000(上限値)を超過していないかチェックするためのチェックルールを定義する。
package com.example.batch.tutorial.common.dto;
import javax.validation.constraints.Max;
public class MemberInfoDto {
private String id;
private String type;
private String status;
@Max(1000000) // (1)
private int point;
// Getter and setter are omitted.
}
項番 | 説明 |
---|---|
(1) |
対象のフィールドが指定した数値以下であることを示す@Maxアノテーションを付与する。 |
入力チェック処理の実装
ポイント加算処理を行うビジネスロジッククラスに入力チェック処理を実装する。
既に実装してあるPointAddItemProcessor
クラスに入力チェック処理の実装を追加する。
前提のとおりデータベースアクセスするジョブの場合の説明となるため、ファイルアクセスするジョブの場合の
実装は以下の(1)~(3)のみ追加する。
// Package and the other import are omitted.
import javax.inject.Inject;
import org.springframework.batch.item.validator.Validator;
@Component
public class PointAddItemProcessor implements ItemProcessor<MemberInfoDto, MemberInfoDto> {
// Definition of constans are omitted.
@Inject // (1)
Validator<MemberInfoDto> validator; // (2)
@Override
public MemberInfoDto process(MemberInfoDto item) throws Exception {
validator.validate(item); // (3)
// The other codes of bussiness logic are omitted.
}
}
項番 | 説明 |
---|---|
(1) |
|
(2) |
|
(3) |
|
ジョブの実行と結果の確認
作成したジョブをSTS上で実行し、結果を確認する。
実行構成からジョブを実行
既に作成してある実行構成から、ジョブを実行する。
ここでは、異常系データを利用してジョブを実行する。
入力チェックを実装したジョブが扱うリソース(データベース or ファイル)によって
入力データの切替方法が異なるため、以下のとおり実行すること。
- データベースアクセスでデータ入出力を行うジョブに対して入力チェックを実装した場合
-
データベースアクセスでデータ入出力を行うジョブの実行構成からジョブを実行 で作成した実行構成を使ってジョブを実行する。
異常系データを利用するために、batch-application.properties
のDatabase Initializeで
正常系データのスクリプトをコメントアウトし、異常系データのスクリプトのコメントアウトを解除する。
# Database Initialize
tutorial.create-table.script=file:sqls/create-member-info-table.sql
#tutorial.insert-data.script=file:sqls/insert-member-info-data.sql
tutorial.insert-data.script=file:sqls/insert-member-info-error-data.sql
- ファイルアクセスでデータ入出力を行うジョブに対して入力チェックを実装した場合
-
ファイルアクセスでデータ入出力を行うジョブの実行構成からジョブを実行 で作成した実行構成を使ってジョブを実行する。
異常系データを利用するために、実行構成で設定する引数のうち、 入力ファイル(inputFile)のパスを正常系データ(insert-member-info-data.csv)から異常系データ(insert-member-info-error-data.csv)に変更する。
コンソールログの確認
Console Viewを開き、以下の内容のログが出力されていることを確認する。
-
処理が異常終了(FAILED)していること。
-
org.springframework.batch.item.validator.ValidationException
が発生していること。
(.. omitted)
[2020/03/10 16:04:18] [main] [o.s.b.c.l.s.SimpleJobLauncher] [INFO ] Job: [FlowJob: [name=jobPointAddChunk]] launched with the following parameters: [{jsr_batch_run_id=140}]
[2020/03/10 16:04:18] [main] [o.s.b.c.j.SimpleStepHandler] [INFO ] Executing step: [jobPointAddChunk.step01]
[2020/03/10 16:04:18] [main] [o.s.b.c.s.AbstractStep] [ERROR] Encountered an error executing step jobPointAddChunk.step01 in job jobPointAddChunk
org.springframework.batch.item.validator.ValidationException: Validation failed for com.example.batch.tutorial.common.dto.MemberInfoDto@2b1cd7bc:
Field error in object 'item' on field 'point': rejected value [1000001]; codes [Max.item.point,Max.point,Max.int,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [item.point,point]; arguments []; default message [point],1000000]; default message [must be less than or equal to 1000000]
at org.springframework.batch.item.validator.SpringValidator.validate(SpringValidator.java:54)
(.. omitted)
Caused by: org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'item' on field 'point': rejected value [1000001]; codes [Max.item.point,Max.point,Max.int,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [item.point,point]; arguments []; default message [point],1000000]; default message [must be less than or equal to 1000000]
... 29 common frames omitted
[2020/03/10 16:04:18] [main] [o.s.b.c.s.AbstractStep] [INFO ] Step: [jobPointAddChunk.step01] executed in 243ms
[2020/03/10 16:04:18] [main] [o.s.b.c.l.s.SimpleJobLauncher] [INFO ] Job: [FlowJob: [name=jobPointAddChunk]] completed with the following parameters: [{jsr_batch_run_id=140}] and the following status: [FAILED] in 319ms
終了コードの確認
終了コードにより、異常終了したことを確認する。
確認手順はジョブの実行と結果の確認を参照。
終了コード(exit value)が255(異常終了)となっていることを確認する。
出力リソースの確認
入力チェックを実装したジョブによって出力リソース(データベース or ファイル)を確認する。
チャンクモデルの場合、中間コミット方式をとっているため、エラー箇所直前のチャンクまで更新が確定していることを確認する。
会員情報テーブルの確認
DBeaverを使用して会員情報テーブルの確認を行う。
更新前後の会員情報テーブルの内容を比較し、確認内容のとおりとなっていることを確認する。
確認手順はDBeaverを使用してデータベースを参照するを参照。
- 確認内容
-
-
1から10番目のレコード(会員番号が"00000001"から"00000010"のレコード)について
-
statusカラム
-
"1"(処理対象)から"0"(初期状態)に更新されていること
-
-
pointカラム
-
ポイント加算対象について、会員種別に応じたポイントが加算されていること
-
typeカラムが"G"(ゴールド会員)の場合は100ポイント
-
typeカラムが"N"(一般会員)の場合は10ポイント
-
-
-
-
11から15番目のレコード(会員番号が"00000011"から"00000015"のレコード)について
-
更新されていないこと(破線の赤枠で示した範囲)
-
-
更新前後の会員情報テーブルの内容を以下に示す。
会員情報ファイルの確認
会員情報ファイルの入出力内容を比較し、確認内容のとおりとなっていることを確認する。
- 確認内容
-
-
出力ディレクトリに会員情報ファイルが出力されていること
-
出力ファイル: files/output/output-member-info-data.csv
-
-
1から10番目のレコード(会員番号が"00000001"から"00000010"のレコード)について
-
statusフィールド
-
"1"(処理対象)から"0"(初期状態)に更新されていること
-
-
pointフィールド
-
ポイント加算対象について、会員種別に応じたポイントが加算されていること
-
typeフィールドが"G"(ゴールド会員)の場合は100ポイント
-
typeフィールドが"N"(一般会員)の場合は10ポイント
-
-
-
-
11から15番目のレコード(会員番号が"00000011"から"00000015"のレコード)について
-
出力されていないこと(破線の赤枠で示した範囲)
-
-
会員情報ファイルの入出力内容を以下に示す。
ファイルのフィールドはid(会員番号)、type(会員種別)、status(商品購入フラグ)、point(ポイント)の順で出力される。
タスクレットモデルでの実装
タスクレットモデルで入力チェックを行うジョブの作成から実行までを以下の手順で実施する。
入力チェックルールの定義
入力チェックを行うために、DTOクラスのチェック対象のフィールドにBean Validationのアノテーションを付与する。
入力チェック用のアノテーションについては、Macchinetta Server 1.x 開発ガイドラインのBean Validationのチェックルール
およびHibernate Validatorのチェックルールを参照。
チャンクモデル/タスクレットモデルで共通して利用するため、既に実施している場合は読み飛ばしてよい。
ここでは、ポイントが1,000,000(上限値)を超過していないかチェックするためのチェックルールを定義する。
package com.example.batch.tutorial.common.dto;
import javax.validation.constraints.Max;
public class MemberInfoDto {
private String id;
private String type;
private String status;
@Max(1000000) // (1)
private int point;
// Getter and setter are omitted.
}
項番 | 説明 |
---|---|
(1) |
対象のフィールドが指定した数値以下であることを示す@Maxアノテーションを付与する。 |
入力チェック処理の実装
ポイント加算処理を行うビジネスロジッククラスに入力チェック処理を実装する。
既に実装してあるPointAddTasklet
クラスに入力チェック処理の実装を追加する。
前提のとおりデータベースアクセスするジョブの場合の説明となるため、
ファイルアクセスするジョブの場合の実装は以下の(1)~(3)のみ追加する。
// Package and the other import are omitted.
import javax.inject.Inject;
import org.springframework.batch.item.validator.Validator;
@Component
public class PointAddTasklet implements Tasklet {
// Definition of constans, ItemStreamReader and ItemWriter are omitted.
@Inject // (1)
Validator<MemberInfoDto> validator; // (2)
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
MemberInfoDto item = null;
List<MemberInfoDto> items = new ArrayList<>(CHUNK_SIZE);
try {
reader.open(chunkContext.getStepContext().getStepExecution().getExecutionContext());
while ((item = reader.read()) != null) {
validator.validate(item); // (3)
// The other codes of bussiness logic are omitted.
}
writer.write(items);
} finally {
reader.close();
}
return RepeatStatus.FINISHED;
}
}
項番 | 説明 |
---|---|
(1) |
|
(2) |
|
(3) |
|
ジョブの実行と結果の確認
作成したジョブをSTS上で実行し、結果を確認する。
実行構成からジョブを実行
既に作成してある実行構成から、ジョブを実行する。
ここでは、異常系データを利用してジョブを実行する。
入力チェックを実装したジョブが扱うリソース(データベース or ファイル)によって
入力データの切替方法が異なるため、以下のとおり実行すること。
- データベースアクセスでデータ入出力を行うジョブに対して入力チェックを実装した場合
-
データベースアクセスでデータ入出力を行うジョブの実行構成からジョブを実行 で作成した実行構成を使ってジョブを実行する。
異常系データを利用するために、batch-application.properties
のDatabase Initializeで
正常系データのスクリプトをコメントアウトし、異常系データのスクリプトのコメントアウトを解除する。
# Database Initialize
tutorial.create-table.script=file:sqls/create-member-info-table.sql
#tutorial.insert-data.script=file:sqls/insert-member-info-data.sql
tutorial.insert-data.script=file:sqls/insert-member-info-error-data.sql
- ファイルアクセスでデータ入出力を行うジョブに対して入力チェックを実装した場合
-
ファイルアクセスでデータ入出力を行うジョブの実行構成からジョブを実行 で作成した実行構成を使ってジョブを実行する。
異常系データを利用するために、実行構成で設定する引数のうち、 入力ファイル(inputFile)のパスを正常系データ(insert-member-info-data.csv)から異常系データ(insert-member-info-error-data.csv)に変更する。
コンソールログの確認
Console Viewを開き、以下の内容のログが出力されていることを確認する。
-
処理が異常終了(FAILED)していること。
-
org.springframework.batch.item.validator.ValidationException
が発生していること
(.. omitted)
[2020/03/10 16:05:44] [main] [o.s.b.c.l.s.SimpleJobLauncher] [INFO ] Job: [FlowJob: [name=jobPointAddTasklet]] launched with the following parameters: [{jsr_batch_run_id=142}]
[2020/03/10 16:05:44] [main] [o.s.b.c.j.SimpleStepHandler] [INFO ] Executing step: [jobPointAddTasklet.step01]
[2020/03/10 16:05:44] [main] [o.s.b.c.s.AbstractStep] [ERROR] Encountered an error executing step jobPointAddTasklet.step01 in job jobPointAddTasklet
org.springframework.batch.item.validator.ValidationException: Validation failed for com.example.batch.tutorial.common.dto.MemberInfoDto@3811510:
Field error in object 'item' on field 'point': rejected value [1000001]; codes [Max.item.point,Max.point,Max.int,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [item.point,point]; arguments []; default message [point],1000000]; default message [must be less than or equal to 1000000]
at org.springframework.batch.item.validator.SpringValidator.validate(SpringValidator.java:54)
(.. omitted)
Caused by: org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'item' on field 'point': rejected value [1000001]; codes [Max.item.point,Max.point,Max.int,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [item.point,point]; arguments []; default message [point],1000000]; default message [must be less than or equal to 1000000]
... 24 common frames omitted
[2020/03/10 16:05:44] [main] [o.s.b.c.s.AbstractStep] [INFO ] Step: [jobPointAddTasklet.step01] executed in 178ms
[2020/03/10 16:05:44] [main] [o.s.b.c.l.s.SimpleJobLauncher] [INFO ] Job: [FlowJob: [name=jobPointAddTasklet]] completed with the following parameters: [{jsr_batch_run_id=142}] and the following status: [FAILED] in 244ms
終了コードの確認
終了コードにより、異常終了したことを確認する。
確認手順はジョブの実行と結果の確認を参照。
終了コード(exit value)が255(異常終了)となっていることを確認する。
出力リソースの確認
入力チェックを実装したジョブによって出力リソース(データベース or ファイル)を確認する。
タスクレットモデルの場合、一括コミット方式をとっているため、エラーが発生した場合は一切更新されていないことを確認してほしい。
会員情報テーブルの確認
DBeaverを使用して会員情報テーブルの確認を行う。
更新前後の会員情報テーブルの内容を比較し、確認内容のとおりとなっていることを確認する。
確認手順はDBeaverを使用してデータベースを参照するを参照。
- 確認内容
-
-
すべてのレコードについて、データが更新されていないこと
-