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 == 1displayName = "携帯";}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 スタイル)@Beanpublic 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の定義@Beanpublic 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)にしているため、ここに文字列を入れられると異常終了していまう。それを回避する修正をしよう。

コメントする