Spring Batch 6 のバッチ処理にパラメータ(ジョブ引数)を渡す方法。
例えばファイルパスなど、毎回違う値を渡すときに推奨されるやり方でコーディングしてみる。(reader や write などの Step で指定する(受け取る)やり方。今回は processor でジョブ引数を受け取るか形)
「Spring Batch 6 で Listener を追加してログを出す」で修正したバッチプログラムに修正を加える。(修正点のみ記述するので、全体のソースが見たい人は、元ネタのページを手繰ってくださいませ)
修正点は以下のとおり。
■import の追加
import org.springframework.batch.core.configuration.annotation.StepScope; //追加
import org.springframework.beans.factory.annotation.Value; //追加
■processor の修正
@Bean
@StepScope // ジョブ引数を受け取るために必須
public ItemProcessor<UserCombinedDto, UserCombinedDto> processor(
@Value("#{jobParameters['models']}") Integer models,
@Value("#{jobParameters['modelName']}") String modelName) {
return item -> {
String phone = item.user_phone_number(); // DtoがRecordの場合のゲッター。通常のクラスなら .getUser_phone_number()
// 電話番号が null でなく、かつ 090 または 080 で始まる場合
if (phone != null && (phone.startsWith("090") || phone.startsWith("080"))) {
// 表示名
String displayName = "スマホ";
// ジョブ引数をチェック
if (models == null || "1".equals(models)) { // ジョブ引数未指定、あるいは models == 1
displayName = "携帯";
}
else {
if ("2".equals(models) && (modelName != null && !modelName.isEmpty())) {
// ジョブ引数 models = 2 で、modelName も指定されている
displayName = modelName;
}
else {
// ジョブ引数 models が 1でも 2でもない。または modelName が未指定
displayName = "未定義";
}
}
// 文字列を追加した新しいDtoを作成して返す
// (DtoがRecordの場合はイミュータブル(不変)なので、このように新しくインスタンスを作る)
return new UserCombinedDto(
item.user_id(),
item.user_name(),
item.user_address1(),
item.user_address2(),
phone + "(" + displayName + ")",
item.anniversary(),
item.note()
);
}
// 条件に当てはまらない場合は、加工せずにそのまま次に渡す
return item;
};
}
<修正ポイント>
- メソッドに @StepScope アノテーションを必ず付与(このアノテーションを付けないとジョブ引数が受け取れない)
- メソッドの引数に、受け取りたいジョブ引数を設定(例:@Value("#{jobParameters['modelName']}") String modelName)
- 受け取ったジョブ引数で色々と処理(ジョブ引数は編集できないので、編集したいときは他の変数にコピー)
■Stepの定義の変更
// 4. Stepの定義 (Spring Batch 6 スタイル)
@Bean
public Step exportStep (
JdbcCursorItemReader<UserCombinedDto> reader,
ItemProcessor<UserCombinedDto, UserCombinedDto> processor,
FlatFileItemWriter<UserCombinedDto> writer) {
return new StepBuilder("exportStep", jobRepository)
.<UserCombinedDto, UserCombinedDto>chunk(100) // 100件ごとにコミット/出力
.transactionManager(transactionManager)
.reader(reader) // 読んで
.processor(processor) // 処理して
.writer(writer) // 書き出す
.build();
}
<修正ポイント>
- メソッドの引数を。各Bean(Reader、Processor、Writer)の定義を受け取る形に設定
- 各 Step の呼び出しで引数指定は不要(例:.processor(processor()) →.processor(processor) )
■Jobの定義の変更
// 5. Jobの定義
@Bean
public Job exportJob(Step exportStep) {
return new JobBuilder("exportJob", jobRepository)
.start(exportStep)
.listener(jobListener) // JobExecutionListenerを追加
.build();
}
<修正ポイント>
- メソッドの引数を、exportStep の定義を受け取る形に設定
- exportStep を呼び出すときの引数を不要に(例:.start(exportStep())→.start(exportStep))
Spring Framework は「@Bean アノテーションの付いたメソッドの引数に他の Bean の型を書いておくと、Spring が自動的にそれを探して持ってきてくれる(依存性注入)」というルールがあるので、これをすると Step を呼ぶときの引数を省略できる。
「processor は引数でパラメータ(ジョブ引数)を受け取る」→「exportStep はその情報を読み込む(Spring により自動で流し込まれる)」→「exportJob は Spring により自動的に組み立てられた Step の情報を読み込む(流し込まれる)」という過程を経て、あとは Spring が引数はうまいことやってくれる・・・という仕掛けだ。なので、呼び出すときの引数を省略できる。
ま、あまり Framework を使わず、一からしこしこコードを書いてきた人間には、こういう「Framework が巧いことなってくれる」という状態がなかなかしっくり来なくて、いつまでも「どうしてこうなる?どこからこの値はきた?」ってことになるんだけど、「そういうもんだ」と納得するしかないのである(笑)
と、ここまでできたら Eclipse で実行してみる。
Eclipse で実行させるときにパラメータ(ジョブ引数)を渡したいときは、プロジェクト名の上で右ボタンメニューから「実行」→「実行の構成」を選択し、開いた窓で、まずはプロジェクト名とメインクラスを指定。
次に、プログラムのパラメータ(引数)を設定する。これは、コマンドラインから jar を実行するときと同じように、「パラメータ名=値」という形で記述する。複数のパラメータがある場合は半角スペース区切りで入力する。
値を設定後、「実行」ボタン押下でジョブ実行。
ログにも引数の情報が出力されている。(長いので、行の先頭は'~'に省略)
~: Started BatchDb2Csv1Application in 5.341 seconds (process running for 6.314)
~: Running default command line with: [models=2, modelName=未来電話]
~: [name=exportJob]] launched with the following parameters:
[{JobParameter{name='models', value=2, type=class java.lang.String,
identifying=true},JobParameter{name='modelName', value=未来電話,
type=class java.lang.String, identifying=true}}]
~: ★Job started: exportJob
~: Executing step: [exportStep]
~: Step: [exportStep] executed in 767ms
~: ★Job finished: exportJob with status: COMPLETED
~: [{JobParameter{name='models', value=2, type=class java.lang.String, identifying=true},JobParameter{name='modelName', value=未来電話, type=class java.lang.String, identifying=true}}] and the following status: [COMPLETED] in 785ms
出力された CSV ファイルを見ると、しっかり携帯種が「未来電話」と出ている。パラメータの設定が効いているようだ。
ID,名前,住所1,住所2,電話,記念日,内容
1,テスト太郎,広島市中区橋本町2-17,若林ビル302,082-221-7555 ,,
2,HIROSHIMATAROU,広島県廿日市市宮島町5050-11,,090-1111-2222(未来電話),19661210,誕生日
2,HIROSHIMATAROU,広島県廿日市市宮島町5050-11,,090-1111-2222(未来電話),19981003,結婚
3,中国 花子,島根県呪郡呪村1078-13,川村診療所内,0120-333-444 ,,
4,近藤まちゃ,山口県周南市銀座3丁目2-19,,080-5555-6666(未来電話),19641225,誕生日
4,近藤まちゃ,山口県周南市銀座3丁目2-19,,080-5555-6666(未来電話),20230407,死人村トレイルラン100 完走
4,近藤まちゃ,山口県周南市銀座3丁目2-19,,080-5555-6666(未来電話),20240122,死人村雪山激走フェス 2.1km地点リタイア
5,MASA SAITO,広島市中区橋本町2-17,若林ビル201,082-221-7549 ,,
6,KAORU MITARAI,山口県岩国市周東町祖生99999,,0827-85-0427 ,20011011,結婚
6,KAORU MITARAI,山口県岩国市周東町祖生99999,,0827-85-0427 ,20011210,離婚
6,KAORU MITARAI,山口県岩国市周東町祖生99999,,0827-85-0427 ,20200523,結婚
7,日本 太郎,,,009-1234-5678,,
次回は、第1パラメータの型を整数(Integer)にしているため、ここに文字列を入れられると異常終了していまう。それを回避する修正をしよう。