1. はじめに

1.1. 利用規約

本ドキュメントを使用するにあたり、以下の規約に同意していただく必要があります。同意いただけない場合は、本ドキュメント及びその複製物の全てを直ちに消去又は破棄してください。

  1. 本ドキュメントの著作権及びその他一切の権利は、NTTデータあるいはNTTデータに権利を許諾する第三者に帰属します。

  2. 本ドキュメントの一部または全部を、自らが使用する目的において、複製、翻訳、翻案することができます。ただし本ページの規約全文、およびNTTデータの著作権表示を削除することはできません。

  3. 本ドキュメントの一部または全部を、自らが使用する目的において改変したり、本ドキュメントを用いた二次的著作物を作成することができます。ただし、「参考文献:TERASOLUNA Batch Framework for Java (5.x) Development Guideline」あるいは同等の表現を、作成したドキュメント及びその複製物に記載するものとします。

  4. 前2項によって作成したドキュメント及びその複製物を、無償の場合に限り、第三者へ提供することができます。

  5. NTTデータの書面による承諾を得ることなく、本規約に定められる条件を超えて、本ドキュメント及びその複製物を使用したり、本規約上の権利の全部又は一部を第三者に譲渡したりすることはできません。

  6. NTTデータは、本ドキュメントの内容の正確性、使用目的への適合性の保証、使用結果についての的確性や信頼性の保証、及び瑕疵担保義務も含め、直接、間接に被ったいかなる損害に対しても一切の責任を負いません。

  7. NTTデータは、本ドキュメントが第三者の著作権、その他如何なる権利も侵害しないことを保証しません。また、著作権、その他の権利侵害を直接又は間接の原因としてなされる如何なる請求(第三者との間の紛争を理由になされる請求を含む。)に関しても、NTTデータは一切の責任を負いません。

本ドキュメントで使用されている各社の会社名及びサービス名、商品名に関する登録商標および商標は、以下の通りです。

  • TERASOLUNA は、株式会社NTTデータの登録商標です。

  • その他の会社名、製品名は、各社の登録商標または商標です。

1.2. 導入

1.2.1. ガイドラインの目的

本ガイドラインではSpring Framework、Spring Batch、MyBatis を中心としたフルスタックフレームワークを利用して、 保守性の高いバッチアプリケーション開発をするためのベストプラクティスを提供する。

本ガイドラインを読むことで、ソフトウェア開発(主にコーディング)が円滑に進むことを期待する。

1.2.2. ガイドラインの対象読者

本ガイドラインはソフトウェア開発経験のあるアーキテクトやプログラマ向けに書かれており、 以下の知識があることを前提としている。

  • Spring FrameworkのDIやAOPに関する基礎的な知識がある

  • Javaを使用してアプリケーションを開発したことがある

  • SQLに関する知識がある

  • Mavenを使用したことがある

これからJavaを勉強し始めるという人向けではない。

Spring Frameworkに関して、本ドキュメントを読むための基礎知識があるかどうかを測るために Spring Framework理解度チェックテスト を参照するとよい。 この理解度テストが4割回答できない場合は、別途以下のような書籍で学習することを推奨する。

1.2.3. ガイドラインの構成

まず、重要なこととして、本ガイドラインは TERASOLUNA Server Framework for Java (5.x) Development Guideline (以降、TERASOLUNA Server 5.x 開発ガイドライン)のサブセットとして位置づけている。 出来る限りTERASOLUNA Server 5.x 開発ガイドラインを活用し説明の重複を省くことで、ユーザの学習コスト低減を狙っている。 よって随所にTERASOLUNA Server 5.x 開発ガイドラインへの参照を示しているため、両方のガイドを活用しながら開発を進めていってほしい。

TERASOLUNA Batch Framework for Java (5.x)のコンセプト

バッチ処理の基本的な考え方、TERASOLUNA Batch Framework for Java (5.x)の基本的な考え方、Spring Batchの概要を説明する。

アプリケーション開発の流れ

TERASOLUNA Batch Framework for Java (5.x)を利用してアプリケーション開発する上で必ず押さえておかなくてはならない知識や作法について説明する。

ジョブの起動

同期実行、非同期実行、起動パラメータといったジョブの起動方法について説明する。

データの入出力

データベースアクセス、ファイルアクセスといった、各種リソースへの入出力について説明する。

異常系への対応

入力チェックや例外ハンドリングといった異常系について説明する。

ジョブの管理

ジョブの実行管理の方法について説明する。

フロー制御と並列・多重処理

ジョブを並列処理/分散処理する方法について説明する。

1.2.4. ガイドラインの読み方

以下のコンテンツはTERASOLUNA Batch Framework for Java (5.x)を使用するすべての開発者が読むことを強く推奨する。

以下のコンテンツは通常必要となるため、基本的には読んでおくこと。 開発対象に応じて、取捨選択するとよい。

以下のコンテンツは一歩進んだ実装をする際にはじめて参照すれば良い。

1.2.4.1. ガイドラインの表記

本ガイドラインの表記について、留意事項を述べる。

WindowsコマンドプロンプトとUnix系ターミナルについて

WindowsとUnix系では表記の違いで動作しなくなる場合は併記する。 そうでない場合は、Unix系の表記で統一する。

プロンプト記号

Unix系の$にて表記する。

プロンプト表記例
$ java -version
Bean定義のプロパティとコンストラクタについて

本ガイドラインでは、pcのネームスペースを用いた表記とする。 ネームスペースを用いることで、Bean定義の記述が簡潔になったり、コンストラクタ引数が明確になる効果がある。

ネームスペースを利用した記述
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
    <property name="lineTokenizer">
        <bean class="org.terasoluna.batch.item.file.transform.FixedByteLengthLineTokenizer"
              c:ranges="1-6, 7-10, 11-12, 13-22, 23-32"
              c:charset="MS932"
              p:names="branchId,year,month,customerId,amount"/>
    </property>
</bean>

参考までに、ネームスペースを利用しない記述を示す。

ネームスペースを利用しない記述
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
    <property name="lineTokenizer">
        <bean class="org.terasoluna.batch.item.file.transform.FixedByteLengthLineTokenizer">
            <constructor-arg index="0" value="1-6, 7-10, 11-12, 13-22, 23-32"/>
            <constructor-arg index="1" value="MS932"/>
            <property name="names" value="branchId,year,month,customerId,amount"/>
    </property>
</bean>

なお、ユーザに対してネームスペースを用いる記述を強要することはない。 あくまで説明を簡潔にするための配慮であると受け止めてほしい。

1.2.5. ガイドラインの動作検証環境

本ガイドラインで説明している内容の動作検証環境については、 「 テスト済み環境 」を参照されたい。

1.3. 更新履歴

更新日付 更新箇所 変更内容

2017-03-17

-

5.0.0 RELEASE 版公開

2. TERASOLUNA Batch Framework for Java (5.x)のコンセプト

2.1. 一般的なバッチ処理

2.1.1. 一般的なバッチ処理とは

一般的に、バッチ処理とは「まとめて一括処理する」ことを指す。
データベースやファイルから大量のレコードを読み込み、処理し、書き出す処理であることが多い。
バッチ処理には以下の特徴があり、オンライン処理と比較して、応答性より処理スループットを優先した処理方式である。

バッチ処理の特徴
  • データを一定の量でまとめて処理する。

  • 処理に一定の順序がある。

  • スケジュールに従って実行・管理される。

次にバッチ処理を利用する主な目的を以下に示す。

スループットの向上

データをまとめて処理することで、処理のスループットを向上できる。
ファイルやデータベースは、1件ごとにデータを入出力せず、一定件数にまとめることで、I/O待ちのオーバヘッドが劇的に少なくなり効率的である。 1件ごとのI/O待ちは微々たるものでも、大量データを処理する場合はその累積が致命的な遅延となる。

応答性の確保

オンライン処理の応答性を確保するため、即時処理を行う必要がない処理をバッチ処理に切り出す。
たとえば、すぐに処理結果が必要でない場合、オンライン処理で受付まで処理を行い、裏でバッチ処理を行う構成がある。 このような処理方式は、ディレードバッチディレードオンラインなどと呼ばれる。

時間やイベントへの対応

特定の時間やイベントに応じた処理は、バッチ処理として実装することが素直と言える。
たとえば、業務要件により1ヶ月分のデータを翌月第1週の週末に集計する、システム運用ルールに則って週末日曜の午前2時に1週間分の業務データをバックアップする、などである。

外部システムとの連携上の制約

ファイルなど外部システムとのインターフェースが制約となるために、バッチ処理を利用することもある。
外部システムから送付されてきたファイルは、一定期間のデータをまとめたものになる。 これを取り込む処理は、オンライン処理よりもバッチ処理が向いている。

バッチ処理を実現するには、さまざまな技術要素を組み合わせることが一般的である。ここでは、主要な技術を紹介する。

ジョブスケジューラ

バッチ処理の1実行単位をジョブと呼ぶ。これを管理するためのミドルウェアである。
バッチシステムにおいて、ジョブが数個であることは稀であり、通常は数百、ときには数千にいたる場合もある。 そのため、ジョブの関連を定義し、実行スケジュールを管理する専用の仕組みが不可欠になる。

シェルスクリプト

ジョブを実現する方法の1つ。OSやミドルウェアなどに実装されているコマンドを組み合わせて1つの処理を実現する。
手軽に実装できる反面、複雑なビジネスロジックを記述するには不向きであるため、ファイルのコピー・バックアップ・テーブルクリアなど主にシンプルな処理に用いる。 また、別のプログラミング言語で実装した処理を実行する際に、起動前の設定や実行後の処理だけをシェルスクリプトが担うことも多い。

プログラミング言語

ジョブを実現する方法の1つ。シェルスクリプトよりも構造化されたコードを記述でき、開発生産性・メンテナンス性・品質などを確保するのに有利である。 そのため、比較的複雑なロジックになりやすいファイルやデータベースのデータを加工するようなビジネスロジックの実装によく使われる。

2.1.2. バッチ処理に求められる要件

業務処理を実現するために、バッチ処理に求められる要件には以下のようなものがある。

  • 性能向上

    • 一定量のデータをまとめて処理できる。

    • ジョブを並列/多重に実行できる。

  • 異常発生時のリカバリ

    • 再実行(手動/スケジュール)ができる。

    • 再処理した時に、処理済レコードのスキップして、未処理部分だけを処理できる。

  • 多様な起動方式

    • 同期実行ができる。

    • 非同期実行ができる。

      • 実行契機としては、DBポーリング、HTTPリクエスト、などがある。

  • さまざまな入出力インターフェース

    • データベース

    • ファイル

      • CSVやTSVなどの可変長

      • 固定長

      • XML

上記の要件について具体的な内容を以下に示す。

大量データを一定のリソースで効率よく処理できる(性能向上)

大量のデータをまとめて処理することで処理時間を短縮する。このとき重要なのは、 「一定のリソースで」 の部分である。
100万件でも1億件でも、一定のCPUやメモリの使用で処理でき、件数に応じて緩やかにかつリニアに処理時間が延びるのが理想である。 まとめて処理するには、一定件数ごとにトランザクションを開始・終了させ、まとめてI/O入出力しすることで、 使用するリソースを平準化させる仕組みが必要となる。
それでも処理しきれない膨大なデータを相手にする場合は、一歩進んでハードウェアリソースを限界まで使い切る仕組みも追加で必要になる。 処理対象データを件数やグループで分割して、複数プロセス・複数スレッドによって多重処理する。 さらに推し進めて複数マシンによる分散処理をすることもある。 リソースを限界まで使い切る際には、I/Oを限りなく低減することがきわめて重要になる。

可能な限り処理を継続する(異常発生時のリカバリ)

大量データを処理するにあたって、入力データが異常な場合や、システム自体に異常が発生した場合の防御策を考えておく必要がある。
大量データは必然的に処理し終わるまでに長時間かかるが、エラー発生後に復旧までの時間が長期化すると、システム運用に大きな影響を及ぼしてしまう。
たとえば、1000万件のデータを処理する場合を考える。999万件目でエラーになり、それまでの処理をすべてやり直すとしたら、 運用スケジュールに影響が出てしまうことは明白である。
このような影響を抑えるために、バッチ処理ならではの処理継続性が重要となる。これにはエラーデータをスキップしながら次のデータを処理する仕組み、 処理をリスタートする仕組み、可能な限り自動復旧を試みる仕組み、などが必要となる。また、1つのジョブを極力シンプルなつくりにし、再実行を容易にすることも重要である。

実行契機に応じて柔軟に実行できる(多様な起動方式)

時刻を契機とする場合、オンラインや外部システムとの連携を契機とした場合など、さまざまな実行契機に対応するの仕組みが必要になる。 同期実行ではジョブスケジューラから定時になったら処理を起動する、 非同期実行ではプロセスを常駐させておきイベントに応じて随時バッチ処理を行う、というような 様々な仕組みが一般的に知られている。

さまざまな入出力インターフェースを扱える(さまざまな入出力インターフェース)

オンラインや外部システムと連携するということは、データベースはもちろん、CSV/XMLといったさまざまなフォーマットのファイルを扱えることが重要となる。 さらに、それぞれの入出力形式を透過的に扱える仕組みがあると実装しやすくなり、複数フォーマットへの対応も迅速に行なえるようになる。

2.1.3. バッチ処理で考慮する原則と注意点

バッチ処理システムを構築する際に考慮すべき重要な原則、および、いくつかの一般的な考慮事項を示す。

  • 単一のバッチ処理は可能な限り簡素化し、複雑な論理構造を避ける。

  • 処理とデータは物理的に近い場所におく(処理を実行する場所にデータを保存する)。

  • システムリソース(特にI/O)の利用を最小限にし、できるだけインメモリで多くの操作を実行する。

  • また、不要な物理I/Oを避けるため、アプリケーションのI/O(SQLなど)を見直す。

  • 複数のジョブで同じ処理を繰り返さない。

    • たとえば、集計処理とレポート処理がある場合に、レポート処理で集計処理を再度することは避ける。

  • 常にデータの整合性に関しては最悪の事態を想定する。十分なチェックとデータの整合性を維持するために、データの検証を行う。

  • バックアップについて十分に検討する。特にシステムが年中無休で実行されている場合は、バックアップの難易度が高くなる。

2.2. TERASOLUNA Batch Framework for Java (5.x)のスタック

2.2.1. 概要

TERASOLUNA Batch Framework for Java (5.x)の構成について説明し、TERASOLUNA Batch Framework for Java (5.x)の担当範囲を示す。

2.2.2. TERASOLUNA Batch Framework for Java (5.x)のスタック

TERASOLUNA Batch Framework for Java (5.x)で使用するSoftware Frameworkは、 Spring Framework (Spring Batch) を中心としたOSSの組み合わせである。以下にTERASOLUNA Batch Framework for Java (5.x)のスタック概略図を示す。

TERASOLUNA Batch Framework for Java (5.x) Stack
TERASOLUNA Batch Framework for Java (5.x)のスタック概略図

ジョブスケジューラやデータベースなどの製品についての説明は、本ガイドラインの説明対象外とする。

2.2.2.1. 利用するOSSのバージョン

TERASOLUNA Batch Framework for Java (5.x)のバージョン5.0.0.RELEASEで利用するOSSのバージョン一覧を以下に示す。

TERASOLUNA Batch Framework for Java (5.x)で使用するOSSのバージョンは、原則として、Spring IO platformの定義に準じている。 なお、バージョン5.0.0.RELEASEにおけるSpring IO platformのバージョンは、 Athens-SR2である。
Spring IO platformの詳細については、TERASOLUNA Server Framework for Java (5.x)の 利用するOSSのバージョンを参照。

OSSバージョン一覧
Type GroupId ArtifactId Version Spring IO platform Remarks

Spring

org.springframework

spring-aop

4.3.5.RELEASE

*

Spring

org.springframework

spring-beans

4.3.5.RELEASE

*

Spring

org.springframework

spring-context

4.3.5.RELEASE

*

Spring

org.springframework

spring-expression

4.3.5.RELEASE

*

Spring

org.springframework

spring-core

4.3.5.RELEASE

*

Spring

org.springframework

spring-tx

4.3.5.RELEASE

*

Spring

org.springframework

spring-jdbc

4.3.5.RELEASE

*

Spring Batch

org.springframework.batch

spring-batch-core

3.0.7.RELEASE

*

Spring Batch

org.springframework.batch

spring-batch-infrastructure

3.0.7.RELEASE

*

Spring Retry

org.springframework.retry

spring-retry

1.1.5.RELEASE

*

Java Batch

javax.batch

javax.batch-api

1.0.1

*

Java Batch

com.ibm.jbatch

com.ibm.jbatch-tck-spi

1.0

*

MyBatis3

org.mybatis

mybatis

3.4.2

MyBatis3

org.mybatis

mybatis-spring

1.3.1

MyBatis3

org.mybatis

mybatis-typehandlers-jsr310

1.0.2

DI

javax.inject

javax.inject

1

*

ログ出力

ch.qos.logback

logback-classic

1.1.8

*

ログ出力

ch.qos.logback

logback-core

1.1.8

*

*1

ログ出力

org.slf4j

jcl-over-slf4j

1.7.22

*

ログ出力

org.slf4j

slf4j-api

1.7.22

*

入力チェック

javax.validation

validation-api

1.1.0.Final

*

入力チェック

org.hibernate

hibernate-validator

5.2.4.Final

*

入力チェック

org.jboss.logging

jboss-logging

3.3.0.Final

*

*1

入力チェック

com.fasterxml

classmate

1.3.3

*

*1

コネクションプール

org.apache.commons

commons-dbcp2

2.1.1

*

コネクションプール

org.apache.commons

commons-pool2

2.4.2

*

EL式

org.glassfish

javax.el

3.0.0

*

インメモリデータベース

com.h2database

h2

1.4.193

*

XML

com.thoughtworks.xstream

xstream

1.4.9

*

*1

XML

xmlpull

xmlpull

1.1.3.1

*1

XML

xpp

xpp3_min

1.1.4c

*1

XML

xpp

xpp3_min

1.1.4c

*1

JSON

org.codehaus.jettison

jettison

1.2

*

*1

Remarksについて
  1. Spring IO platformでサポートしているライブラリが個別に依存しているライブラリ

2.2.3. TERASOLUNA Batch Framework for Java (5.x)の構成要素

TERASOLUNA Batch Framework for Java (5.x)のSoftware Framework構成要素について説明する。

TERASOLUNA Batch Framework for Java (5.x) Components of Software Framework
Software Framework構成要素の概略図

以下に、各要素の概要を示す。

基盤フレームワーク

フレームワークの基盤として、Spring Frameworkを利用する。DIコンテナをはじめ各種機能を活用する。

バッチフレームワーク

バッチフレームワークとして、Spring Batchを利用する。

非同期実行

非同実行を実現する方法として、以下の機能を利用する。

DBポーリングによる周期起動

TERASOLUNA Batch Framework for Java (5.x)が提供するライブラリを利用する。

Webコンテナ起動

Spring MVCを使用して、Spring Batchと連携をする。

O/R Mapper

MyBatisを利用し、Spring Frameworkとの連携ライブラリとして、MyBatis-Springを使用する。

ファイルアクセス

Spring Batchから提供されている機能 に加えて、補助機能をTERASOLUNA Batch Framework for Java (5.x)がする。

ロギング

ロガーはAPIにSLF4J、実装にLogbackを利用する。

バリデーション
単項目チェック

単項目チェックにはBean Validationを利用し、実装はHibernate Validatorを使用する。

相関チェック

相関チェックにはBean Validation、もしくはSpring Validationを利用する。

コネクションプール

コネクションプールには、DBCPを利用する。

2.2.3.1. TERASOLUNA Batch Framework for Java (5.x)が実装を提供する機能

TERASOLUNA Batch Framework for Java (5.x)が実装を提供する機能を以下に示す。

TERASOLUNA Batch Framework for Java (5.x)が実装を提供する機能一覧

機能名

概要

非同期実行(DBポーリング)

DBポーリングによる非同期実行を実現する。

ファイルアクセス

改行なしの固定長ファイルをバイト数で読み込む。

固定長レコードをバイト数で各項目に分解する。

可変長レコードで囲み文字の出力を制御する。

2.3. Spring Batchのアーキテクチャ

2.3.1. Overview

TERASOLUNA Server Framework for Java (5.x)の基盤となる、Spring Batchのアーキテクチャについて説明をする。

2.3.1.1. Spring Batchとは

Spring Batchは、その名のとおりバッチアプリケーションフレームワークである。 SpringがもつDIコンテナやAOP、トランザクション管理機能をベースとして以下の機能を提供している。

処理の流れを定型化する機能
タスクレットモデル
シンプルな処理

自由に処理を記述する方式である。SQLを1回発行するだけ、コマンドを発行するだけ、といった簡素なケースや 複数のデータベースやファイルにアクセスしながら処理するような複雑で定型化しにくいケースで用いる。

チャンクモデル
大量データを効率よく処理

一定件数のデータごとにまとめて入力/加工/出力する方式。データの入力/加工/出力といった処理の流れを定型化し、 一部を実装するだけでジョブが実装できる。

様々な起動方法

コマンドライン実行、Servlet上で実行、その他のさまざまな契機での実行を実現する。

様々なデータ形式の入出力

ファイル、データベース、メッセージキューをはじめとするさまざまなデータリソースとの入出力を簡単に行う。

処理の効率化

多重実行、並列実行、条件分岐を設定ベースで行う。

ジョブの管理

実行状況の永続化、データ件数を基準にしたリスタートなどを可能にする。

2.3.1.2. Hello, Spring Batch!

Spring Batchのアーキテクチャを理解する上で、未だSpring Batchに触れたことがない場合は、 以下の公式ドキュメントを一読するとよい。 Spring Batchを用いた簡単なアプリケーションの作成を通して、イメージを掴んでほしい。

2.3.1.3. Spring Batchの基本構造

Spring Batchの基本的な構造を説明する。

Spring Batchはバッチ処理の構造を定義している。この構造を理解してから開発を行うことを推奨する。

Spring Batch Main Components
Spring Batchに登場する主な構成要素
Spring Batchに登場する主な構成要素
構成要素 役割

Job

Spring Batchにおけるバッチアプリケーションの一連の処理をまとめた1実行単位。

Step

Jobを構成する処理の単位。1つのJobに1~N個のStepをもたせることが可能。
1つのJobを複数のStepに分割して処理することにより、処理の再利用、並列化、条件分岐が可能になる。 Stepは、チャンクモデルまたはタスクレットモデル(これらについては後述する)のいずれかで実装する。

JobLauncher

Jobを起動するためのインターフェース。
JobLauncherをユーザが直接利用することも可能だが、javaコマンドから
CommandLineJobRunnerを起動することでより簡単にバッチ処理を開始できる。 CommandLineJobRunnerは、JobLauncherを起動するための各種処理を引き受けてくれる。

ItemReader
ItemProcessor
ItemWriter

チャンクモデルを実装する際に、データの入力/加工/出力の3つに分割するためのインターフェース。
バッチアプリケーションは、この3パターンの処理で構成されることが多いことに由来し、 Spring Batchでは主にチャンクモデルで
これらインターフェースの実装を活用する。 ユーザはビジネスロジックをそれぞれの役割に応じて分割して記述する。
データの入出力を担うItemReaderとItemWriterは、データベースやファイルからJavaオブジェクトへの変換、もしくはその逆の処理であることが多い。 そのため、Spring Batchから標準的な実装が提供されている。 ファイルやデータベースからデータの入出力を行う一般的なバッチアプリケーションの場合は、
Spring Batchの標準実装をそのまま使用するだけで要件を満たせるケースもある。
データの加工を担うItemProcessorは、入力チェックやビジネスロジックを実装する。

タスクレットモデルでは、ItemReader/ItemProcessor/ItemWriterが、1つのTaksletインターフェース実装に置き換わる。Tasklet内に入出力、入力チェック、ビジネスロジックのすべてを実装する必要がある。

JobRepository

JobやStepの状況を管理する機構。これらの管理情報は、Spring Batchが規定するテーブルスキーマを元にデータベース上に永続化される。

2.3.2. Architecture

OverviewではSpring Batchの基本構造については簡単に説明した。

これを踏まえて、以下の点について説明をする。

最後に、Spring Batchを利用したバッチアプリケーションの性能チューニングポイントについて説明をする。

2.3.2.1. 処理全体の流れ

Spring Batchの主な構成要素と処理全体の流れについて説明をする。 また、ジョブの実行状況などのメタデータがどのように管理されているかについても説明する。

Spring Batchの主な構成要素と処理全体の流れ(チャンクモデル)を下図に示す。

Spring Batch Process Flow
Spring Batchの主な構成要素と処理全体の流れ

中心的な処理の流れ(黒線)とジョブ情報を永続化する流れ(赤線)について説明する。

中心的な処理の流れ
  1. ジョブスケジューラからJobLauncherが起動される。

  2. JobLauncherからJobを実行する。

  3. JobからStepを実行する。

  4. StepはItemReaderによって入力データを取得する。

  5. StepはItemProcessorによって入力データを加工する。

  6. StepはItemWriterによって加工されたデータを出力する

ジョブ情報を永続化する流れ
  1. JobLauncherはJobRepositoryを介してDatabaseにJobInstanceを登録する。

  2. JobLauncherはJobRepositoryを介してDatabaseにジョブが実行開始したことを登録する。

  3. JobStepはJobRepositoryを介してDatabaseに入出力件数や状態など各種情報を更新する。

  4. JobLauncherはJobRepositoryを介してDatabaseにジョブが実行終了したことを登録する。

新たに構成要素と永続化に焦点をあてたJobRepositoryについての説明を以下に示す。

永続化に関連する構成要素
構成要素 役割

JobInstance

Spring BatchはJobの「論理的」な実行を示す。JobInstanceをJob名と引数によって識別している。 言い換えると、Job名と引数が同一である実行は、同一JobInstanceの実行と認識し、前回起動時の続きとしてJobを実行する。
対象のJobが再実行をサポートしており、前回実行時にエラーなどで処理が途中で中断していた場合は処理の途中から実行される。 一方、再実行をサポートしていないJobや、対象のJobInstanceがすでに正常に処理が完了している場合は例外が発生し、Javaプロセスが異常終了する。 たとえば、すでに正常に処理が完了している場合はJobInstanceAlreadyCompleteExceptionが発生する。

JobExecution
ExecutionContext

JobExecutionはJobの「物理的」な実行を示す。JobInstance とは異なり、同一のJobを再実行する場 合も別のJobExecutionとなる。結果、JobInstanceとJobExecutionは1対多の関係になる。
同一のJobExecution内で処理の進捗状況などのメタデータを共有するための領域として、ExecutionContextがある。 ExecutionContextは主にSpring Batchがフレームワークの状態などを記録するために使用されているが、アプリケーションがExecutionContextへアクセスする手段も提供されている。
JobExecutionContextに格納するオブジェクトは、java.io.Serializableを実装したクラスでなければならない。

StepExecution
ExecutionContext

StepExecutionはStep の「物理的」な実行を示す。JobExecutionとStepExecutionは1対多の関係になる。
JobExecutionと同様に、Step内でデータを共有するための領域ExecutionCotnextがある。データの局所化という観点から、 複数のStepで共有しなくてもよい情報はJobのExecutionContextを使用するのでなく、対象StepのExecutionContextを利用したほうがよい。
StepExecutionContextに格納するオブジェクトは、java.io.Serializableを実装したクラスでなければならない。

JobRepository

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

Spring Batchが重厚にメタデータの管理を行っている理由は、再実行を実現するためである。 バッチ処理を再実行可能にするには、前回実行時のスナップショットを残しておく必要があり、メタデータやJobRepositoryはそのための基盤となっている。

2.3.2.2. Jobの起動

Jobをどのように起動するかについて説明する。

Javaプロセス起動直後にバッチ処理を開始し、バッチ処理が完了後にJavaプロセスを終了するケースを考える。 下図にJavaプロセス起動からバッチ処理を開始までについて処理の流れを示す。

Job Launch Flow
Javaプロセス起動からバッチ処理を開始までの処理の流れ
Javaプロセスの起動とJobの開始

Javaプロセス起動と同時に、Spring Batch上で定義されたJobを開始するためには、Javaを起動するシェルスクリプトを記述するのが一般的である。 Spring Batchが提供するCommandLineJobRunnerを使用すると、ユーザが定義したSpring Batch上のJobを簡単に起動することができる。

CommandLineJobRunnerを使用したジョブの起動コマンドは以下のとおりである。

XMLによるBean定義を行った場合の起動コマンド
java -cp ${CLASSPATH} org.springframework.batch.core.launch.support.CommandLineJobRunner <jobPath> <jobName> <JobArgumentName1>=<value1> <JobArgumentName2>=<value2> ...
ジョブパラメータの指定

CommandLineJobRunnerは起動するJob名だけでなく、引数(ジョブパラメータ)を渡すことも可能である。 引数は前述した例のように、<Job引数名>=<値>の形式で指定する。 すべての引数はCommandLineJobRunnerやJobLauncherが解釈とチェックを行なったうえで、JobExecutionへJobParametersに変換して格納する。 詳細はジョブの起動パラメータを参照のこと。

JobInstanceの登録と復元

JobLauncherがJobRepositoryからJob名と引数に合致するJobInstanceをデータベースから取得する。

  • 該当するJobInstanceが存在しない場合は、JobInstanceを新規登録する。

  • 該当するJobInstanceが存在した場合は、紐付いているJobExecutionを復元する。

    • Spring Batchでは日次実行など繰り返して起動する可能性のあるJobに対しては、JobInstanceがユニークにするためだけの引数を追加する方法がとられている。 たとえば、システム時刻であったり、乱数を引数に追加する方法が挙げられる。
      本ガイドラインで推奨している方法についてはパラメータ変換クラスについてを参照。

2.3.2.3. ビジネスロジックの実行

Spring Batchでは、JobをStepと呼ぶさらに細かい単位に分割する。 Jobが起動すると、Jobは自身に登録されているStepを起動し、StepExecutionを生成する。 Stepはあくまで処理を分割するための枠組みであり、ビジネスロジックの実行はStepから呼び出されるTaskletに任されている。

StepからTaskletへの流れを以下に示す。

Step-Tasklet Flow
StepからTaskletへの処理の流れ

Taskletの実装方法には「チャンクモデル」と「タスクレットモデル」の2つの方式がある。 概要についてはすでに説明しているため、ここではその構造について説明する。

2.3.2.3.1. チャンクモデル

前述したようにチャンクモデルとは、処理対象となるデータを1件ずつ処理するのではなく、一定数の塊(チャンク)を単位として処理を行う方式である。 ChunkOrientedTaskletがチャンク処理をサポートしたTaskletの具象クラスとなる。 このクラスがもつcommit-intervalという設定値により、チャンクに含めるデータの最大件数(以降、「チャンク数」と呼ぶ)を調整することができる。 ItemReader、ItemProcessor、ItemWriterは、いずれもチャンク処理を前提としたインターフェースとなっている。

次に、ChunkOrientedTasklet がどのようにItemReader、ItemProcessor、ItemWriterを呼び出しているかを説明する。

ChunkOrientedTaskletが1つのチャンクを処理するシーケンス図を以下に示す。

Sequence of Chunk processing with ChunkOrientedTasklet
ChunkOrientedTaskletによるチャンク処理

ChunkOrientedTaskletは、チャンク数分だけItemReaderおよびItemProcessor、すなわちデータの読み込みと加工を繰り返し実行する。 チャンク数分のデータすべての読み込みが完了してから、ItemWriterのデータ書き込み処理が1回だけ呼び出され、チャンクに含まれるすべての加工済みデータが渡される。 データの更新処理がチャンクに対して1回呼び出されるように設計されているのは、JDBCのaddBatch、executeBatchのようにI/Oをまとめやすくするためである。

次に、チャンク処理において実際の処理を担うItemReader、ItemProcessor、ItemWriterについて紹介する。 各インターフェースともユーザが独自に実装を行うことが想定されているが、Spring Batchが提供する汎用的な具象クラスでまかなうことができる場合がある。

特にItemProcessorはビジネスロジックそのものが記述されることが多いため、Spring Batchからは具象クラスがあまり提供されていない。 ビジネスロジックを記述する場合はItemProcessorインターフェースを実装する。 ItemProcessorはタイプセーフなプログラミングが可能になるよう、入出力で使用するオブジェクトの型をそれぞれジェネリクスに指定できるようになっている。

以下に簡単なItemProcessorの実装例を示す。

ItemProcessorの実装例
public class MyItemProcessor implements
      ItemProcessor<MyInputObject, MyOutputObject> {  // (1)
  @Override
    public MyOutputObject process(MyInputObject item) throws Exception {  // (2)

        MyOutputObject processedObject = new MyOutputObject();  // (3)

        // Coding business logic for item of input data

    return processedObject; // (4)
  }
}
項番 説明

(1)

入出力で使用するオブジェクトの型をそれぞれジェネリクスに指定したItemProcessorインターフェースを実装する。

(2)

processメソッドを実装する。引数のitemが入力データである。

(3)

出力オブジェクトを作成し、入力データのitemに対して処理したビジネスロジックの結果を格納する。

(4)

出力オブジェクトを返却する。

ItemReaderやItemWriterは様々な具象クラスがSpring Batchから提供されており、それらを利用することで十分な場合が多い。 しかし、特殊な形式のファイルを入出力したりする場合は、独自のItemReaderやItemWriterを実装した具象クラスを作成し使用することができる。

実際のアプリケーション開発時におけるビジネスロジックの実装に関しては、アプリケーション開発の流れを参照。

最後にSpring Batchが提供するItemReader、ItemProcessor、ItemWriterの代表的な具象クラスを示す。

Spring Batchが提供するItemReader、ItemProcessor、ItemWriterの代表的な具象クラス
インターフェース 具象クラス名 概要

ItemReader

FlatFileItemReader

CSVファイルなどの、フラットファイル(非構造的なファイル)の読み込みを行う。Resourceオブジェクトをインプットとし、区切り文字やオブジェクトへのマッピングルールをカスタマイズすることができる。

StaxEventItemReader

XMLファイルの読み込みを行う。名前のとおり、StAXをベースとしたXMLファイルの読み込みを行う実装となっている。

JdbcPagingItemReader
JdbcCursorItemReader

JDBCを使用してSQLを実行し、データベース上のレコードを読み込む。データベース上にある大量のデータを処理する場合は、全件をメモリ上に読み込むことを避け、一度の処理に必要なデータのみの読み込み、破棄を繰り返す必要がある。
JdbcPagingItemReaderはJdbcTemplateを用いてSELECT SQLをページごとに分けて発行することで実現する。一方、JdbcCursorItemReaderはJDBCのカーソルを使用することで、1回のSELECT SQLの発行で実現する。
TERASOLUNA Batch 5.xではMyBatisを利用することを基本とする。

MyBatisCursorItemReader
MyBatisPagingItemReader

MyBatisと連携してデータベース上のレコードを読み込む。MyBatisが提供しているSpring連携ライブラリMyBatis-Springから提供されている。PagingとCursorの違いについては、MyBatisを利用して実現していること以外はJdbcXXXItemReaderと同様。
その他に、JPA実装やHibernateなどと連携してデータベース上のレコードを読み込むJpaPagingItemReader、HibernatePagingItemReader、 HibernateCursorItemReaderが提供されている。 TERASOLUNA Batch 5.xではMyBatisCursorItemReaderを利用することを基本とする。

JmsItemReader
AmqpItemReader

JMSやAMQPからメッセージを受信し、その中に含まれるデータの読み込みを行う。

ItemProcessor

PassThroughItemProcessor

何も行なわない。入力データの加工や修正が不要な場合に使用する。

ValidatingItemProcessor

入力チェックを行う。入力チェックルールの実装には、Spring Batch独自の
org.springframework.batch.item.validator.Validatorを実装する必要がある。
しかし、Springから提供されている汎用的なorg.springframework.validation.ValidatorへのアダプタであるSpringValidatorが提供されており、 org.springframework.validation.Validatorのルールを利用できる。
TERASOLUNA Batch 5.xではValidatingItemProcessorの利用は禁止している。
詳細は、入力チェックを参照。

CompositeItemProcessor

同一の入力データに対し、複数のItemProcessorを逐次的に実行する。ValidatingItemProcessorによる入力チェックの後にビジネスロジックを実行したい場合などに有効。

ItemWriter

FlatFileItemWriter

処理済みのJavaオブジェクトを、CSVファイルなどのフラットファイルとして書き込みを行う。区切り文字やオブジェクトからファイル行へのマッピングルールをカスタマイズできる。

StaxEventItemWriter

処理済みのJavaオブジェクトをXMLファイルとして書き込みを行う。

JdbcBatchItemWriter

JDBCを使用してSQLを実行し、処理済みのJavaオブジェクトをデータベースへ出力する。内部ではJdbcTemplateが使用されている。

MyBatisBatchItemWriter

MyBatisと連携して、処理済みのJavaオブジェクトをデータベースへ出力する。MyBatisが提供しているSpring連携ライブラリMyBatis-Springから提供されている。
TERASOLUNA Batch 5.xでは、JPA実装やHibernate向けのJpaItemWriter、HibernateItemWriterは利用しない。

JmsItemWriter
AmqpItemWriter

処理済みのJavaオブジェクトを、JMSやAMQPでメッセージを送信する。

PassThroughItemProcessorの省略

XMLでジョブを定義する場合は、ItemProcessorの設定を省略することができる。 省略した場合、PassThroughItemProcessorと同様に何もせずに入力データをItemWriterへ受け渡すことになる。

ItemProcessorの省略
<batch:job id="exampleJob">
    <batch:step id="exampleStep">
        <batch:tasklet>
            <batch:chunk reader="reader" writer="writer" commit-interval="10" />
        </batch:tasklet>
    </batch:step>
</batch:job>
2.3.2.3.2. タスクレットモデル

チャンクモデルは、複数の入力データを1件ずつ読み込み、一連の処理を行うバッチアプリケーションに適した枠組みとなっている。 しかし、時にはチャンク処理の型に当てはまらないような処理を実装することもある。 たとえば、システムコマンドを実行したり、制御用テーブルのレコードを1件だけ更新したいような場合などである。

そのような場合には、チャンク処理によって得られる性能面のメリットが少なく、 設計や実装を困難にするデメリットの方が大きいため、タスクレットモデルを使用するほうが合理的である。

タスクレットモデルを使用する場合は、Spring Batchから提供されているTaskletインターフェースをユーザが実装する必要がある。 また、Spring Batchでは以下の具象クラスが提供されているが、TERASOLUNA Batch 5.xでは以降説明しない。

Spring Batchが提供するTaskletの具象クラス
クラス名 概要

SystemCommandTasklet

非同期にシステムコマンドを実行するためのTasklet。commandプロパティに実行したいコマンドを指定する。
システムコマンドは呼び出し元のスレッドと別スレッドで実行されるため、タイムアウトを設定したり、処理中にシステムコマンドの実行スレッドをキャンセルすることも可能である。

MethodInvokingTaskletAdapter

POJOクラスに定義された特定のメソッドを実行するためのTasklet。targetObjectプロパティに対象クラスのBeanを、targetMethodプロパティに実行させたいメソッド名を指定する。
POJOクラスはバッチ処理の終了状態をメソッドの返り値として返却することができるが、その場合は後述するExitStatusをメソッドの返り値とする必要がある。 他の型で返り値を返却した場合は、返り値の内容にかかわらず正常終了した(ExitStatus.COMPLETED)とみなされる。

2.3.2.4. JobRepositoryのメタデータスキーマ

JobRepositoryのメタデータスキーマについて説明する。

なお、Spring Batchのリファレンス Appendix B. Meta-Data Schema にて説明されている内容も含めて、全体像を説明する。

Spring Batchメタデータテーブルは、Javaでそれらを表すドメインオブジェクト(Entityオブジェクト)に対応している。

対応一覧

テーブル

Entityオブジェクト

概要

BATCH_JOB_INSTANCE

JobInstance

ジョブ名、およびジョブパラメータをシリアライズした文字列を保持する。

BATCH_JOB_EXECUTION

JobExecution

ジョブの状態・実行結果を保持する。

BATCH_JOB_EXECUTION_PARAMS

JobExecutionParams

起動時に与えられたジョブパラメータを保持する。

BATCH_JOB_EXECUTION_CONTEXT

JobExecutionContext

ジョブ内部のコンテキストを保持する。

BATCH_STEP_EXECUTION

StepExecution

ステップの状態・実行結果、コミット・ロールバック件数を保持する。

BATCH_STEP_EXECUTION_CONTEXT

StepExecutionContext

ステップ内部のコンテキストを保持する。

JobRepositoryは、各Javaオブジェクトに保存された内容を、テーブルへ正確に格納する責任がある。

6つの全テーブルと相互関係のERDモデルはを以下に示す。

ER Diagram
ER図
2.3.2.4.1. バージョン

データベーステーブルの多くは、バージョンカラムが含まれてる。 Spring Batchは、データベースへの更新を扱う楽観的ロック戦略を採用しているため、このカラムは重要となる。 このレコードは、バージョンカラムの値がインクリメントされるたびに更新されることを意味している。 JobRepositoryが値の更新時に、バージョン番号が変更されている場合、同時アクセスのエラーが発生したことを示すOptimisticLockingFailureExceptionがスローされる。 別のバッチジョブは異なるマシンで実行されているかもしれないが、それらはすべて同じデータベーステーブルを使用しているため、このチェックが必要となる。

2.3.2.4.2. ID(シーケンス)定義

BATCH_JOB_INSTANCE、BATCH_JOB_EXECUTION、およびBATCH_STEP_EXECUTIONは各_IDで終わる列が含まれている。 これらのフィールドは、それぞれのテーブル用主キーとして機能する。 しかし、それらは、データベースで生成されたキーではなく、むしろ個別のシーケンスで生成される。 データベースにドメインオブジェクトのいずれかを挿入した後、それが与えられたキーは、それらが一意にJavaで識別できるように、実際のオブジェクトに設定する必要なためである。
データベースによってはシーケンスをサポートしていないことがある。この場合、テーブルを各シーケンスの代わりに使用している。

2.3.2.4.3. テーブル定義

各テーブルの項目について説明をする。

BATCH_JOB_INSTANCE

BATCH_JOB_INSTANCEテーブルはJobInstanceに関連するすべての情報を保持し、全体的な階層の最上位である。

BATCH_JOB_INSTANCEの定義
カラム名 説明

JOB_INSTANCE_ID

インスタンスを識別する一意のIDで主キーである。

VERSION

バージョンを参照。

JOB_NAME

ジョブの名前。 インスタンスを識別するために必要とされるので非nullである。

JOB_KEY

同じジョブを別々のインスタンスとして一意に識別するためのシリアライズ化されたJobParameters。
同じジョブ名をもつJobInstancesは、異なるJobParameters(つまり、異なるJOB_KEY値)をもつ必要がある。

BATCH_JOB_EXECUTION

BATCH_JOB_EXECUTIONテーブルはJobExecutionオブジェクトに関連するすべての情報を保持する。 ジョブが実行されるたびに、常に新しいJobExecutionでこの表に新しい行が登録される。

BATCH_JOB_EXECUTIONの定義
カラム名 説明

JOB_EXECUTION_ID

一意にこのジョブ実行を識別する主キー。

VERSION

バージョンを参照。

JOB_INSTANCE_ID

このジョブ実行が属するインスタンスを示すBATCH_JOB_INSTANCEテーブルからの外部キー。 インスタンスごとに複数の実行が存在する場合がある。

CREATE_TIME

ジョブ実行が作成された時刻。

START_TIME

ジョブ実行が開始された時刻。

END_TIME

ジョブ実行が成功または失敗に関係なく、終了した時刻を表す。
ジョブが現在実行されていないにもかかわらず、このカラムの値が空であることは、いくつかのエラータイプがあり、フレームワークが最後のセーブを実行できなかったことを示す。

STATUS

ジョブ実行のステータスを表す文字列。BatchStatus列挙オブジェクトが出力する文字列である。

EXIT_CODE

ジョブ実行の終了コードを表す文字列。 CommandLineJobRunnerによる起動の場合、これを数値に変換することができる。

EXIT_MESSAGE

ジョブが終了状態のより詳細な説明を表す文字列。 障害が発生した場合には、可能であればスタックトレースをできるだけ多く含む文字列となる場合がある。

LAST_UPDATED

このレコードのジョブ実行が最後に更新された時刻。

BATCH_JOB_EXECUTION_PARAMS

BATCH_JOB_EXECUTION_PARAMSテーブルは、JobParametersオブジェクトに関連するすべての情報を保持する。 これはジョブに渡された0以上のキーと値とのペアが含まれ、ジョブが実行されたパラメータを記録する役割を果たす。

BATCH_JOB_EXECUTION_PARAMSの定義
カラム名 説明

JOB_EXECUTION_ID

このジョブパラメータが属するジョブ実行を示すBATCH_JOB_EXECUTIONテーブルからの外部キー。

TYPE_CD

String、date、long、またはdoubleのいずれかのデータ型であることを示す文字列。

KEY_NAME

パラメータキー。

STRING_VAL

データ型が文字列である場合のパラメータ値。

DATE_VAL

データ型が日時である場合のパラメータ値。

LONG_VAL

データ型が整数値である場合のパラメータ値。

DOUBLE_VAL

データ型が実数である場合のパラメータ値。

IDENTIFYING

パラメータがジョブインスタンスが一意であることを識別するための値であることを示すフラグ。

ジョブパラメータの制約について
  • BATCH_JOB_EXECUTION_PARAMSに格納するため、パラメータが取りうる値にはサイズによる制限がある。

  • マルチバイト文字を使用する場合は、使用するエンコーディングによりSTRING_VALのサイズを調整することを検討する。

BATCH_JOB_EXECUTION_CONTEXT

BATCH_JOB_EXECUTION_CONTEXTテーブルは、JobのExecutionContextに関連するすべての情報は保持する。 特定のジョブ実行に必要とされるジョブレベルのデータがすべて含まれている。 このデータは、ジョブが失敗した後で処理を再処理する際に取得しなければならない状態を表し、失敗したジョブが「処理を中断したところから始める」ことを可能にする。

BATCH_JOB_EXECUTION_CONTEXTの定義
カラム名 説明

JOB_EXECUTION_ID

このJobのExecutionContextが属するジョブ実行を示すBATCH_JOB_EXECUTIONテーブルからの外部キー。

SHORT_CONTEXT

SERIALIZED_CONTEXTの文字列表現。

SERIALIZED_CONTEXT

シリアライズされたコンテキスト全体。

BATCH_STEP_EXECUTION

BATCH_STEP_EXECUTIONテーブルは、StepExecutionオブジェクトに関連するすべての情報を保持する。 このテーブルには、BATCH_JOB_EXECUTIONテーブルと多くの点で非常に類似しており、各JobExecutionが作られるごとに常にStepごとに少なくとも1つのエントリがある。

BATCH_STEP_EXECUTIONの定義
カラム名 説明

STEP_EXECUTION_ID

一意にこのステップ実行を識別する主キー。

VERSION

バージョンを参照。

STEP_NAME

ステップの名前。

JOB_EXECUTION_ID

このStepExecutionが属するJobExecutionを示すBATCH_JOB_EXECUTIONテーブルからの外部キー。

START_TIME

ステップ実行が開始された時刻。

END_TIME

ステップ実行が成功または失敗に関係なく、終了した時刻を表す。
ジョブが現在実行されていないにもかかわらず、このカラムの値が空であることは、いくつかのエラータイプがあり、フレームワークが最後のセーブを実行できなかったことを示す。

STATUS

ステップ実行のステータスを表す文字列。BatchStatus列挙オブジェクトが出力する文字列である。

COMMIT_COUNT

トランザクションをコミットしている回数。

READ_COUNT

ItemReaderで読み込んだデータ件数。

FILTER_COUNT

ItemProcessorでフィルタリングしたデータ件数。

WRITE_COUNT

ItemWriterで書き込んだデータ件数。

READ_SKIP_COUNT

ItemReaderでスキップしたデータ件数。

WRITE_SKIP_COUNT

ItemWriterでスキップしたデータ件数。

PROCESS_SKIP_COUNT

ItemProcessorでスキップしたデータ件数。

ROLLBACK_COUNT

トランザクションをロールバックしている回数。

EXIT_CODE

ステップ実行の終了コードを表す文字列。 CommandLineJobRunnerによる起動の場合、これを数値に変換することができる。

EXIT_MESSAGE

ステップが終了状態のより詳細な説明を表す文字列。 障害が発生した場合には、可能であればスタックトレースをできるだけ多く含む文字列となる場合がある。

LAST_UPDATED

このレコードのステップ実行が最後に更新された時刻。

BATCH_STEP_EXECUTION_CONTEXT

BATCH_STEP_EXECUTION_CONTEXTテーブルは、StepのExecutionContext に関連するすべての情報を保持する。 特定のステップ実行に必要とされるステップレベルのデータがすべて含まれている。 このデータは、ジョブが失敗した後で処理を再処理する際に取得しなければならない状態を表し、失敗したジョブが「処理を中断したところから始める」ことを可能にする。

BATCH_STEP_EXECUTION_CONTEXTの定義
カラム名 説明

STEP_EXECUTION_ID

このStepのExecutionContextが属するジョブ実行を示すBATCH_STEP_EXECUTIONテーブルからの外部キー。

SHORT_CONTEXT

SERIALIZED_CONTEXTの文字列表現。

SERIALIZED_CONTEXT

シリアライズされたコンテキスト全体。

2.3.2.4.4. DDLスクリプト

Spring Batch CoreのJARファイルには、いくつかのデータベースプラットフォームに応じたリレーショナル表を作成するサンプルスクリプトが含まれている。 これらのスクリプトはそのまま使用、または必要に応じて追加のインデックスと制約を変更することができる。
スクリプトは、org.springframework.batch.coreのパッケージに含まれており、ファイル名は、schema-*.sqlで形成されている。 "*"は、ターゲット・データベース・プラットフォームの短い名前である。

2.3.2.5. 代表的な性能チューニングポイント

Spring Batchにおける代表的な性能チューニングポイントを説明する。

チャンクサイズの調整

リソースへの出力によるオーバヘッドを抑えるために、チャンクサイズを大きくする。
ただし、チャンクサイズを大きくしすぎるとリソース側の負荷が高くなりかえって性能が低下することがあるので、 適度なサイズになるように調整を行う。

フェッチサイズの調整

リソースからの入力によるオーバヘッドを抑えるために、リソースに対するフェッチサイズ(バッファサイズ)を大きくする。

ファイル読み込みの効率化

BeanWrapperFieldSetMapperを使用すると、Beanのクラスとプロパティ名を順番に指定するだけでレコードをBeanにマッピングしてくれる。 しかし、内部で複雑な処理を行うため時間がかかる。マッピングを行う専用のFieldSetMapperインターフェース実装を用いることで処理時間を短縮できる可能性がある。
ファイル入出力の詳細は、ファイルアクセスを参照。

並列処理・多重処理

Spring Batchでは、Step実行の並列化、データ分割による多重処理をサポートしている。並列化もしくは多重化を行い、処理を並列走行させることで性能を改善できる。 しかし、並列数および多重数を大きくしすぎるとリソース側の負荷が高くなりかえって性能が低下することがあるので、適度なサイズになるように調整を行う。
並列処理・多重処理の詳細は、並列処理と多重処理を参照。

分散処理の検討

Spring Batchでは、複数マシンでの分散処理もサポートしている。指針は、並列処理・多重処理と同様である。
分散処理は、基盤設計や運用設計が複雑化するため、本ガイドラインでは説明を行わない。

2.4. TERASOLUNA Batch Framework for Java (5.x)のアーキテクチャ

2.4.1. 概要

TERASOLUNA Batch Framework for Java (5.x)のアーキテクチャ全体像を説明する。

TERASOLUNA Batch Framework for Java (5.x)では、一般的なバッチ処理システムで説明したとおり Spring Batchを中心としたOSSの組み合わせを利用して実現する。

Spring Batchの階層アーキテクチャを含めたTERASOLUNA Batch Framework for Java (5.x)の構成概略図を以下に示す。

TERASOLUNA Batch Framework for Java (5.x) Stack
TERASOLUNA Batch Framework for Java (5.x)の構成概略図
Spring Batchの階層アーキテクチャの説明
アプリケーション

開発者によって書かれたすべてのジョブ定義およびビジネスロジック。

コア

Spring Batch が提供するバッチジョブを起動し、制御するために必要なコア・ランタイム・クラス。

インフラストラクチャ

Spring Batch が提供する開発者およびコアフレームワーク自体が利用する一般的なItemReader/ItemProcessor/ItemWriterの実装。

2.4.2. ジョブの構成要素

ジョブの構成要素を説明するため、ジョブの構成概略図を下記に示す。

Job Components
ジョブの構成概略図

この節では、ジョブとステップについて構成すべき粒度の指針も含めて説明をする。

2.4.2.1. ジョブ

ジョブとは、バッチ処理全体をカプセル化するエンティティであり、ステップを格納するためのコンテナである。
1つのジョブは、1つ以上のステップで構成することができる。

ジョブの定義は、XMLによるBean定義ファイルに記述する。 ジョブ定義ファイルには複数のジョブを定義することができるが、ジョブの管理が煩雑になりやすくなる。

従って、TERASOLUNA Batch Framework for Java (5.x)では以下の指針とする。

1ジョブ=1ジョブ定義ファイル

2.4.2.2. ステップ

ステップとは、バッチ処理を制御をるために必要な情報を定義したものである。 ステップにはチャンクモデルとタスクレットモデルを定義することができる。

チャンクモデル
  • ItemReader、ItemProcessor、およびItemWriterで構成される。

タスクレットモデル
  • Taskletだけで構成される。

バッチ処理で考慮する原則と注意点にあるとおり、 単一のバッチ処理では、可能な限り簡素化し、複雑な論理構造を避ける必要がある。

従って、TERASOLUNA Batch Framework for Java (5.x)では以下の指針とする。

1ステップ=1バッチ処理=1ビジネスロジック

チャンクモデルでのビジネスロジック分割

1つのビジネスロジックが複雑で規模が大きくなる場合、ビジネスロジックを分割することがある。 概略図を見るとわかるとおり、1つのステップには1つのItemProcessorしか設定できないため、ビジネスロジックの分割ができないように思える。 しかし、CompositeItemProcssorという複数のItemProcessorをまとめるItemProcessorがあり、 この実装を使うことでビジネスロジックを分割して実行することができる。

2.4.3. ステップの実装方式

2.4.3.1. チャンクモデル

チャンクモデルの定義と使用目的を説明する。

定義

ItemReader、ItemProcessorおよびItemWriter実装とチャンク数をChunkOrientedTaskletに設定する。それぞれの役割を説明する。

  • ChunkOrientedTasklet・・・ItemReader/ItemProcessorを呼び出し、チャンクを作成する。作成したチャンクをItemWriterへ渡す。

  • ItemReader・・・入力データを読み込む。

  • ItemProcessor・・・読み込んだデータを加工する。

  • ItemWriter・・・加工されたデータをチャンク単位で出力する。

チャンクモデルの概要は、 チャンクモデル を参照。

チャンクモデルのジョブ設定例
<batch:job id="exampleJob">
    <batch:step id="exampleStep">
        <batch:tasklet>
            <batch:chunk reader="reader"
                         processor="processor"
                         writer="writer"
                         commit-interval="100" />
        </batch:tasklet>
    </batch:step>
</batch:job>
使用目的

一定件数のデータをまとめて処理を行うため、大量データを取り扱う場合に用いられる。

2.4.3.2. タスクレットモデル

タスクレットモデルの定義・使用目的を説明する。

定義

Tasklet実装だけを設定する。
タスクレットモデルの概要は、 タスクレットモデル を参照。

  .タスクレットモデルのジョブ設定例
----
<batch:job id="exampleJob">
    <batch:step id="exampleStep">
        <batch:tasklet ref="myTasklet">
    </batch:step>
</batch:job>
----
使用目的

システムコマンドの実行など、入出力を伴わない処理を実行するために用いられる。
また、一括でデータをコミットしたい場合にも用いられる。

2.4.3.3. チャンクモデルとタスクレットモデルの機能差

チャンクモデルとタスクレットモデルの機能差について説明する。 ここでは、詳細については各機能の節を参照してもらい、ここでは概略のみにとどめる。

機能差一覧
機能 チャンクモデル タスクレットモデル

構成要素

ItemReader/ItemProcessor/ItemWriter/ChunkOrientedTaskletで構成される。

Taksletのみで構成される。

トランザクション

チャンク単位にトランザクションが発生する。

1トランザクションで処理する。

推奨する再処理方式

リラン、リスタートを利用できる。

リランのみ利用することを原則とする。

例外ハンドリング

リスナーを使うことでハンドリング処理が容易になっている。独自実装も可能である。

独自実装が必要である。

2.4.4. ジョブの起動方式

ジョブの起動方式について説明する。ジョブの起動方式には以下のものがある。

それぞれの起動方式について説明する。

2.4.4.1. 同期実行方式

同期実行方式とは、ジョブを起動してからジョブが終了するまで起動元へ制御が戻らない実行方式である。

ジョブスケジューラからジョブを起動する概略図を示す。

Synchronized Execution
同期実行概略図
  1. ジョブスケジューラからジョブを起動するためのシェルスクリプトを起動する。
    シェルスクリプトから終了コード(数値)が返却するまでジョブスケジューラは待機する。

  2. シェルスクリプトからジョブを起動するためにCommandLineJonRunnerを起動する。
    CommandLineJonRunnerから終了コード(数値)が返却するまでシェルスクリプトは待機する。

  3. CommandLineJonRunnerはジョブを起動する。ジョブは処理終了後に終了コード(文字列)をCommandLineJonRunnerへ返却する。
    CommandLineJonRunnerは、ジョブから返却された終了コード(文字列)から終了コード(数値)に変換してシェルスクリプトへ返却する。

2.4.4.2. 非同期実行方式

非同期実行方式とは、起動元とは別の実行基盤(別スレッドなど)でジョブを実行することで、ジョブ起動後すぐに起動元へ制御が戻る方式である。 この方式の場合、ジョブの実行結果はジョブ起動とは別の手段で取得する必要がある。

TERASOLUNA Batch Framework for Java (5.x)では、以下に示す2とおりの方法について説明をする。

その他の非同期実行方式

MQなどのメッセージを利用して非同期実行を実現することもできるが、ジョブ実行のポイントは同じであるため、{batch5_guide}では説明は割愛する。

2.4.4.2.1. 非同期実行方式(DBポーリング)

非同期実行(DBポーリング)とは、 ジョブ実行の要求をデータベースに登録し、その要求をポーリングして、ジョブを実行する方式である。

TERASOLUNA Batch Framework for Java (5.x)は、DBポーリング機能を提供している。提供しているDBポーリングによる起動の概略図を示す。

DB Polling
DBポーリング概略図
  1. ユーザはデータベースへジョブ要求を登録する。

  2. DBポーリング機能は、定期的にジョブ要求の登録を監視していて、登録されたことを検知すると該当するジョブを実行する。

    • SimpleJobOperatorからジョブを起動し、ジョブ終了後にJobExecutionIdを受け取る。

    • JobExecutionIdとは、ジョブ実行を一意に識別するIDであり、このIDを使ってJobRepositoryから実行結果を参照する。

    • ジョブの実行結果は、Spring Batchの仕組みによって、JobRepositoryへ登録される。

    • DBポーリング自体が非同期で実行されている。

  3. DBポーリング機能は、SimpleJobOperatorから返却されたJobExecutionIdとスタータスを起動したジョブ要求に対して更新を行う。

  4. ジョブの処理経過・結果は、JobExecutionIdを利用して別途参照を行う。

2.4.4.2.2. 非同期実行方式(Webコンテナ)

非同期実行(Webコンテナ)とは、 Webコンテナ上のWebアプリケーションへのリクエストを契機にジョブを非同期実行する方式である。 Webアプリケーションは、ジョブの終了を待たずに起動後すぐにレスポンスを返却することができる。

Web Container
Webコンテナ概略図
  1. クライアントからWebアプリケーションへリクエストを送信する。

  2. Webアプリケーションは、リクエストから要求されたジョブを非同期実行する。

    • SimpleJobOperatorからジョブを起動直後にJobExecutionIdを受け取る。

    • ジョブの実行結果は、Spring Batchの仕組みによって、JobRepositoryへ登録される。

  3. Webアプリケーションは、ジョブの終了を待たずにクライアントへレスポンスを返信する。

  4. ジョブの処理経過・結果は、JobExecutionIdを利用して別途参照を行う。

また、 TERASOLUNA Server Framework for Java (5.x)で構築されるWebアプリケーションと連携することも可能である。

2.4.5. 利用する際の検討ポイント

TERASOLUNA Batch Framework for Java (5.x)を利用する際の検討ポイントを示す。

ジョブ起動方法
同期実行方式

スケジュールどおりにジョブを起動したり、複数のジョブを組み合わせてバッチ処理行う場合に利用する。

非同期実行方式(DBポーリング)

ディレード処理、処理時間が短いジョブの連続実行、大量ジョブの集約などに利用する。

非同期実行方式(Webコンテナ)

DBポーリングと同様だが、起動までの即時性が求められる場合にはこちらを利用する。

実装方式
チャンクモデル

大量データを効率よく処理したい場合に利用する。

タスクレットモデル

シンプルな処理や、定型化しにくい処理、データを一括で処理したい場合に利用する。

3. アプリケーション開発の流れ

3.1. バッチアプリケーションの開発

バッチアプリケーションの開発について、以下の流れで説明する。

3.1.1. ブランクプロジェクトとは

ブランクプロジェクトとは、Spring BatchやMyBatis3をはじめとする各種設定を予め行った開発プロジェクトの雛形であり、 アプリケーション開発のスタート地点である。
本ガイドラインでは、シングルプロジェクト構成のブランクプロジェクトを提供する。
構成の説明については、プロジェクトの構成を参照のこと。

TERASOLUNA Server 5.xとの違い

TERASOLUNA Server 5.xはマルチプロジェクト構成を推奨している。 この理由は主に、以下の様なメリットを享受するためである。

  • 環境差分の吸収しやすくする

  • ビジネスロジックとプレゼンテーションを分離しやすくする

しかし、本ガイドラインではTERASOLUNA Server 5.xと異なりシングルプロジェクト構成としている。

これは、前述の点はバッチアプリケーションの場合においても考慮すべきだが、 シングルプロジェクト構成にすることで1ジョブに関連する資材を近づけることを優先している。
また、バッチアプリケーションの場合、 環境差分はプロパティファイルや環境変数で切替れば十分なケースが多いことも理由の1つである。

3.1.2. プロジェクトの作成

Maven Archetype Pluginarchetype:generateを使用して、プロジェクトを作成する方法を説明する。

作成環境の前提について

以下を前提とし説明する。

  • Java SE Development Kit 8

  • Apache Maven 3.x

    • インターネットに繋がっていること

    • インターネットにプロキシ経由で繋ぐ場合は、Mavenのプロキシ設定 が行われていること

  • IDE

    • Spring Tool Suite / Eclipse 等

プロジェクトを作成するディレクトリにて、以下のコマンドを実行する。

コマンドプロンプト(Windows)
C:\xxx> mvn archetype:generate^
  -DarchetypeGroupId=org.terasoluna.batch^
  -DarchetypeArtifactId=terasoluna-batch-archetype^
  -DarchetypeVersion=5.0.0.RELEASE
Bash(Unix, Linux, …​)
$ mvn archetype:generate \
  -DarchetypeGroupId=org.terasoluna.batch \
  -DarchetypeArtifactId=terasoluna-batch-archetype \
  -DarchetypeVersion=5.0.0.RELEASE

その後、利用者の状況に合わせて、以下を対話式に設定する。

  • groupId

  • artifactId

  • version

  • package

以下の値を設定し実行した例を示す。

ブランクプロジェクトの各要素の説明
項目名 設定例

groupId

com.example.batch

artifactId

batch

version

1.0.0-SNAPSHOT

package

com.example.batch

実行例
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.4:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.4:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.4:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode

(.. omitted)

Define value for property 'groupId': : com.example.batch
Define value for property 'artifactId': : batch
Define value for property 'version':  1.0-SNAPSHOT: : 1.0.0-SNAPSHOT
Define value for property 'package':  com.example.batch: :
Confirm properties configuration:
groupId: com.example.batch
artifactId: batch
version: 1.0.0-SNAPSHOT
package: com.example.batch
 Y: : y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: terasoluna-batch-archetype:5.0.0-SNAPSHOT
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example.batch
[INFO] Parameter: artifactId, Value: batch
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example.batch
[INFO] Parameter: packageInPathFormat, Value: com/example/batch
[INFO] Parameter: package, Value: com.example.batch
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: groupId, Value: com.example.batch
[INFO] Parameter: artifactId, Value: batch
[INFO] project created from Archetype in dir: C:\workspaces\zzz\batch
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:56 min
[INFO] Finished at: 2017-02-07T17:09:52+09:00
[INFO] Final Memory: 16M/240M
[INFO] ------------------------------------------------------------------------

以上により、プロジェクトの作成が完了した。

正しく作成出来たかどうかは、以下の要領で確認できる。

正しく作成できたことの確認(Bash)
$ mvn clean dependency:copy-dependencies -DoutputDirectory=lib package
$ java -cp 'lib/*:target/*' org.springframework.batch.core.launch.support.CommandLineJobRunner \
    META-INF/jobs/job01/job01.xml job01

以下の出力が得られれば正しく作成できている。

出力例
$ mvn clean dependency:copy-dependencies -DoutputDirectory=lib package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building TERASOLUNA Batch Framework for Java (5.x) Blank Project 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]

(.. omitted)

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.618 s
[INFO] Finished at: 2017-02-07T17:32:27+09:00
[INFO] Final Memory: 26M/250M
[INFO] ------------------------------------------------------------------------

$ java -cp 'lib/*;target/*' org.springframework.batch.core.launch.support.CommandLineJobRunner META-INF/jobs/job01/job01.xml job01
[2017/02/07 17:35:26] [main] [o.s.c.s.ClassPathXmlApplicationContext] [INFO ] Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@62043840: startup date [Tue Feb 07 17:35:26 JST 2017]; root of context hierarchy
(.. ommited)
[2017/02/07 17:35:27] [main] [o.s.b.c.l.s.SimpleJobLauncher] [INFO ] Job: [FlowJob: [name=job01]] launched with the following parameters: [{jsr_batch_run_id=1}]
[2017/02/07 17:35:27] [main] [o.s.b.c.j.SimpleStepHandler] [INFO ] Executing step: [job01.step01]
[2017/02/07 17:35:27] [main] [o.s.b.c.l.s.SimpleJobLauncher] [INFO ] Job: [FlowJob: [name=job01]] completed with the following parameters: [{jsr_batch_run_id=1}] and the following status: [COMPLETED]
[2017/02/07 17:35:27] [main] [o.s.c.s.ClassPathXmlApplicationContext] [INFO ] Closing org.springframework.context.support.ClassPathXmlApplicationContext@62043840: startup date [Tue Feb 07 17:35:26 JST 2017]; root of context hierarchy

3.1.3. プロジェクトの構成

前述までで作成したプロジェクトの構成について説明する。 プロジェクトは、以下の点を考慮した構成となっている。

  • 起動方式に依存しないジョブの実装を実現する

  • Spring BatchやMyBatisといった各種設定の手間を省く

  • 環境依存の切替を容易にする

以下に構成を示し、各要素について説明する。
(わかりやすさのため、前述のmvn archetype:generate実行時の出力を元に説明する。)

BlankProject Structure
プロジェクトのディレクトリ構造
ブランクプロジェクトの各要素の説明
項番 説明

(1)

バッチアプリケーション全体の各種クラスを格納するrootパッケージ。

(2)

1ジョブに関する各種クラスを格納するパッケージ。
ここには、DTO、TaskletやProcessorの実装、MyBatis3のMapperインターフェースを格納する。
本ガイドラインでは格納方法に制約は設けないので、これは一例として参考にしてほしい。

初期状態を参考にユーザにて自由にカスタムしてよいが、 ジョブ固有の資材を判断しやすくすることに配慮してほしい。

(3)

バッチアプリケーション全体に関わる設定ファイル。
初期状態では、データベースの接続や、非同期実行に関する設定を記述している。 ユーザにて、自由に追記してよい。

(4)

Logback(ログ出力)の設定ファイル。

(5)

BeanValidationを用いた入力チェックにて、エラーとなった際に表示するメッセージを定義する設定ファイル。
初期状態では、BeanValidationと、その実装であるHibernateValidatorのデフォルトメッセージを定義したうえで、 すべてコメントアウトしている。
この状態ではデフォルトメッセージを使うため、メッセージをカスタマイズしたい場合にのみ コメントインし任意のメッセージに修正すること。

(6)

MyBatis3のMapperインターフェースの対となるMapper XMLファイル。

(7)

主にログ出力時に用いるメッセージを定義するプロパティファイル。

(8)

ジョブ固有のBean定義ファイルを格納するディレクトリ。
階層構造はジョブ数に応じて自由に構成してよい。

(9)

バッチアプリケーション全体に関わるBean定義ファイルを格納するディレクトリ。
Spring BatchやMyBatisの初期設定や、同期/非同期といった起動契機に依らずにジョブを起動するための設定を行っている。

(10)

非同期実行(DBポーリング)機能に関連する設定を記述したBean定義ファイル。

(11)

ジョブ固有のBean定義ファイルにてimportすることで、各種設定を削減するためのBean定義ファイル。
これをimportすることで、ジョブは起動契機によるBean定義の差を吸収することが出来る。

(12)

Spring Batchの挙動や、ジョブ共通の設定に対するBean定義ファイル。

また、各ファイルの関連図を以下に示す。

Files Relation
各ファイルの関連図

3.1.4. 開発の流れ

ジョブを開発する一連の流れについて説明する。
ここでは、詳細な説明ではなく、大まかな流れを把握することを主眼とする。

3.1.4.1. IDEへの取り込み

生成したプロジェクトはMavenのプロジェクト構成に従っているため、 各種IDEによって、Mavenプロジェクトとしてimportする。
詳細な手順は割愛する。

3.1.4.2. アプリケーション全体の設定

ユーザの状況に応じて以下をカスタマイズする。

これら以外の設定をカスタマイズする方法については、個々の機能にて説明する。

3.1.4.2.1. pom.xmlのプロジェクト情報

プロジェクトのPOMには以下の情報が仮の値で設定されているため、状況に応じて設定すること。

  • プロジェクト名(name要素)

  • プロジェクト説明(description要素)

  • プロジェクトURL(url要素)

  • プロジェクト創設年(inceptionYear要素)

  • プロジェクトライセンス(licenses要素)

  • プロジェクト組織(organization要素)

3.1.4.2.2. データベース関連の設定

データベース関連の設定は複数箇所にあるため、それぞれを修正すること。

pom.xml
<!-- (1) -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>
batch-application.properties
# (2)
# Admin DataSource settings.
admin.jdbc.driver=org.h2.Driver
admin.jdbc.url=jdbc:h2:mem:batch-admin;DB_CLOSE_DELAY=-1
admin.jdbc.username=sa
admin.jdbc.password=

# (2)
# Job DataSource settings.
#jdbc.driver=org.postgresql.Driver
#jdbc.url=jdbc:postgresql://localhost:5432/postgres
#jdbc.username=postgres
#jdbc.password=postgres
jdbc.driver=org.h2.Driver
jdbc.url=jdbc:h2:mem:batch;DB_CLOSE_DELAY=-1
jdbc.username=sa
jdbc.password=

# (3)
# Spring Batch schema initialize.
data-source.initialize.enabled=true
spring-batch.schema.script=classpath:org/springframework/batch/core/schema-h2.sql
terasoluna-batch.commit.script=classpath:org/terasoluna/batch/async/db/schema-commit.sql
META-INF/spring/launch-context.xml
<!-- (3) -->
<jdbc:initialize-database data-source="adminDataSource"
                          enabled="${data-source.initialize.enabled:false}"
                          ignore-failures="ALL">
    <jdbc:script location="${spring-batch.schema.script}" />
    <jdbc:script location="${terasoluna-batch.commit.script}" />
</jdbc:initialize-database>

<!-- (4) -->
<bean id="adminDataSource" class="org.apache.commons.dbcp2.BasicDataSource"
      destroy-method="close"
      p:driverClassName="${admin.jdbc.driver}"
      p:url="${admin.jdbc.url}"
      p:username="${admin.jdbc.username}"
      p:password="${admin.jdbc.password}"
      p:maxTotal="10"
      p:minIdle="1"
      p:maxWaitMillis="5000"
      p:defaultAutoCommit="false"/>

<!-- (4) -->
<bean id="jobDataSource" class="org.apache.commons.dbcp2.BasicDataSource"
      destroy-method="close"
      p:driverClassName="${jdbc.driver}"
      p:url="${jdbc.url}"
      p:username="${jdbc.username}"
      p:password="${jdbc.password}"
      p:maxTotal="10"
      p:minIdle="1"
      p:maxWaitMillis="5000"
      p:defaultAutoCommit="false" />

<!-- (5) -->
<bean id="jobSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
      p:dataSource-ref="jobDataSource" >
    <property name="configuration">
        <bean class="org.apache.ibatis.session.Configuration"
            p:localCacheScope="STATEMENT"
            p:lazyLoadingEnabled="true"
            p:aggressiveLazyLoading="false"
            p:defaultFetchSize="1000"
            p:defaultExecutorType="REUSE" />
    </property>
</bean>
META-INF/spring/async-batch-daemon.xml
<!-- (5) -->
<bean id="adminSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
      p:dataSource-ref="adminDataSource" >
    <property name="configuration">
        <bean class="org.apache.ibatis.session.Configuration"
              p:localCacheScope="STATEMENT"
              p:lazyLoadingEnabled="true"
              p:aggressiveLazyLoading="false"
              p:defaultFetchSize="1000"
              p:defaultExecutorType="REUSE" />
    </property>
</bean>
データベース関連の設定における各要素の説明
項番 説明

(1)

pom.xmlでは利用するデータベースへの接続に使用するJDBCドライバの依存関係を定義する。
初期状態ではH2 Database(インメモリデータベース)とPostgreSQLが設定されているが、必要に応じて追加削除を行うこと。

(2)

JDBCドライバの接続設定をする。
- admin.jdbc.xxxはSpring BatchやTERASOLUNA Batch 5.xが利用する
- jdbc.xxx~はジョブ個別が利用する

(3)

Spring BatchやTERASOLUNA Batch 5.xが利用するデータベースの初期化処理を実行するか否か、および、利用するスクリプトを定義する。
Spring BatchはJobRepositoryにアクセスするため、データベースが必須となる。 また、TERASOLUNA Batch 5.xは非同期実行(DBポーリング)にてジョブ要求テーブルにアクセスするため、 データベースが必須となる。
有効にするか否かは、以下を基準とするとよい。
- H2 Databaseを利用する場合は有効にする。無効にするとJobRepositoryジョブ要求テーブルにアクセスできずエラーになる。
- H2 Databaseを利用しない場合は事故を予防するために無効にする。

(4)

データソースの設定をする。
必要に応じて接続数等をチューニングする。

(5)

MyBatisの挙動を設定する。
必要に応じてフェッチサイズ等をチューニングする。

3.1.5. ジョブの作成

ジョブの作成方法は、以下を参照のこと。

3.1.6. プロジェクトのビルドと実行

プロジェクトのビルドと実行について説明する。

3.1.6.1. アプリケーションのビルド

プロジェクトのルートディレクトリに移動し、以下のコマンドを発行する。

ビルド(Windows/Bash)
$ mvn clean dependency:copy-dependencies -DoutputDirectory=lib package

これにより、以下が生成される。

  • <ルートディレクトリ>/target/<archetypeId>-<version>.jar

    • 作成したバッチアプリケーションのJarが生成される

  • <ルートディレクトリ>/lib/(依存Jarファイル)

    • 依存するJarファイル一式がコピーされる

試験環境や商用環境へ配備する際は、これらのJarファイルを任意のディレクトリにコピーすればよい。

3.1.6.2. 環境に応じた設定ファイルの切替

プロジェクトのpom.xmlでは、初期値として以下のProfileを設定している。

pom.xmlのProfiles設定
<profiles>
    <!-- Including application properties and log settings into package. (default) -->
    <profile>
        <id>IncludeSettings</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <exclude-property/>
            <exclude-log/>
        </properties>
    </profile>

    <!-- Excluding application properties and log settings into package. -->
    <profile>
        <id>ExcludeSettings</id>
        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>
        <properties>
            <exclude-property>batch-application.properties</exclude-property>
            <exclude-log>logback.xml</exclude-log>
        </properties>
    </profile>
</profiles>

ここでは、環境依存となる設定ファイルを含めるかどうかを切替ている。 この設定を活用して、環境配備の際に設定ファイルを別途配置することで環境差分を吸収することができる。 また、これを応用して、試験環境と商用環境でJarに含める設定ファイルを変えることもできる。 以下に、一例を示す。

環境ごとに設定ファイルを切替えるpom.xmlの記述例
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
        <resource>
            <directory>${project.root.basedir}/${project.config.resource.directory.rdbms}</directory>
        </resource>
    </resources>
</build>

<profiles>
    <profile>
        <id>postgresql9-local</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <dependencies>
            <dependency>
                <groupId>org.postgresql</groupId>
                <artifactId>postgresql</artifactId>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
        <properties>
            <project.config.resource.directory.rdbms>config/rdbms/postgresql9/local</project.config.resource.directory.rdbms>
        </properties>
    </profile>
    <profile>
        <id>postgresql9-it</id>
        <dependencies>
            <dependency>
                <groupId>org.postgresql</groupId>
                <artifactId>postgresql</artifactId>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
        <properties>
            <project.config.resource.directory.rdbms>config/rdbms/postgresql9/it</project.config.resource.directory.rdbms>
        </properties>
    </profile>
</profiles>

なお、MavenのProfileは以下の要領で、コマンド実行時に有効化することができる。
必要に応じて、複数Profileを有効化することもできる。必要に応じて、有効活用して欲しい。

MavenのProfileを有効化する例
$ mvn -P profile-1,profile-2
3.1.6.2.1. アプリケーションの実行

前段でビルドした結果を元に、ジョブを実行する例を示す。
archetypeIdversionはユーザの環境に応じて読み替えて欲しい。

コマンドプロンプト(Windows)
C:\xxxx> java -cp target\archetypeId-version.jar;lib\*^
    org.springframework.batch.core.launch.support.CommandLineJobRunner^
    META-INF/jobs/job01.xml job01
シェル(Unix, Linux, …​)
$ java -cp 'target/archetypeId-version.jar:lib/*' \
    org.springframework.batch.core.launch.support.CommandLineJobRunner \
    META-INF/jobs/job01.xml job01
javaコマンドが返却する終了コードをハンドリングする必要性

実際のシステムでは、 ジョブスケジューラからジョブを発行する際にjavaコマンドを直接発行するのではなく、 java起動用のシェルスクリプトを挟んで起動することが一般的である。

これはjavaコマンド起動前の環境変数を設定するためや、javaコマンドの終了コードをハンドリングするためである。 この、javaコマンドの終了コードをハンドリングは、以下を理由に常に行うことを推奨する。

  • javaコマンドの終了コードは正常:0、異常:1であるが、ジョブスケジューラはジョブの成功/失敗を終了コードの範囲で判断する。 そのため、ジョブスケジューラの設定によっては、javaコマンドは異常終了したのにもかかわらずジョブスケジューラは正常終了したと判断してしまう。

  • OSやジョブスケジューラが扱うことができる終了コードは有限の範囲である。

    • OSやジョブスケジューラの仕様に応じて、ユーザにて使用する終了コードの範囲を定義することが重要である。

    • 一般的に、POSIX標準で策定されている0から255の間に収めることが多い。

      • TERASOLUNA Batch 5.xでは、正常:0、それ以外:255として終了コードを返却するよう設定している。

以下に、終了コードのハンドリング例を示す。

終了コードのハンドリング例
#!/bin/bash

# ..omitted.

java -cp ...
RETURN_CODE=$?
if [ $RETURN_CODE = 1 ]; then
   return 255
else
   return $RETURN_CODE
fi

3.2. チャンクモデルジョブの作成

3.2.1. Overview

チャンクモデルジョブの作成方法について説明する。 チャンクモデルのアーキテクチャについては、Spring Batchのアーキテクチャを参照のこと。

ここでは、チャンクモデルジョブの構成要素について説明する。

3.2.1.1. 構成要素

チャンクモデルジョブの構成要素を以下に示す。 これらの構成要素をBean定義にて組み合わせることで1つのジョブを実現する。

チャンクモデルジョブの構成要素
項番 名称 役割 設定必須 実装必須

1

ItemReader

様々なリソースからデータを取得するためのインターフェース。
Spring Batchにより、フラットファイルや
データベースを対象とした実装が提供されているため、ユーザにて作成する必要はない。

2

ItemProcessor

入力から出力へデータを加工するためのインターフェース。
ユーザは必要に応じてこのインターフェースをimplementsし、ビジネスロジックを実装する。

3

ItemWriter

様々なリソースへデータを出力するためのインターフェース。
ItemReaderと対になるインターフェースと考えてよい。
Spring Batchにより、フラットファイルや
データベースのための実装が提供されているため、ユーザにて作成する必要はない。

この表のポイントは以下である。

  • 入力リソースから出力リソースへ単純にデータを移し替えるだけであれば、設定のみで実現できる。

  • ItemProcessorは、必要が生じた際にのみ実装すればよい。

以降、これらの構成要素を用いたジョブの実装方法について説明する。

3.2.2. How to use

ここでは、実際にチャンクモデルジョブを実装する方法について、以下の順序で説明する。

3.2.2.1. ジョブの設定

Bean定義ファイルにて、チャンクモデルジョブを構成する要素の組み合わせ方を定義する。 以下に例を示し、構成要素の繋がりを説明する。

Bean定義ファイルの例(チャンクモデル)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:batch="http://www.springframework.org/schema/batch"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
             http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
             http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

    <!-- (1) -->
    <import resource="classpath:META-INF/spring/job-base-context.xml"/>

    <!-- (2) -->
    <context:annotation-config/>

    <!-- (3) -->
    <context:component-scan
        base-package="org.terasoluna.batch.functionaltest.app.common" />

    <!-- (4) -->
    <mybatis:scan
        base-package="org.terasoluna.batch.functionaltest.app.repository.mst"
        factory-ref="jobSqlSessionFactory"/>

    <!-- (5) -->
    <bean id="reader"
          class="org.mybatis.spring.batch.MyBatisCursorItemReader" scope="step"
          p:queryId="org.terasoluna.batch.functionaltest.app.repository.mst.CustomerRepository.findAll"
          p:sqlSessionFactory-ref="jobSqlSessionFactory"/>

    <!-- (6) -->
    <!-- Item Processor -->
    <!-- Item Processor in order that based on the Bean defined by the annotations, not defined here -->

    <!-- (7) -->
    <bean id="writer"
          class="org.springframework.batch.item.file.FlatFileItemWriter"
          scope="step"
          p:resource="file:#{jobParameters[outputFile]}">
        <property name="lineAggregator">
            <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
                <property name="fieldExtractor">
                    <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
                          p:names="customerId,customerName,customerAddress,customerTel,chargeBranchId"/>
                </property>
            </bean>
        </property>
    </bean>

    <!-- (8) -->
    <batch:job id="jobCustomerList01" job-repository="jobRepository"> <!-- (9) -->
        <batch:step id="jobCustomerList01.step01"> <!-- (10) -->
            <batch:tasklet transaction-manager="jobTransactionManager"> <!-- (11) -->
                <batch:chunk reader="reader"
                             processor="processor"
                             writer="writer"
                             commit-interval="10" /> <!-- (12) -->
            </batch:tasklet>
        </batch:step>
    </batch:job>
</beans>
ItemProcessor実装クラスの設定
@Component("processor") // (6)
public class CustomerProcessor implement ItemProcessor<Customer, Customer> {
  // omitted
}
項番 説明

(1)

TERASOLUNA Batch 5.xを利用する際に、常に必要なBean定義を読み込む設定をインポートする。

(2)

アノテーションによるBean定義の有効化を行う。ItemProcessorやListenerなどを実装する際に、(3)と合わせて利用する。

(3)

コンポーネントスキャン対象のベースパッケージを設定する。アノテーションによるBean定義を行う場合は、(2)と合わせて利用する。

(5)

ItemReaderの設定。
ItemReaderの詳細は、 データベースアクセスファイルアクセス を参照のこと。

(6)

ItemProcessorは、(2),(3)によりアノテーションにて定義することができ、Bean定義ファイルで定義する必要がない。

(7)

ItemWriterの設定。
ItemWriterの詳細は、 データベースアクセスファイルアクセス を参照のこと。

(8)

ジョブの設定。
id属性に設定する値は、1つのバッチアプリケーションに含まれる全ジョブの範囲において、一意とすること。

(9)

JobRepositoryの設定。
job-repository属性に設定する値は、特別な理由がない限りjobRepository固定とすること。
これにより、すべてのジョブが1つのJobRepositoryで管理できる。 jobRepositoryのBean定義は、(1)により解決する。

(10)

ステップの設定。
id属性に設定する値は、1つのジョブ内で一意とすること。
接頭辞として(8)で設定したid属性を付加した形式にし、 ステップもジョブと同様にバッチアプリケーションに含まれる全ジョブの範囲において一意とすると、 ログ出力や異常発生時の特定をはじめ、様々な場面で有効活用できる。
よって、特別な理由がない限り<ジョブのid>.<step名>とすること。

(11)

タスクレットの設定。
transaction-manager属性に設定する値は、特別な理由がない限りjobTransactionManager固定とすること。
これにより、(12)のcommit-intervalごとにトランザクションが管理される。 詳細については、トランザクション制御を参照のこと。
jobTransactionManagerのBean定義は、(1)により解決する。

(12)

チャンクモデルジョブの設定。
readerprocessorwriterの各属性に対し、 前段までで定義したItemReaderItemProcessorItemWriterのBeanIDを指定する。
commit-interval属性に1チャンクあたりの入力データ件数を設定する。

commit-intervalのチューニング

commit-intervalはチャンクモデルジョブにおける、性能上のチューニングポイントである。

前述の例では10件としているが、利用できるマシンリソースやジョブの特性によって適切な件数は異なる。 複数のリソースにアクセスしてデータを加工するジョブであれば10件から100件程度で処理スループットが頭打ちになることもある。 一方、入出力リソースが1:1対応しておりデータを移し替える程度のジョブであれば5000件や10000件でも処理スループットが伸びることがある。

ジョブ実装時のcommit-intervalは100件程度で仮置きしておき、 その後に実施した性能測定の結果に応じてジョブごとにチューニングするとよい。

3.2.2.2. コンポーネントの実装

ここでは主に、ItemProcessorを実装する方法について説明する。

他のコンポーネントについては、以下を参照のこと。

3.2.2.2.1. ItemProcessorの実装

ItemProcessorの実装方法を説明する。

ItemProcessorは、以下のインターフェースが示すとおり、入力リソースから取得したデータ 1件 を元に、 出力リソースに向けたデータ 1件 を作成する役目を担う。 つまり、ItemProcessorはデータ 1件 に対するビジネスロジックを実装する箇所、と言える。

ItemProcessorインターフェース
public interface ItemProcessor<I, O> {
    O process(I item) throws Exception;
}

なお、インターフェースが示すIOは以下のとおり同じ型でも異なる型でもよい。 同じ型であれば入力データを一部修正することを意味し、 異なる型であれば入力データを元に出力データを生成することを意味する。

ItemProcessor実装例(入出力が同じ型)
@Component
public class AmountUpdateItemProcessor implements
        ItemProcessor<SalesPlanDetail, SalesPlanDetail> {

    @Override
    public SalesPlanDetail process(SalesPlanDetail item) throws Exception {
        item.setAmount(new BigDecimal("1000"));
        return item;
    }
}
ItemProcessor実装例(入出力が異なる型)
@Component
public class UpdateItemFromDBProcessor implements
        ItemProcessor<SalesPerformanceDetail, SalesPlanDetail> {

    @Inject
    CustomerRepository customerRepository;

    @Override
    public SalesPlanDetail process(SalesPerformanceDetail readItem) throws Exception {
        Customer customer = customerRepository.findOne(readItem.getCustomerId());

        SalesPlanDetail writeItem = new SalesPlanDetail();
        writeItem.setBranchId(customer.getChargeBranchId());
        writeItem.setYear(readItem.getYear());
        writeItem.setMonth(readItem.getMonth());
        writeItem.setCustomerId(readItem.getCustomerId());
        writeItem.setAmount(readItem.getAmount());
        return writeItem;
    }
}
ItemProcessorからnullを返却することの意味

ItemProcessorからnullを返却することは、当該データを後続処理(Writer)に渡さないことを意味し、 言い換えるとデータをフィルタすることになる。 これは、入力データの妥当性検証を実施する上で有効活用できる。 詳細については、入力チェックを参照のこと。

ItemProcessorの処理スループットをあげるには

前述した実装例のように、ItemProcessorの実装クラスではDBやファイルを始めとしたリソースにアクセスしなければならないことがある。 ItemProcessorは入力データ1件ごとに実行されるため、I/Oが少しでも発生するとジョブ全体では大量のI/Oが発生することになる。 そのため、極力I/Oを抑えることが処理スループットをあげる上で重要となる。

1つの方法として、後述のListenerを活用することで事前に必要なデータをメモリ上に確保しておき、 ItemProcessorにおける処理の大半を、CPU/メモリ間で完結するように実装する手段がある。 ただし、1ジョブあたりのメモリを大量に消費することにも繋がるので、何でもメモリ上に確保すればよいわけではない。 I/O回数やデータサイズを元に、メモリに格納するデータを検討すること。

この点については、データの入出力でも紹介する。

複数のItemProcessorを同時に利用する

汎用的なItemProcessorを用意し、個々のジョブに適用したい場合は、 Spring Batchが提供するCompositeItemProcessorを利用し連結することで実現できる。

CompositeItemProcessorによる複数ItemProcessorの連結
<bean id="processor"
      class="org.springframework.batch.item.support.CompositeItemProcessor">
    <property name="delegates">
        <list>
            <ref bean="commonItemProcessor"/>
            <ref bean="businessLogicItemProcessor"/>
        </list>
    </property>
</bean>

delegates属性に指定した順番に処理されることに留意すること。

3.3. タスクレットモデルジョブの作成

3.3.1. Overview

タスクレットモデルジョブの作成方法について説明する。 タスクレットモデルのアーキテクチャについては、Spring Batchのアーキテクチャを参照のこと。

3.3.1.1. 構成要素

タスクレットモデルジョブでは、複数の構成要素は登場しない。 org.springframework.batch.core.step.tasklet.Taskletを実装し、Bean定義で設定するのみである。 また、発展的な実装手段としてチャンクモデルの構成要素であるItemReaderItemWriterをコンポーネントとして使うことも可能である。

3.3.2. How to use

ここでは、実際にタスクレットモデルジョブを実装する方法について、以下の順序で説明する。

3.3.2.1. ジョブの設定

Bean定義ファイルにて、タスクレットモデルジョブを定義する。 以下に例を示す。

Bean定義ファイルの例(タスクレットモデル)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:batch="http://www.springframework.org/schema/batch"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
             http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd">

    <!-- (1) -->
    <import resource="classpath:META-INF/spring/job-base-context.xml"/>

    <!-- (2) -->
    <context:annotation-config/>

    <!-- (3) -->
    <context:component-scan
          base-package="org.terasoluna.batch.functionaltest.app.common"/>

    <!-- (4) -->
    <batch:job id="jobSimpleJob" job-repository="jobRepository"> <!-- (5) -->
        <batch:step id="simpleJob.step01"> <!-- (6) -->
            <batch:tasklet transaction-manager="jobTransactionManager"
                           ref="simpleJobTasklet"/> <!-- (7) -->
        </batch:step>
    </batch:job>

</beans>
Tasklet実装クラスの例
package org.terasoluna.batch.functionaltest.app.common;

@Component // (3)
public class SimpleJobTasklet implements Tasklet {
  // omitted
}
項番 説明

(1)

TERASOLUNA Batch 5.xを利用する際に、常に必要なBean定義を読み込む設定をインポートする。

(2)

アノテーションによるBean定義の有効化を行う。(3)と合わせて利用する。

(3)

コンポーネントスキャン対象のベースパッケージを設定する。(2)と合わせて利用する。
タスクレットモデルはアノテーションによるBean定義を基本とし、Tasklet実装クラスのBean定義はXML上では不要とする。

(4)

ジョブの設定。
id属性に設定する値は、1つのバッチアプリケーションに含まれる全ジョブの範囲において、一意とすること。

(5)

JobRepositoryの設定。
job-repository属性に設定する値は、特別な理由がない限りjobRepository固定とすること。
これにより、すべてのジョブが1つのJobRepositoryで管理できる。 jobRepositoryのBean定義は、(1)により解決する。

(6)

ステップの設定。
id属性に設定する値は、1ジョブ内で一意とすること。
接頭辞として(8)で設定したid属性を付加した形式にする。 こうすることでステップもジョブと同様にバッチアプリケーションに含まれる全ジョブの範囲において一意となり、 ログ出力や異常発生時の特定をはじめ、様々な場面で有効活用できる。
よって、特別な理由がない限り<ジョブのid>.<step名>とすること。

(7)

タスクレットの設定。
transaction-manager属性に設定する値は、特別な理由がない限りjobTransactionManager固定とすること。
これにより、タスクレット全体の処理が1つのトランザクションで管理される。 詳細については、トランザクション制御を参照のこと。
jobTransactionManagerのBean定義は、(1)により解決する。

また、ref属性に設定する値は、(3)により解決するBean名となる。
ここでは、Tasklet実装クラス名SimpleJobTaskletの先頭を小文字にしたsimpleJobTaskletとなる。

アノテーション利用時のBean名

@Componentアノテーション利用時のBean名は、デフォルトでは org.springframework.context.annotation.AnnotationBeanNameGenerator を通じて生成される。命名ルールを確認したいときは、本クラスのJavadocを参照するとよい。

3.3.2.2. Taskletの実装

まずはシンプルな実装で概要を理解し、次にチャンクモデルのコンポーネントを利用する実装へと進む。

以下の順序で説明する。

3.3.2.3. シンプルなTaskletの実装

ログを出力するのみのTasklet実装を通じ、最低限のポイントを説明する。

シンプルなTasklet実装クラスの例
package org.terasoluna.batch.functionaltest.app.common;

// omitted

@Component
public class SimpleJobTasklet implements Tasklet { // (1)

    private static final Logger logger =
            LoggerFactory.getLogger(SimpleJobTasklet.class);

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {  // (2)
        logger.info("called tasklet."); // (3)
        return RepeatStatus.FINISHED; // (4)
    }
}
項番 説明

(1)

org.springframework.batch.core.step.tasklet.Taskletインターフェースをimplementsして実装する。

(2)

Taskletインターフェースが定義するexecuteメソッドを実装する。 引数のStepContribution, ChunkContextは必要に応じて利用するが、ここでは説明を割愛する。

(3)

任意の処理を実装する。ここではINFOログを出力している。

(4)

Taskletの処理が完了したかどうかを返却する。
常にreturn RepeatStatus.FINISHED;と明示する。

3.3.2.4. チャンクモデルのコンポーネントを利用するTasklet実装

Spring Batch では、Tasklet実装の中でチャンクモデルの各種コンポーネントを利用することに言及していない。 TERASOLUNA Batch 5.xでは、以下のような状況に応じてこれを選択可能してよい。

  • 複数のリソースを組み合わせながら処理するため、チャンクモデルの形式に沿いにくい

  • チャンクモデルでは処理が複数箇所に実装することになるため、タスクレットモデルの方が全体像を把握しやすい

  • リカバリをシンプルにするため、チャンクモデルの中間コミットではなく、タスクレットモデルの一括コミットを使いたい

以下に、チャンクモデルのコンポーネントであるItemReaderItemWriterを利用するTasklet実装について説明する。

チャンクモデルのコンポーネントを利用するTasklet実装例1
@Component()
@Scope("step") // (1)
public class SalesPlanChunkTranTask implements Tasklet {

    @Inject
    @Named("detailCSVReader") // (2)
    ItemStreamReader<SalesPlanDetail> itemReader; // (3)

    @Inject
    SalesPlanDetailRepository repository; // (4)

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {

        SalesPlanDetail item;

        try {
            itemReader.open(chunkContext.getStepContext().getStepExecution()
                    .getExecutionContext()); // (5)

            while ((item = itemReader.read()) != null) { // (6)

                // do some processes.

                repository.create(item); // (7)
            }
        } finally {
            itemReader.close(); // (8)
        }
        return RepeatStatus.FINISHED;
    }
}
Bean定義例1
<!-- omitted -->
<import resource="classpath:META-INF/spring/job-base-context.xml"/>

<context:annotation-config/>

<context:component-scan
    base-package="org.terasoluna.batch.functionaltest.app.plan" />
<context:component-scan
    base-package="org.terasoluna.batch.functionaltest.ch05.transaction.component" />

<!-- (9) -->
<mybatis:scan
    base-package="org.terasoluna.batch.functionaltest.app.repository.plan"
    factory-ref="jobSqlSessionFactory"/>

<!-- (10) -->
<bean id="detailCSVReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}">
    <property name="lineMapper">
        <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer">
                <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
                      p:names="branchId,year,month,customerId,amount"/>
            </property>
            <property name="fieldSetMapper">
                <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
                      p:targetType="org.terasoluna.batch.functionaltest.app.model.plan.SalesPlanDetail"/>
            </property>
        </bean>
    </property>
</bean>

<!-- (11) -->
<batch:job id="createSalesPlanChunkTranTask" job-repository="jobRepository">
    <batch:step id="createSalesPlanChunkTranTask.step01">
        <batch:tasklet transaction-manager="jobTransactionManager"
                       ref="salesPlanChunkTranTask"/>
    </batch:step>
</batch:job>
項番 説明

(1)

本クラス内で利用するItemReaderのBeanスコープに合わせ、stepスコープとする。

(2)

入力リソース(この例ではフラットファイル)へのアクセスはItemReaderを通じて行う。
ここでは、detailCSVReaderというBean名を指定するが、わかりやすさのためなので任意とする。

(3)

ItemReaderのサブインターフェースである、ItemStreamReaderとして型を定義する。
これは、(5), (8)のリソースオープン/クローズを実装する必要があるためである。 後ほど補足する。

(4)

出力リソース(この例ではデータベース)へのアクセスはMyBatisのMapperを通じて行う。
ここでは、簡単のためMapperを直接利用している。常にItemWriterを用いる必要はない。 もちろん、MyBatisBatchItemWriterを用いてもよい。

(5)

入力リソースをオープンする。

(6)

入力リソース全件を逐次ループ処理する。
ItemReader#readは、入力データがすべて読み取り末端に到達した場合、nullを返却する。

(7)

データベースへ出力する。

(8)

リソースは必ずクローズすること。
なお、例外処理は必要に応じて実装すること。 ここで例外が発生した場合、タスクレット全体のトランザクションがロールバックされ、 例外のスタックトレースを出力し、ジョブが異常終了する。

(9)

データベースへ出力するため、mybatis:scanの設定を追加する。詳細はここでは割愛する。

(10)

ファイルから入力するため、FlatFileItemReaderのBean定義を追加する。詳細はここでは割愛する。

(11)

各種コンポーネントはアノテーションによって解決するため、
シンプルなTaskletの実装の場合と同様となる。

スコープの統一について

Tasklet実装クラスと、InjectするBeanのスコープは、同じスコープに統一すること。

たとえば、FlatFileItemReaderが引数から入力ファイルパスを受け取る場合にはBeanスコープをstepにする必要がある。 この時、Tasklet実装クラスのスコープもstepにする必要がある。

仮にTasklet実装クラスのスコープをsingletonとしたケースを説明する。 この時、アプリケーション起動時のApplicationContext生成時にTasklet実装クラスをインスタンス化した後、 FlatFileItemReaderのインスタンスを解決してInjectしようとする。 しかし、FlatFileItemReaderstepスコープでありstep実行時に生成するためまだ存在しない。 結果、Tasklet実装クラスをインスタンス化できないと判断しApplicationContext生成に失敗してしまう。

@Injectを付与するフィールドの型について

利用する実装クラスに応じて、以下のいずれかとする。

  • ItemReader/ItemWriter

    • 対象となるリソースへのオープン・クローズを実施する必要がない場合に利用する。

  • ItemSteamReader/ItemStreaWriter

    • 対象となるリソースへのオープン・クローズを実施する必要がある場合に利用する。

必ずjavadocを確認してどちらを利用するか判断すること。以下に代表例を示す。

FlatFileItemReader/Writerの場合

ItemSteamReader/ItemStreaWriterにて扱う

MyBatisCursorItemReaderの場合

ItemStreamReaderにて扱う

MyBatisBatchItemWriterの場合

ItemWriterにて扱う

もう1つの例として、ItemReaderItemWriterを同時に用いた場合を示す。

チャンクモデルのコンポーネントを利用するTasklet実装例2
@Component
@Scope("step")
public class SalesPerformanceTasklet implements Tasklet {


    @Inject
    ItemStreamReader<SalesPerformanceDetail> reader;

    @Inject
    ItemWriter<SalesPerformanceDetail> writer; // (1)

    int chunkSize = 10; // (2)

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {

        try {
            reader.open(chunkContext.getStepContext().getStepExecution()
                    .getExecutionContext());

            List<SalesPerformanceDetail> items = new ArrayList<>(chunkSize); // (2)
            SalesPerformanceDetail item = null;
            do {
                // Pseudo operation of ItemReader
                for (int i = 0; i < chunkSize; i++) { // (3)
                    item = reader.read();
                    if (item == null) {
                        break;
                    }
                    // Pseudo operation of ItemProcessor
                    // do some processes.

                    items.add(item);
                }

                // Pseudo operation of ItemWriter
                if (!items.isEmpty()) {
                    writer.write(items); // (4)
                    items.clear();
                }
            } while (item != null);
        } finally {
            try {
                reader.close();
            } catch (Exception e) {
                // do nothing.
            }
        }

        return RepeatStatus.FINISHED;
    }
}
Bean定義例2
<!-- omitted -->
<import resource="classpath:META-INF/spring/job-base-context.xml"/>

<context:annotation-config/>
<context:component-scan
    base-package="org.terasoluna.batch.functionaltest.app.common,
        org.terasoluna.batch.functionaltest.app.performance,
        org.terasoluna.batch.functionaltest.ch06.exceptionhandling"/>
<mybatis:scan
    base-package="org.terasoluna.batch.functionaltest.app.repository.performance"
    factory-ref="jobSqlSessionFactory"/>

<bean id="detailCSVReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}">
    <property name="lineMapper">
        <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer">
                <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
                      p:names="branchId,year,month,customerId,amount"/>
            </property>
            <property name="fieldSetMapper">
                <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
                      p:targetType="org.terasoluna.batch.functionaltest.app.model.performance.SalesPerformanceDetail"/>
            </property>
        </bean>
    </property>
</bean>

<!-- (1) -->
<bean id="detailWriter"
      class="org.mybatis.spring.batch.MyBatisBatchItemWriter"
      p:statementId="org.terasoluna.batch.functionaltest.app.repository.performance.SalesPerformanceDetailRepository.create"
      p:sqlSessionTemplate-ref="batchModeSqlSessionTemplate"/>


<batch:job id="jobSalesPerfTasklet" job-repository="jobRepository">
    <batch:step id="jobSalesPerfTasklet.step01">
        <batch:tasklet ref="salesPerformanceTasklet"
                       transaction-manager="jobTransactionManager"/>
    </batch:step>
</batch:job>
項番 説明

(1)

ItemWriterの実装としてMyBatisBatchItemWriterを利用する。

(2)

ItemWriterは一定件数をまとめて出力する。
ここでは10件ごとに処理し出力する。

(3)

チャンクモデルの動作にそって、
read→process→read→process→…​→writeとなるようにする。

(4)

ItemWriterを通じてまとめて出力する。

ItemReaderItemWriterの実装クラスを利用するかどうかは都度判断してほしいが、 ファイルアクセスはItemReaderItemWriterの実装クラスを利用するとよいだろう。 それ以外のデータベースアクセス等は無理に使う必要はない。性能向上のために使えばよい。

3.4. チャンクモデルとタスクレットモデルの使い分け

ここでは、チャンクモデルとタスクレットモデルの使い分けについて、それぞれの特徴を整理することで説明する。 なお、説明において、以降の章で詳細な説明をする事項もあるため、適宜対応する章を参照して欲しい。

また、以降の内容は考え方の一例として捉えて欲しい。制約や推奨事項ではない。 ユーザやシステムの特性に応じてジョブを作成する際の参考にしてほしい。

以下に、チャンクモデルとタスクレットモデルの主要な違いについて列挙する。

チャンクモデルとタスクレットモデルの比較
項目 チャンク タスクレット

構成要素

ItemReader, ItemProcessor, ItemWriterの3つに分割する。

Taskletの1つに集約する。

トランザクション

一定件数で中間コミットを発行しながら処理することが基本となる。一括コミットはできない。
処理対象データ件数に依らず一定のマシンリソースで処理できる。
処理途中でエラーが発生すると未処理データと処理済データが混在する。

全体で一括コミットにて処理することが基本となる。中間コミットはユーザにて実装する必要がある。
処理対象データが大量になると、マシンリソースが枯渇する恐れがある。
処理途中でエラーが発生すると未処理データのみにロールバックされる。

リスタート

件数ベースのリスタートができる。

件数ベースのリスタートはできない。

これを踏まえて、以下にそれぞれを使い分ける例をいくつか紹介する。

リカバリを限りなくシンプルにしたい

エラーとなったジョブは対象のジョブをリランするのみで復旧したい場合など、 リカバリをシンプルにしたい時はタスクレットモデルを選択するとよい。
チャンクモデルでは処理済データをジョブ実行前の状態に戻したり、 未処理データのみ処理するようジョブを予め作りこんでおいたり、 といった対処が必要となる。

処理の内容をまとめたい

1ジョブ1クラスなど、ジョブの見通しを優先したい場合はタスクレットを選択するとよい。

大量のデータを安定して処理したい

1000万件など、一括処理するとリソースに影響する件数を対象とする際はチャンクモデルを活用するか検討するとよい。 これは中間コミットによって安定させることを意味する。 タスクレットモデルでも中間コミットを打つことが可能だが、チャンクモデルの方がシンプルな実装になる可能性がある。

エラー後の復旧は件数ベースリスタートとしたい

バッチウィンドウがシビアであり、エラーとなったデータ以降から再開したい場合に、 Spring Batchが提供する件数ベースリスタートを活用するときは、チャンクモデルを選択する必要がある。 これにより、個々のジョブでその仕組を作りこむ必要がなくなる。

チャンクモデルとタスクレットモデルは、併用することが基本である。
バッチシステム内のジョブすべてをどちらかのモデルでのみ実装する必要はない。
システム全体のジョブがもつ特性を踏まえて、一方のモデルを基本とし、状況に応じてもう一方のモデルを使うことは自然である。

たとえば、大部分は処理件数や処理時間に余裕があるならばタスクレットモデルを基本とし、 極少数の大量件数を処理するジョブはチャンクモデルを選択する、といったことは自然といえる。

4. ジョブの起動

4.1. 同期実行

4.1.1. Overview

同期実行について説明する。 同期実行とは、ジョブスケジューラなどによりシェルを介して新規プロセスとして起動し、ジョブの実行結果を呼び出し元に返却する実行方法である。

overview of sync job
同期実行の概要
sequence of sync job
同期実行の流れ

本機能は、チャンクモデルとタスクレットモデルとで同じ使い方になる。

4.1.2. How to use

CommandLineJobRunnerによってジョブを起動する方法を説明する。

なお、アプリケーションのビルドや実行については、プロジェクトの作成を参照のこと。 また、起動パラメータの指定方法や活用方法については、ジョブの起動パラメータを参照のこと。 これらと本節の説明は一部重複するが、同期実行の要素に注目して説明する。

4.1.2.1. 実行方法

TERASOLUNA Batch 5.xにおいて、同期実行は Spring Batch が提供するCommandLineJobRunnerによって実現する。 CommandLineJobRunnerは、以下の要領にてjavaコマンドを発行することで起動する。

CommandLineJobRunnerの構文
$ java org.springframework.batch.core.launch.support.CommandLineJobRunner <jobPath> <options> <jobIdentifier> <jobParameters>
引数にて指定する項目
指定する項目 説明 必須

jobPath

起動するジョブの設定を記述したBean定義ファイルのパス。classpathからの相対パスにて指定する。

options

起動する際の各種オプション(停止、リスタートなど)を指定する。

jobIdentifier

ジョブの識別子として、Bean定義上のジョブ名、もしくはジョブを実行後のジョブ実行IDを指定する。 通常はジョブ名を指定する。ジョブ実行IDは停止やリスタートの際にのみ指定する。

jobParameters

ジョブの引数を指定する。指定はkey=value形式となる。

以下に、必須項目のみを指定した場合の実行例を示す。

CommandLineJobRunnerの実行例1
$ java -cp 'target/archetypeId-version.jar:lib/*' \ # (1)
    org.springframework.batch.core.launch.support.CommandLineJobRunner \ # (2)
    META-INF/jobs/job01.xml job01 # (3)
Bean定義の設定(抜粋)
<batch:job id="job01" job-repository="jobRepository"> <!-- (3) -->
    <batch:step id="job01.step01">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="employeeReader"
                         processor="employeeProcessor"
                         writer="employeeWriter" commit-interval="10" />
        </batch:tasklet>
    </batch:step>
</batch:job>
設定内容の項目一覧
項番 説明

(1)

javaコマンドを実行する際に、バッチアプリケーションのjarと、依存するjarをclasspathに指定する。 ここではコマンド引数で指定しているが、環境変数等を用いてもよい。

(2)

起動するクラスに、CommandLineJobRunnerをFQCNで指定する。

(3)

CommandLineJobRunnerに沿って、起動引数を渡す。 ここでは、jobPathjobIdentifierとしてジョブ名の2つを指定している。

次に、任意項目として起動パラメータを指定した場合の実行例を示す。

CommandLineJobRunnerの実行例2
$ java -cp 'target/archetypeId-version.jar:lib/*' \
    org.springframework.batch.core.launch.support.CommandLineJobRunner \
    META-INF/jobs/setupJob.xml setupJob target=server1 outputFile=/tmp/result.csv # (1)
設定内容の項目一覧
項番 説明

(1)

ジョブの起動パラメータとして、target=server1outputFile=/tmp/result.csvを指定している。

4.1.2.2. 任意オプション

CommandLineJobRunnerの構文で示した任意のオプションについて補足する。

CommandLineJobRunnerでは以下の4つの起動オプションが使用できる。 ここでは個々の説明は他に委ねることとし、概要のみ説明する。

-restart

失敗したジョブを再実行する。詳細は、処理の再実行を参照のこと。

-stop

実行中のジョブを停止する。詳細は、ジョブの管理を参照のこと。

-abandon

停止されたジョブを放棄する。放棄されたジョブは再実行不可となる。 TERASOLUNA Batch 5.xでは、このオプションを活用するシーンがないため、説明を割愛する。

-next

過去に一度実行完了したジョブを再度実行する。ただし、TERASOLUNA Batch 5.xでは、このオプションを利用しない。
なぜなら、TERASOLUNA Batch 5.xでは、Spring Batchのデフォルトである「同じパラメータで起動したジョブは同一ジョブとして認識され、同一ジョブは1度しか実行できない」 という制約を回避しているためである。
詳細はパラメータ変換クラスについてにて説明する。
また、本オプションを利用するには、JobParametersIncrementerというインターフェースの実装クラスが必要だが、 TERASOLUNA Batch 5.xでは設定を行っていない。
そのため、本オプションを指定して起動すると、必要なBean定義が存在しないためエラーとなる。

4.2. ジョブの起動パラメータ

4.2.1. Overview

本節では、ジョブの起動パラメータ(以降、パラメータ)の利用方法について説明する。

本機能は、チャンクモデルとタスクレットモデルとで同じ使い方になる。

パラメータは、以下のような実行環境や実行タイミングに応じてジョブの動作を柔軟に切替える際に使用する。

  • 処理対象のファイルパス

  • システムの運用日時

パラメータを与える方法は、以下のとおりである。

指定したパラメータは、Bean定義やSpring管理下のJavaで参照できる。

4.2.2. How to use

4.2.2.1. パラメータ変換クラスについて

Spring Batchでは、受け取ったパラメータを以下の流れで処理する。

  1. JobParametersConverterの実装クラスがJobParametersに変換する。

  2. Bean定義やSpring管理下のJavaにてJobParametersからパラメータを参照する。

パラメータ変換クラスの実装クラスについて

前述したJobParametersConverterの実装クラスは複数提供されている。 以下にそれぞれの特徴を示す。

  • DefaultJobParametersConverter

    • パラメータのデータ型を指定することができる(String、Long、Date、Doubleの4種類)。

  • JsrJobParametersConverter

    • パラメータのデータ型を指定することができない(Stringのみ)。

    • パラメータにジョブ実行を識別するID(RUN_ID)をjsr_batch_run_idという名称で自動的に付与する。

      • RUN_IDは、ジョブが実行される都度増分する。増分は、データベースのSEQUENCE(名称はJOB_SEQとなる)を利用するため、重複することがない。

      • Spring Batchでは、同じパラメータで起動したジョブは同一ジョブとして認識され、同一ジョブは1度しか実行できない、という仕様がある。 これに対し、jsr_batch_run_idという名称のパラメータを一意な値で付加することにより、別のジョブと認識する仕組みとなっている。 詳細は、Spring Batchのアーキテクチャを参照すること。

Spring BatchではBean定義で使用するJobParametersConverterの実装クラスを指定しない場合、DefaultJobParametersConverterが使用される。
しかし、TERASOLUNA Batch 5.xでは以下の理由によりDefaultJobParametersConverterは採用しない。

  • 1つのジョブを同じパラメータによって、異なるタイミングで起動することは一般的である。

  • 起動時刻のタイムスタンプなどを指定し、異なるジョブとして管理することも可能だが、それだけのためにジョブパラメータを指定するのは煩雑である。

  • DefaultJobParametersConverterはパラメータに対しデータ型を指定することができるが、型変換に失敗した場合のハンドリングが煩雑になる。

TERASOLUNA Batch 5.xでは、JsrJobParametersConverterを利用することで、ユーザが意識することなく自動的にRUN_IDを付与している。 この仕組みにより、ユーザから見ると同一ジョブをSpring Batchとしては異なるジョブとして扱っている。

パラメータ変換クラスの設定について

TERASOLUNA Batch 5.xでは、予めlaunch-context.xmlにてJsrJobParametersConverterを使用するように設定している。
そのためTERASOLUNA Batch 5.xを推奨設定で使用する場合はJobParametersConverterの設定を行う必要はない。

META-INF\spring\launch-context.xml
<bean id="jobParametersConverter"
      class="org.springframework.batch.core.jsr.JsrJobParametersConverter"
      c:dataSource-ref="adminDataSource" />

<bean id="jobOperator"
      class="org.springframework.batch.core.launch.support.SimpleJobOperator"
      p:jobRepository-ref="jobRepository"
      p:jobRegistry-ref="jobRegistry"
      p:jobExplorer-ref="jobExplorer"
      p:jobParametersConverter-ref="jobParametersConverter"
      p:jobLauncher-ref="jobLauncher" />

以降はJsrJobParametersConverterを利用する前提で説明する。

4.2.2.2. コマンドライン引数から与える

まず、もっとも基本的な、コマンドライン引数から与える方法について説明する。

パラメータの付与

コマンドライン引数としてCommandLineJobRunnerの第3引数以降に<パラメータ名>=<値>形式で列挙する。

パラメータの個数や長さは、Spring BatchやTERASOLUNA Batch 5.xにおいては制限がない。 しかし、OSにはコマンド引数の長さに制限がある。
そのため、あまりに大量の引数が必要な場合は、ファイルから標準入力へリダイレクトするパラメータとプロパティの併用などの方法を活用すること。

コマンドライン引数としてパラメータを設定する例
# Execute job
$ java org.springframework.batch.core.launch.support.CommandLineJobRunner \
    JobDefined.xml JOBID param1=abc outputFileName=/tmp/result.csv
パラメータの参照

以下のように、Bean定義またはJavaで参照することができる。

  • Bean定義で参照する

    • #{jobParamaters[xxx]}で参照可能

  • Javaで参照する

    • @Value("#{jobParameters[xxx]}")で参照可能

JobParametersを参照するBeanのスコープはStepスコープでなければならない

JobParametersを参照する際は、参照するBeanのスコープをStepスコープとする必要がある。 これは、JobParametersを参照する際に、Spring Batchのlate bindingという仕組みを使用しているためである。

late bindingとはその名のとおり、遅延して値を設定することである。 Spring FrameworkのApplicationContextは、デフォルトでは各種Beanのプロパティを解決してからApplicationContextのインスタンスを生成する。 Spring BatchではApplicationContextのインスタンスを生成する時にはプロパティを解決せず、 各種Beanが必要になった際にプロパティを解決する機能をもつ。これが遅延という言葉が意味することである。 この機能により、Spring Batch自体の実行に必要なApplicationContextを生成し実行した後に、 パラメータに応じて各種Beanの振る舞いを切替えることが可能となる。

なお、StepスコープはSpring Batch独自のスコープであり、Stepの実行ごとに新たなインスタンスが生成される。 また、late bindingによる値の解決は、Bean定義においてSpEL式を用いることで可能となる。

Stepスコープの指定では@StepScopeアノテーションは使用できない

Spring Batchでは、Stepスコープを指定するアノテーションとして@StepScopeが提供されているが、 これはJavaConfigにおいてのみ使用できるアノテーションである。

そのため、TERASOLUNA Batch 5.xにおけるStepスコープの指定は以下のいずれかの方法で行う。

  1. Bean定義では、Beanにscope="step"を付与する。

  2. Javaでは、クラスに@Scope("step")を付与する。

コマンドライン引数で与えたパラメータをBean定義で参照する例
<!-- (1) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}">  <!-- (2) -->
    <property name="lineMapper">
        <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 説明

(1)

beanタグにscope属性としてスコープを指定する。

(2)

参照するパラメータを指定する。

コマンドライン引数で与えたパラメータをJavaで参照する例
@Component
@Scope("step")  // (1)
public class ParamRefInJavaTasklet implements Tasklet {

    /**
     * Holds a String type value
     */
    @Value("#{jobParameters[str]}")  // (2)
    private String str;

    // omitted execute()
}
設定内容の項目一覧
項番 説明

(1)

クラスに@Scopeアノテーションを付与してスコープを指定する。

(2)

@Valueアノテーションを使用して参照するパラメータを指定する。

4.2.2.3. ファイルから標準入力へリダイレクトする

ファイルから標準入力へリダイレクトする方法について説明する。

パラメータを定義するファイルの作成

パラメータは下記のようにファイルに定義する。

params.txt
param1=abc
outputFile=/tmp/result.csv
パラメータを定義したファイルを標準入力へリダイレクトする

コマンドライン引数としてパラメータを定義したファイルをリダイレクトする。

実行方法
# Execute job
$ java org.springframework.batch.core.launch.support.CommandLineJobRunner \
    JobDefined.xml JOBID < params.txt
パラメータの参照

パラメータの参照方法はコマンドライン引数から与える方法と同様である。

4.2.2.4. パラメータのデフォルト値を設定する

パラメータを任意とした場合、以下の形式でデフォルト値を設定することができる。

  • #{jobParameters[パラメータ名] ?: デフォルト値}

ただし、パラメータを使用して値を設定している項目であるということは、デフォルト値もパラメータと同様に環境や実行タイミングによって異なる可能性がある。

まずは、デフォルト値をソースコード上にハードコードをする方法を説明する。 しかし、後述のパラメータとプロパティの併用を活用する方が適切なケースが多いため、合わせて参照すること。

デフォルト値を設定したパラメータの参照

該当するパラメータが設定されなかった場合にデフォルト値に設定した値が参照される。

コマンドライン引数で与えたパラメータをBean定義で参照する例
<!-- (1) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParamaters[inputFile] ?: /input/sample.csv}">  <!-- (2) -->
    <property name="lineMapper">
        // omitted settings
    </property>
</bean>
設定内容の項目一覧
項番 説明

(1)

beanタグにscope属性としてスコープを指定する。

(2)

参照するパラメータを指定する。
デフォルト値に/output/result.csvを設定している。

コマンドライン引数で与えたパラメータをJavaで参照する例
@Component
@Scope("step")  // (1)
public class ParamRefInJavaTasklet implements Tasklet {

    /**
     * Holds a String type value
     */
    @Value("#{jobParameters[str] ?: xyz}")  // (2)
    private String str;

    // omitted execute()
}
設定内容の項目一覧
項番 説明

(1)

クラスに@Scopeアノテーションを付与してスコープを指定する。

(2)

@Valueアノテーションを使用して参照するパラメータを指定する。
デフォルト値にxyzを設定している。

4.2.2.5. パラメータの妥当性検証

オペレーションミスや意図しない挙動を防ぐために、ジョブの起動時にパラメータの妥当性検証が必要となる場合もある。
パラメータの妥当性検証はSpring Batchが提供するJobParametersValidatorを活用することで実現可能である。

パラメータはItemReader/ItemProcessor/ItemWriterといった様々な場所で参照するため、 ジョブの起動直後に妥当性検証が行われる。

パラメータの妥当性を検証する方法は2つあり、検証の複雑度によって異なる。

  • 簡易な妥当性検証

    • 適用例

      • 必須パラメータが設定されていることの検証

      • 意図しないパラメータが設定されていないことの検証

    • 使用するバリデータ

      • Spring Batchが提供しているDefaultJobParametersValidator

  • 複雑な妥当性検証

    • 適用例

      • 数値の範囲検証やパラメータ間の相関チェックなどの複雑な検証

      • Spring Batchが提供しているDefaultJobParametersValidatorにて実現不可能な検証

    • 使用するバリデータ

      • JobParametersValidatorを自作で実装したクラス

簡易な妥当性検証および複雑な妥当性検証の妥当性を検証する方法についてそれぞれ説明する。

4.2.2.5.1. 簡易な妥当性検証

Spring BatchはJobParametersValidatorのデフォルト実装として、DefaultJobParametersValidatorを提供している。
このバリデータでは設定により以下を検証することができる。

  • 必須パラメータが設定されていること

  • 必須または任意パラメータ以外のパラメータが指定されていないこと

以下に定義例を示す。

DefaultJobParametersValidatorを使用する妥当性検証の定義
<!-- (1) -->
<bean id="jobParametersValidator"
      class="org.springframework.batch.core.job.DefaultJobParametersValidator">
  <property name="requiredKeys">  <!-- (2) -->
    <list>
        <value>jsr_batch_run_id</value>  <!-- (3) -->
        <value>inputFileName</value>
        <value>outputFileName</value>
    </list>
  </property>
  <property name="optionalKeys">  <!-- (4) -->
    <list>
        <value>param1</value>
        <value>param2</value>
    </list>
  </property>
</bean>

<batch:job id="jobUseDefaultJobParametersValidator" job-repository="jobRepository">
  <batch:step id="jobUseDefaultJobParametersValidator.step01">
    <batch:tasklet ref="sampleTasklet" transaction-manager="jobTransactionManager"/>
  </batch:step>
  <batch:validator ref="jobParametersValidator"/>  <!-- (5) -->
</batch:job>
設定内容の項目一覧
項番 説明

(1)

DefaultJobParametersValidatorのBeanを定義する。

(2)

必須パラメータはプロパティrequiredKeysに設定する。
listタグを使用して必須パラメータのパラメータ名を複数指定できる。

(3)

必須パラメータにjsr_batch_run_idを設定する。
TERASOLUNA Batch 5.xでは、DefaultJobParametersValidatorを使用する場合はこの設定が必須である。
必須となる理由は後述する。

(4)

任意パラメータはプロパティoptionalKeysに設定する。
listタグを使用して任意パラメータのパラメータ名を複数指定できる。

(5)

jobタグ内にvalidatorタグを使用してジョブにバリデータを適用する。

TERASOLUNA Batch 5.xでは省略できない必須パラメータ

TERASOLUNA Batch 5.xではパラメータ変換にJsrJobParametersConverterを採用しているため、以下のパラメータが常に設定される。

  • jsr_batch_run_id

そのため、requiredKeysには、jsr_batch_run_idを必ず含めること。
詳細な説明は、パラメータ変換クラスについてを参照すること。

パラメータの定義例
<bean id="jobParametersValidator"
      class="org.springframework.batch.core.job.DefaultJobParametersValidator">
  <property name="requiredKeys">
    <list>
        <value>jsr_batch_run_id</value>  <!-- mandatory -->
        <value>inputFileName</value>
        <value>outputFileName</value>
    </list>
  </property>
  <property name="optionalKeys">
    <list>
        <value>param1</value>
        <value>param2</value>
    </list>
  </property>
</bean>
DefaultJobParametersValidatorを使用した場合のOKケースとNGケース

DefaultJobParametersValidatorにて検証可能な条件の理解を深めるため、検証結果がOKとなる場合とNGとなる場合の例を示す。

DefaultJobParametersValidator定義例
<bean id="jobParametersValidator"
    class="org.springframework.batch.core.job.DefaultJobParametersValidator"
    p:requiredKeys="outputFileName"
    p:optionalKeys="param1"/>
NGケース1
# Execute job
$ java org.springframework.batch.core.launch.support.CommandLineJobRunner \
    JobDefined.xml JOBID param1=aaa

必須パラメータoutputFileが設定されていないためNGとなる。

NGケース2
# Execute job
$ java org.springframework.batch.core.launch.support.CommandLineJobRunner \
    JobDefined.xml JOBID outputFileName=/tmp/result.csv param2=aaa

必須パラメータ、任意パラメータのどちらにも指定されていないパラメータparam2が設定されたためNGとなる。

OKケース1
# Execute job
$ java org.springframework.batch.core.launch.support.CommandLineJobRunner \
    JobDefined.xml JOBID param1=aaa outputFileName=/tmp/result.csv

必須および任意として指定されたパラメータが設定されているためOKとなる。

OKケース2
# Execute job
$ java org.springframework.batch.core.launch.support.CommandLineJobRunner \
    JobDefined.xml JOBID fileoutputFilename=/tmp/result.csv

必須パラメータが設定されているためOKとなる、任意パラメータは設定されていなくてもよい。

4.2.2.5.2. 複雑な妥当性検証

JobParametersValidatorインターフェースの実装を自作することで、 要件に応じたパラメータの検証を実現することができる。

JobParametersValidatorクラスは以下の要領で実装する。

  • JobParametersValidatorクラスを実装し、validateメソッドをオーバーライドする

  • validateメソッドは以下の要領で実装する

    • JobParametersから各パラメータを取得し検証する

      • 検証の結果がOKである場合には、何もする必要はない

      • 検証の結果がNGである場合には、JobParametersInvalidExceptionをスローする

JobParametersValidatorクラスの実装例を示す。 ここでは、strで指定された文字列の長さが、numで指定された数値以下であることを検証している。

JobParametersValidatorインターフェースの実装例
public class ComplexJobParametersValidator implements JobParametersValidator {  // (1)
    @Override
    public void validate(JobParameters parameters) throws JobParametersInvalidException {
        Map<String, JobParameter> params = parameters.getParameters();  // (2)

        String str = params.get("str").getValue().toString();  // (3)
        int num = Integer.parseInt(params.get("num").getValue().toString()); // (4)

        if(str.length() > num){
            throw new JobParametersInvalidException(
            "The str must be less than or equal to num. [str:"
                    + str + "][num:" + num + "]");  // (5)
        }
    }
}
設定内容の項目一覧
項番 説明

(1)

JobParametersValidatorクラスを実装しvalidateメソッドをオーバーライドする。

(2)

パラメータはJobParameters型で引数として受ける。
parameters.getParameters()とすることで、Map形式で取得することでパラメータの参照が容易になる。

(3)

keyを指定してパラメータを取得する。

(4)

パラメータをint型へ変換する。String型以外を扱う場合は適宜変換を行うこと。

(5)

パラメータstrの文字列長がパラメータnumの値を超えている場合に妥当性検証結果NGとしている。

ジョブの定義例
<batch:job id="jobUseComplexJobParametersValidator" job-repository="jobRepository">
    <batch:step id="jobUseComplexJobParametersValidator.step01">
        <batch:tasklet ref="sampleTasklet" transaction-manager="jobTransactionManager"/>
    </batch:step>
    <batch:validator>  <!-- (1) -->
        <bean class="org.terasoluna.batch.functionaltest.ch04.jobparameter.ComplexJobParametersValidator"/>
    </batch:validator>
</batch:job>
設定内容の項目一覧
項番 説明

(1)

jobタグ内にvalidatorタグを使用してジョブにバリデータを適用する。

非同期起動時におけるパラメータの妥当性検証について

非同期起動方式(DBポーリングやWebコンテナ)でも、同様にジョブ起動時に検証することは可能だが、 以下のようなタイミングでジョブを起動する前に検証することが望ましい。

  • DBポーリング

    • ジョブ要求テーブルへのINSERT前

  • Webコンテナ

    • Controller呼び出し時(@Validatedを付与する)

非同期起動の場合、結果は別途確認する必要が生じるため、パラメータ設定ミスのような 場合は早期にエラーを応答し、ジョブの要求をリジェクトすることが望ましい。

また、この時の妥当性検証において、JobParametersValidatorを使う必要はない。 ジョブ要求テーブルへINSERTする機能や、Webコンテナ上のControllerは 多くの場合Spring Batchに依存していないはずであり、 JobParametersValidatorを使用するためだけにSpring Batchに依存することは避けた方がよい。

4.2.3. How to extends

4.2.3.1. パラメータとプロパティの併用

Spring BatchのベースであるSpring Frameworkには、プロパティ管理の機能が備わっており、 環境変数やプロパティファイルに設定した値を扱うことができる。 詳細は、TERASOLUNA Server 5.x 開発ガイドラインの プロパティ管理 を参照すること。

プロパティとパラメータを組み合わせることで、大部分のジョブに共通的な設定をプロパティファイルに行ったうえで、一部をパラメータで上書きするといったことが可能になる。

パラメータとプロパティが解決されるタイミングについて

前述のとおり、パラメータとプロパティは、機能を提供するコンポーネントが異なる。
Spring Batchはパラメータ管理の機能をもち、Spring Frameworkはプロパティ管理の機能をもつ。
この差は記述方法の差に現れている。

  • Spring Batchがもつ機能の場合

    • #{jobParamaters[xxx]}

  • Spring Frameworkがもつ機能の場合

    • @Value("${xxx}")

また、それぞれの値が解決されるタイミングが異なる。

  • Spring Batchがもつ機能の場合

    • Application Contextを生成後、ジョブを実行するタイミングで設定される。

  • Spring Frameworkがもつ機能の場合

    • Application Contextの生成時に設定される。

よって、Spring Batchによるパラメータの値が優先される結果になる。
この点を念頭におくと、組み合わせる際に応用が効くため両者を区別して扱うこと。

以降、プロパティとパラメータを組み合わせて設定する方法について説明する。

環境変数による設定に加えて、コマンドライン引数で追加設定する場合

環境変数による設定に加えて、コマンドライン引数を使用してパラメータを設定する方法を説明する。
Bean定義においても同様に参照可能である。

環境変数に加えてコマンドライン引数でパラメータを設定する例
# Set environment variables
$ export env1=aaa
$ export env2=bbb

# Execute job
$ java org.springframework.batch.core.launch.support.CommandLineJobRunner \
    JobDefined.xml JOBID param3=ccc outputFile=/tmp/result.csv
Javaにおいて環境変数とパラメータを参照する例
@Value("${env1}")  // (1)
private String param1;

@Value("${env2}")  // (1)
private String param2;

private String param3;

@Value("#{jobParameters[param3]")  // (2)
public void setParam3(String param3) {
    this.param3 = param3;
}
設定内容の項目一覧
項番 説明

(1)

@Valueアノテーションを使用して参照する環境変数を指定する。
参照する際の形式は${環境変数名}である。

(2)

@Valueアノテーションを使用して参照するパラメータを指定する。
参照する際の形式は#{jobParameters[パラメータ名]である。

環境変数をデフォルトとする場合の例
# Set environment variables
$ export env1=aaa

# Execute job
$ java org.springframework.batch.core.launch.support.CommandLineJobRunner \
    JobDefined.xml JOBID param1=bbb outputFile=/tmp/result.csv
Javaにおいて環境変数をデフォルト値としてパラメータを参照する例
@Value("#{jobParameters[param1] ?: '${env1}'}")  // (1)
public void setParam1(String param1) {
    this.param1 = param1;
}
設定内容の項目一覧
項番 説明

(1)

環境変数をデフォルト値として@Valueアノテーションを使用して参照するパラメータを指定する。
パラメータが設定されなかった場合、環境変数の値が設定される。

誤ったデフォルト値の設定方法

以下の要領で定義した場合、コマンドライン引数からparam1を設定しない場合に、 env1の値が設定されてほしいにも関わらず、param1にnullが設定されてしまうため注意すること。

誤ったデフォルト値の設定方法例
@Value("${env1}")
private String param1;

@Value("#{jobParameters[param1]}")
public void setParam1(String param1) {
  this.param1 = param1;
}

4.3. 非同期実行(DBポーリング)

4.3.1. Overview

DBポーリングによるジョブ起動について説明をする。

本機能は、チャンクモデルとタスクレットモデルとで同じ使い方になる。

4.3.1.1. DBポーリングによるジョブの非同期実行とは

非同期実行させたいジョブを登録する専用のテーブル(以降、ジョブ要求テーブル)を一定周期で監視し、登録された情報を元にジョブを非同期実行することをいう。
TERASOLUNA Batch 5.xでは、テーブルを監視しジョブを起動するモジュールを非同期バッチデーモンという名称で定義する。 非同期バッチデーモンは1つのJavaプロセスとして稼働し、1ジョブごとにプロセス内のスレッドを割り当てて実行する。

4.3.1.1.1. TERASOLUNA Batch 5.xが提供する機能

TERASOLUNA Batch 5.xは、以下の機能を非同期実行(DBポーリング)として提供する。

非同期実行(DBポーリング)の機能一覧
機能 説明

非同期バッチデーモン機能

ジョブ要求テーブルポーリング機能を常駐実行させる機能

ジョブ要求テーブルポーリング機能

ジョブ要求テーブルに登録された情報にもとづいてジョブを非同期実行する機能。
ジョブ要求テーブルのテーブル定義も合わせて提供する。

利用前提

ジョブ要求テーブルでは、ジョブ要求のみを管理する。要求されたジョブの実行状況および結果は、JobRepositoryに委ねる。 これら2つを通じてジョブのステータスを管理することを前提としている。

また、JobRepositoryにインメモリデータベースを使用すると、非同期バッチデーモン停止後にJobRepositoryがクリアされ、ジョブの実行状況および結果を参照できない。 そのため、JobRepositoryには永続性が担保されているデータベースを使用することを前提とする。

インメモリデータベースの使用

JobRepositoryを参照せずにジョブ実行結果の成否を得る手段がある場合、インメモリデータベースで運用するケースも考えられる。
インメモリデータベースで長期連続運用をする場合、メモリリソースを大量消費してジョブ実行に悪影響を及ぼす可能性がある。
つまり、インメモリデータベースは、長期連続運用するには向かず、定期的に再起動する運用が望ましい。
それでも長期連続運用で利用したい場合は、定期的にJobRepositoryからデータを削除するなどのメンテナンス作業が必須である。
再起動する場合は、初期化を有効にしておけば再起動時に再作成されるため、メンテナンスは不要である。 初期化については、データベース関連の設定参照。

4.3.1.1.2. 活用シーン

非同期実行(DBポーリング)を活用するシーンを以下にいくつか示す。

活用シーン一覧
活用シーン 説明

ディレード処理

オンライン処理と連携して、即時に完了する必要がなく、かつ、時間がかかる処理をジョブとして切り出したい場合。

処理時間が短いジョブの連続実行

1ジョブあたり数秒~数十秒の処理を連続実行する場合。
非同期実行(DBポーリング)を活用することで、1ジョブごとにJavaプロセスの起動・終了によるリソースの圧迫を回避できる。 また、起動・終了処理を割愛することに繋がるためジョブの実行時間を短縮することが可能となる。

大量にあるジョブの集約

処理時間が短いジョブの連続実行と同様である。

非同期実行(Webコンテナ)と使い分けるポイント

非同期実行(Webコンテナ)と使い分けるポイントを以下に示す。

  • バッチ処理にWebAPサーバを導入することにハードルがある

  • 可用性を担保する際に、データベースのみを考慮すればよい

    • その代わり、データベースにアクセスが集中するため、非同期実行(Webコンテナ)ほどスケールしない可能性がある

Spring Batch Integerationを採用しない理由

Spring Batch Integerationを利用して同様の機能を実現することは可能である。
しかし、Spring Batch Integerationを使用すると非同期実行以外の要素も含めた技術要素の理解・取得が必要となる。
それにより、本機能の理解/活用/カスタマイズが難しくなるのを避けるため、Spring Batch Integerationの適用は見送っている。

非同期実行(DBポーリング)での注意点

1ジョブあたり数秒にも満たない超ショートバッチを大量に実行する場合、JobRepositoryも含めてデータベースへのアクセスが都度発生する。 この点に起因する性能劣化もあり得るため、超ショートバッチの大量処理は、非同期実行(DBポーリング)には向いていない。 本機能を利用する際はこの点を踏まえ、目標性能を満たせるか十分に検証をすること。

4.3.2. Architecture

4.3.2.1. DBポーリングの処理シーケンス

DBポーリングの処理シーケンスについて説明する。

sequence of DB polling
DBポーリングの処理シーケンス図
  1. AsyncBatchDeamonをshなどから起動する。

  2. AsyncBatchDeamonは、起動時にジョブを定義したBean定義ファイルをすべて読み込む。

  3. AsyncBatchDeamonは、一定間隔でポーリングするためにTaskSchedulerを起動する。

    • TaskSchedulerは、一定間隔で特定の処理を起動する。

  4. TaskSchedulerは、JobRequestPollTask(ジョブ要求テーブルをポーリングする処理)を起動する。

  5. JobRequestPollTaskは、ジョブ要求テーブルからポーリングステータスが未実行(INIT)のレコードを取得する。

    • 一定件数をまとめて取得する。デフォルトは3件。

    • 対象のレコードが存在しない場合は、一定間隔を空けて再度ポーリングを行う。デフォルトは5秒間隔。

  6. JobRequestPollTaskは、レコードの情報にもとづいて、ジョブをスレッドに割り当てて実行する。

  7. JobRequestPollTaskは、ジョブ要求テーブルのポーリングステータスをポーリング済み(POLLED)へ更新する。

    • ジョブの同時実行数に達している場合は、取得したレコードから起動できないレコードを破棄し、次回ポーリング処理時にレコードを再取得する。

  8. スレッドに割り当てられたジョブは、JobOperatorによりジョブを開始する。

  9. 実行したジョブのジョブ実行ID(Job execution id)を取得する。

  10. JobRequestPollTaskは、ジョブ実行時に取得したジョブ実行IDにもとづいて、ジョブ要求テーブルのポーリングステータスをジョブ実行済み(EXECUTED)に更新する。

処理シーケンスの補足

Spring Batchのリファレンスでは、JobLauncherAsyncTaskExecutorを設定することで非同期実行が実現できることを示している。 しかし、この方法を採用するとAsyncTaskExecutorがジョブ実行が出来ない状態を検知できない。 これは、ジョブに割り当てられるスレッドがない時などに発生し、その結果以下の事象に繋がる可能性がある。

  • ジョブが実行できないにも関わらず、ジョブの起動をしようとし続け不要な処理をしてしまう

  • スレッドが空いたタイミングによっては、ポーリングした順番にジョブが起動せず、ジョブ要求テーブル上ランダムに起動するように見えてしまう

この事象を回避するため前述の処理シーケンスとなっている。

4.3.2.2. ポーリングするテーブルについて

非同期実行(DBポーリング)でポーリングを行うテーブルについて説明する。

以下データベースオブジェクトを必要とする。

  • ジョブ要求テーブル(必須)

  • ジョブシーケンス(データベース製品によっては必須)

    • データベースがカラムの自動採番に対応していない場合に必要となる。

4.3.2.2.1. ジョブ要求テーブルの構造

以下に、TERASOLUNA Batch 5.xが対応しているデータベース製品のうち、PostgreSQLの場合を示す。 その他のデータベースについては、TERASOLUNA Batch 5.xのjarに同梱されているDDLを参照してほしい。

batch_job_request (PostgreSQLの場合)
カラム名 データ型 制約 説明

job_seq_id

bigserial

(別途シーケンスを定義する場合は、bigintとする)

NOT NULL
PRIMARY KEY

ポーリング時に実行するジョブの順序を決める番号。
データベースの自動採番機能を利用。

job_name

varchar(100)

NOT NULL

実行するジョブ名。
ジョブ実行時の必須パラメータ。

job_parameter

varchar(2000)

-

実行するジョブに渡すパラメータ。

単一パラメータの書式は同期実行と同じだが、複数パラメータを指定する場合は、同期型実行の空白区切りとは異なり、 各パラメータをカンマ区切り(下記参照)にする必要がある。

{パラメータ名}={パラメータ値},{パラメータ名}={パラメータ値}…​

job_execution_id

bigint

-

ジョブ実行時に払い出されるID。
このIDをキーにしてJobRepositoryを参照する。

polling_status

varchar(10)

NOT NULL

ポーリング処理状況。
INIT : 未実行
POLLED: ポーリング済み
EXECUTED : ジョブ実行済み

create_date

TIMESTAMP

NOT NULL

ジョブ要求のレコードを登録した日時。

update_date

TIMESTAMP

-

ジョブ要求のレコードを更新した日時。

DDLは以下のとおり。

CREATE TABLE IF NOT EXISTS batch_job_request (
    job_seq_id bigserial PRIMARY KEY,
    job_name varchar(100) NOT NULL,
    job_parameter varchar(200),
    job_execution_id bigint,
    polling_status varchar(10) NOT NULL,
    create_date timestamp NOT NULL,
    update_date timestamp
);
4.3.2.2.2. ジョブ要求シーケンスの構造

データベースがカラムの自動採番に対応していない場合は、シーケンスによる採番が必要になる。
以下に、TERASOLUNA Batch 5.xが対応しているデータベース製品のうち、PostgreSQLの場合を示す。
その他のデータベースについては、TERASOLUNA Batch 5.xのjarに同梱されているDDLを参照してほしい。

DDLは以下のとおり。

CREATE SEQUENCE batch_job_request_seq MAXVALUE 9223372036854775807 NO CYCLE;

PostgreSQLはカラムの自動採番に対応しているため、TERASOLUNA Batch 5.xのjarに同梱されているDDLにジョブ要求シーケンスは定義されていない。 シーケンスの最大値を変更したい場合などに、job_seq_idのデータ型をbigserialからbigintに変更した上で、 ジョブ要求シーケンスを定義すると良い。

4.3.2.2.3. ポーリングステータス(polling_status)の遷移パターン

ポーリングステータスの遷移パターンを下表に示す。

ポーリングステータスの遷移パターン一覧
遷移元 遷移先 説明

INIT

INIT

同時実行数に達して、ジョブの実行を拒否された場合はステータスの変更はない。
次回ポーリング時にポーリング対象のレコードとなる。

INIT

POLLED

ジョブの起動に成功した時に遷移する。
ジョブを実行している時のステータス。

POLLED

EXECUTED

ジョブの実行が終了した時に遷移する。

4.3.2.3. ジョブの起動について

ジョブの起動方法について説明をする。

TERASOLUNA Batch 5.xのジョブ要求テーブルポーリング機能内部では、 Spring Batchから提供されているJobOperatorstartメソッドでジョブを起動する。

TERASOLUNA Batch 5.xでは、非同期実行(DBポーリング)で起動したジョブのリスタートは、 コマンドラインからの実行をガイドしている。 そのため、JobOperatorにはstart以外にもrestartなどの起動メソッドがあるが、 satrtメソッド以外は使用していない。

startメソッドの引数
jobName

ジョブ要求テーブルのjob_nameに登録した値を設定する。

jobParametrers

ジョブ要求テーブルのjob_parametersに登録した値を設定する。

4.3.2.4. DBポーリング処理で異常が発生した場合について

DBポーリング処理で異常が発生した場合について説明する。

4.3.2.4.1. データベース接続障害

障害が発生した時点で行われていた処理別に振る舞いを説明する。

ジョブ要求テーブルからのレコード取得時
  • JobRequestPollTaskはエラーとなるが、次回のポーリングにてJobRequestPollTaskが再実行される。

ポーリングステータスをINITからPOLLEDに変更する間
  • JobOperatorによるジョブ実行前にJobRequestPollTaskはエラー終了する。ポーリングステータスは、INITのままになる。

  • 接続障害回復後に行われるポーリング処理では、ジョブ要求テーブルに変更がないため実行対象となり、次回ポーリング時にジョブが実行される。

ポーリングステータスをPOLLEDからEXECUTEDに変更する間
  • JobRequestPollTaskは、ジョブ実行IDをジョブ要求テーブルに更新することができずにエラー終了する。ポーリングステータスは、POLLEDのままになる。

  • 接続障害回復後に行われるポーリング処理の対象外となり、障害時のジョブは実行されない。

  • ジョブ要求テーブルからジョブ実行IDを知ることができないため、ジョブの最終状態をログやJobRepositoryから判断し、必要に応じてジョブの再実行など回復処理を行う。

JobRequestPollTaskで例外が発生しても、即座に自動復旧しようとはしない。以下に理由を示す。

  1. JobRequestPollTaskは、一定間隔で起動するため、これに委ねることで(即座ではないが)自動復旧できる。

  2. 障害発生時に即座にリトライしても回復できるケースは稀であり、かえってリトライにより負荷を発生してしまう可能性がある。

4.3.2.4.2. 非同期バッチデーモンのプロセス異常終了

非同期バッチデーモンのプロセスが異常終了した場合は、実行中ジョブのトランザクションは暗黙的にロールバックされる。
ポーリングステータスによる状態はデータベース接続障害と同じになる。

4.3.2.5. DBポーリング処理の停止について

非同期バッチデーモン(AsyncBatchDeamon)は、ファイルの生成によって停止する。 ファイルが生成されたことを確認後、ポーリング処理を空振りさせ、起動中ジョブの終了を可能な限り待ってから停止する。

4.3.2.6. 非同期実行特有のアプリケーション構成となる点について

非同期実行における特有の構成を説明する。

4.3.2.6.1. ApplicationContextの構成

非同期バッチデーモンは、非同期実行専用のasync-batch-daemon.xmlをApplicationContextとして読み込む。 同期実行でも使用しているlaunch-context.xmlの他に次の構成を追加している。

非同期実行設定

JobRequestPollTaskなどの非同期実行に必要なBeanを定義している。

ジョブ登録設定

非同期実行として実行するジョブは、org.springframework.batch.core.configuration.support.AutomaticJobRegistrarで登録を行う。 AutomaticJobRegistrarを用いることで、ジョブ単位にコンテキストのモジュール化を行っている。 モジュール化することにより、ジョブ間で利用するBeanIDが重複していても問題にならないようにしている。

モジュール化とは

モジュール化とは、「共通定義-各ジョブ定義」の階層構造になっており、各ジョブで定義されたBeanは、ジョブ間で独立したコンテキストに属することである。 各ジョブ定義で定義されていないBeanへの参照がある場合は、共通定義で定義されたBeanを参照することになる。

4.3.2.6.2. Bean定義の構成

ジョブのBean定義は、同期実行のBean定義と同じ構成でよい。ただし、以下の注意点がある。

  • AutomaticJobRegistrarでジョブを登録する際、ジョブのBeanIDは識別子となるため重複をしてはいけない。

  • ステップのBeanIDも重複しないことが望ましい。

    • 設計時に、BeanIDの命名規則を{ジョブID}.{ステップID}とすることで、ジョブIDのみ一意に設計すればよい。

ジョブのBean定義におけるjob-base-context.xmlのインポートは、同期実行と非同期実行で挙動が異なる。

  • 同期実行では、job-base-context.xmlから更にlaunch-context.xmlをインポートする。

  • 非同期実行では、job-base-context.xmlからlaunch-context.xmlをインポートしない。 その代わりにAsyncBatchDeamonがロードするasync-batch-daemon.xmlにて、 launch-context.xmlをインポートする。

これは、Spring Batchを起動する際に必要な各種Beanは各ジョブごとにインスタンス化する必要はないことに起因する。 Spring Batchの起動に必要な各種Beanは各ジョブの親となる共通定義(async-batch-daemon.xml)にて1つだけ生成すればよい。

4.3.3. How to use

4.3.3.1. 各種設定
4.3.3.1.1. ポーリング処理の設定

非同期実行に必要な設定は、batch-application.propertiesで行う。

batch-application.properties
#(1)
# Admin DataSource settings.
admin.jdbc.driver=org.postgresql.Driver
admin.jdbc.url=jdbc:postgresql://localhost:5432/postgres
admin.jdbc.username=postgres
admin.jdbc.password=postgres

# TERASOLUNA AsyncBatchDaemon settings.
# (2)
async-batch-daemon.schema.script=classpath:org/terasoluna/batch/async/db/schema-postgresql.sql
# (3)
async-batch-daemon.job-concurrency-num=3
# (4)
async-batch-daemon.polling-interval=5000
# (5)
async-batch-daemon.polling-initial-delay=1000
# (6)
async-batch-daemon.polling-stop-file-path=/tmp/end-async-batch-daemon
設定内容の項目一覧
項番 説明

(1)

ジョブ要求テーブルが格納されているデータベースへの接続設定。
デフォルトではJobRepositoryの設定を使用する。

(2)

ジョブ要求テーブルを定義するDDLのパス。
非同期バッチデーモン起動時にジョブ要求テーブルがない場合は、自動生成される。
これは主に試験用機能であり、batch-application.properties内の
data-source.initialize.enabledで実行可否を設定できる。
詳細な定義はasync-batch-daemon.xml内の<jdbc:initialize-database>>を参照のこと。

(3)

ポーリング時に一括で取得する件数の設定。この設定値は同時並行数としても用いる。

(4)

ポーリング周期の設定。単位はミリ秒。

(5)

ポーリング初回起動遅延時間の設定。単位はミリ秒。

(6)

終了ファイルパスの設定。

環境変数による設定値の変更

batch-application.propertiesの設定値は、同名の環境変数を定義することで設定の変更が可能である。
環境変数が設定された場合は、プロパティ値より優先して使用される。
これは、以下のBean定義に起因する。

launch-context.xmlの設定箇所
<context:property-placeholder location="classpath:batch-application.properties"
        system-properties-mode="OVERRIDE"
        ignore-resource-not-found="false"
        ignore-unresolvable="true"
        order="1"/>

詳細については、TERASOLUNA Server 5.x 開発ガイドラインの プロパティファイル定義方法についてを参照。

4.3.3.1.2. ジョブの設定

非同期実行する対象のジョブは、async-batch-daemon.xmlautomaticJobRegistrarに設定する。
以下に初期設定を示す。

async-batch-daemon.xml
<bean id="automaticJobRegistrar"
      class="org.springframework.batch.core.configuration.support.AutomaticJobRegistrar">
    <property name="applicationContextFactories">
        <bean class="org.springframework.batch.core.configuration.support.ClasspathXmlApplicationContextsFactoryBean">
            <property name="resources">
                <list>
                    <value>classpath:/META-INF/jobs/**/*.xml</value>  <!-- (1) -->
                </list>
            </property>
        </bean>
    </property>
    <property name="jobLoader">
        <bean class="org.springframework.batch.core.configuration.support.DefaultJobLoader"
              p:jobRegistry-ref="jobRegistry" />
    </property>
</bean>
設定内容の項目一覧
項番 説明

(1)

非同期実行するジョブBean定義のパス。

登録ジョブの絞込みについて

登録するジョブは、非同期実行することを前提に設計・実装されたジョブを指定すること。 非同期で実行することを想定していないジョブを含めて指定すると、ジョブ登録時に意図しない参照により例外が発生することもあるので注意すること。

絞込の例
<bean id="automaticJobRegistrar"
      class="org.springframework.batch.core.configuration.support.AutomaticJobRegistrar">
    <property name="applicationContextFactories">
        <bean class="org.springframework.batch.core.configuration.support.ClasspathXmlApplicationContextsFactoryBean">
            <property name="resources">
                <list>
                    <!-- For the async directory and below -->
                    <value>classpath:/META-INF/jobs/aysnc/**/*.xml</value>
                    <!-- For a specific job -->
                    <value>classpath:/META-INF/jobs/CASE100/SpecialJob.xml</value>
                </list>
            </property>
        </bean>
    </property>
    <property name="jobLoader">
        <bean class="org.springframework.batch.core.configuration.support.DefaultJobLoader"
            p:jobRegistry-ref="jobRegistry" />
    </property>
</bean>
ジョブパラメータの入力値検証

JobPollingTaskは、ジョブ要求テーブルから取得したレコードについて妥当性検証をしない。
よって、テーブルに登録する側にてジョブ名やジョブパラメータについて検証することが望ましい。
ジョブ名が誤っていると、ジョブを起動するが見つからず、例外が発生してしまう。
ジョブパラメータが誤っていると、ジョブは起動するが誤動作してしまう。
ジョブパラメータに限っては、ジョブ起動後に検証を行うことができる。ジョブパラメータの検証については、 パラメータの妥当性検証を参照のこと。

ジョブ設計上の留意点

非同期実行(DBポーリング)の特性上、同一ジョブの並列実行が可能になっているので、並列実行した場合に同一ジョブが影響を与えないようにする必要がある。

4.3.3.2. 非同期処理の起動から終了まで

非同期バッチデーモンの起動と終了、ジョブ要求テーブルへの登録方法について説明する。

4.3.3.2.1. 非同期バッチデーモンの起動

TERASOLUNA Batch 5.xが提供する、AsyncBatchDaemonを起動する。

AsyncBatchDaemonの起動
# Start AsyncBatchDaemon
$ java -cp dependency/* org.terasoluna.batch.async.db.AsyncBatchDaemon

この場合、META-INF/spring/async-batch-daemon.xmlを読み込み各種Beanを生成する。

また、別途カスタマイズしたasync-batch-daemon.xmlを利用したい場合は第一引数に指定してAsyncBatchDaemonを起動することで実現できる。
引数に指定するBean定義ファイルは、クラスパスからの相対パスで指定すること。
なお、第二引数以降は無視される。

カスタマイズしたMETA-INF/spring/customized-async-batch-daemon.xmlを利用する場合
# Start AsyncBatchDaemon
$ java -cp dependency/* org.terasoluna.batch.async.db.AsyncBatchDaemon \
    META-INF/spring/customized-async-batch-daemon.xml

async-batch-daemon.xmlのカスタマイズは、ごく一部の設定を変更する場合は直接修正してよい。
しかし、大幅な変更を加える場合や、後述する複数起動にて複数の設定を管理する場合は、 別途ファイルを作成して管理するほうが扱いやすい。
ユーザの状況に応じて選択すること。

dependency配下には、実行に必要なjar一式が格納されている前提とする。

4.3.3.2.2. ジョブの要求

INSERT文のSQLを発行することでジョブ要求テーブルに登録を行う。

PostgreSQLの場合
INSERT INTO batch_job_request(job_name,job_parameter,polling_status,create_date)
VALUES ('JOB01', 'param1=dummy,param2=100', 'INIT', current_timestamp);
4.3.3.2.3. 非同期バッチデーモンの停止

batch-application.propertiesに設定した終了ファイルを置く。

$ touch /tmp/end-async-batch-daemon
非同期バッチデーモン起動前に終了ファイルがある場合

非同期バッチデーモン起動前に終了ファイルがある場合、非同期バッチデーモンは即時終了する。 非同期バッチデーモンは、終了ファイルがない状態で起動する必要がある。

4.3.3.3. ジョブのステータス確認

ジョブの状態管理はSpring Batchから提供されるJobRepositoryで行い、ジョブ要求テーブルではジョブのステータスを管理しない。 ジョブ要求テーブルではjob_execution_idのカラムをもち、このカラムに格納される値により個々の要求に対するジョブのステータスを確認できるようにしている。 ここでは、SQLを直接発行してジョブのステータスを確認する簡単な例を示す。 ジョブステータス確認の詳細は、状態の確認を参照のこと。

PostgreSQLの場合
SELECT job_execution_id FROM batch_job_request WHERE job_seq_id = 1;

job_execution_id
----------------
              2
(1 row)

SELECT * FROM batch_job_execution WHERE job_execution_id = 2;

job_execution_id | version | job_instance_id |       create_time       |       start_time        |        end_time         |  status   | exit_code | exit_message |
ocation
------------------+---------+-----------------+-------------------------+-------------------------+-------------------------+-----------+-----------+--------------+-
--------
              2 |       2 |               2 | 2017-02-06 20:54:02.263 | 2017-02-06 20:54:02.295 | 2017-02-06 20:54:02.428 | COMPLETED | COMPLETED |              |
(1 row)
4.3.3.4. ジョブが異常終了した後のリカバリ

異常終了したジョブのリカバリに関する基本事項は、処理の再実行を参照のこと。ここでは、非同期実行特有の事項について説明をする。

4.3.3.4.1. リラン

異常終了したジョブのリランは、ジョブ要求テーブルに別レコードとしてINSERTすることで行う。

4.3.3.4.2. リスタート

異常終了したジョブをリスタートする場合は、コマンドラインから同期実行ジョブとして実行する。 コマンドラインからの実行する理由は、「意図したリスタート実行なのか意図しない重複実行であるかの判断が難しいため、運用で混乱をきたす可能性がある」ためである。
リスタート方法はジョブのリスタートを参照のこと。

4.3.3.4.3. 停止
  1. 処理時間が想定を超えて停止していない場合は、コマンドラインからの停止を試みる。 停止方法はジョブの停止を参照のこと。

  2. コマンドラインからの停止も受け付けない場合は、非同期バッチデーモンの停止により、非同期バッチデーモンを終了させる。

  3. 非同期バッチデーモンも終了できない状態になっている場合は、非同期バッチデーモンのプロセスを強制終了させる。

非同期バッチデーモンを終了させる場合は、他のジョブに影響がないように十分に注意して行う。

4.3.3.5. 環境配備について

ジョブのビルドとデプロイは同期実行と同じである。ただし、ジョブの設定にもあるとおり非同期実行するジョブの絞込みをしておくことが重要である。

4.3.3.6. 累積データの退避について

非同期バッチデーモンを長期運用しているとJobRepositoryとジョブ要求テーブルに膨大なデータが累積されていく。以下の理由によりこれらの累積データを退避させる必要がある。

  • 膨大なデータ量に対してデータを検索/更新する際の性能劣化

  • IDの採番用シーケンスが周回することによるIDの重複

テーブルデータの退避やシーケンスのリセットについては、利用するデータベースのマニュアルを参照してほしい。

以下に退避対象のテーブルおよびシーケンスの一覧を示す。

退避対象一覧
テーブル/シーケンス 提供しているフレームワーク

batch_job_request

TERASOLUNA Batch 5.x

batch_job_request_seq

batch_job_instance

Spring Batch

batch_job_exeution

batch_job_exeution_params

batch_job_exeution_context

batch_step_exeution

batch_step_exeution_context

batch_job_seq

batch_job_execution_seq

batch_step_execution_seq

自動採番カラムのシーケンス

自動採番のカラムに対して自動的にシーケンスが作成されている場合があるので、忘れずにそのシーケンスも退避対象に含める。

データベース固有の仕様について

Oracleではデータ型にCLOBを利用するなど、データベース固有のデータ型を使用している場合があるので注意をする。

4.3.4. How to extend

4.3.4.1. ジョブ要求テーブルのカスタマイズ

ジョブ要求テーブルは、取得レコードの抽出条件を変更するためにカラム追加をしてカスタマイズすることができる。 ただし、JobRequestPollTaskからSQLを発行する際に渡せる項目は、 BatchJobRequestの項目のみである。

ジョブ要求テーブルのカスタマイズによる拡張手順は以下のとおり。

  1. ジョブ要求テーブルのカスタマイズ

  2. BatchJobRequestMapperインターフェースの拡張インターフェースの作成

  3. カスタマイズしたテーブルを使用したSQLMapの定義

  4. async-batch-daemon.xmlのBean定義の修正

カスタマイズ例として以下のようなものがある。

以降、この2つの例について、拡張手順を説明する。

4.3.4.1.1. 優先度カラムによるジョブ実行順序の制御の例
  1. ジョブ要求テーブルのカスタマイズ

ジョブ要求テーブルに優先度カラム(priority)を追加する。

優先度カラムの追加 (PostgreSQLの場合)
CREATE TABLE IF NOT EXISTS batch_job_request (
    job_seq_id bigserial PRIMARY KEY,
    job_name varchar(100) NOT NULL,
    job_parameter varchar(200),
    priority int NOT NULL,
    job_execution_id bigint,
    polling_status varchar(10) NOT NULL,
    create_date timestamp NOT NULL,
    update_date timestamp
);
  1. BatchJobRequestMapperインターフェースの拡張インターフェースの作成

BatchJobRequestMapperインターフェースを拡張したインターフェースを作成する。

拡張インターフェース
// (1)
public interface CustomizedBatchJobRequestMapper extends BatchJobRequestMapper {
    // (2)
}
拡張ポイント
項番 説明

(1)

BatchJobRequestMapperを拡張する。

(2)

メソッドは追加しない。

  1. カスタマイズしたテーブルを使用したSQLMapの定義

優先度を順序条件にしたSQLをSQLMapに定義する。

SQLMap定義(CustomizedBatchJobRequestMapper.xml)
<!-- (1) -->
<mapper namespace="org.terasoluna.batch.extend.repository.CustomizedBatchJobRequestMapper">

    <select id="find" resultType="org.terasoluna.batch.async.db.model.BatchJobRequest">
        SELECT
            job_seq_id AS jobSeqId,
            job_name AS jobName,
            job_parameter AS jobParameter,
            job_execution_id AS jobExecutionId,
            polling_status AS pollingStatus,
            create_date AS createDate,
            update_date AS updateDate
        FROM
            batch_job_request
        WHERE
            polling_status = 'INIT'
        ORDER BY
            priority ASC,   <!--(2) -->
            job_seq_id ASC
        LIMIT #{pollingRowLimit}
    </select>

    <!-- (3) -->
    <update id="updateStatus">
        UPDATE
            batch_job_request
        SET
            polling_status = #{batchJobRequest.pollingStatus},
            job_execution_id = #{batchJobRequest.jobExecutionId},
            update_date = #{batchJobRequest.updateDate}
        WHERE
            job_seq_id = #{batchJobRequest.jobSeqId}
        AND
            polling_status = #{pollingStatus}
    </update>

</mapper>
拡張ポイント
項番 説明

(1)

BatchJobRequestMapperの拡張インターフェースをFQCNでnamespaceに設定する。

(2)

priorityをORDER句へ追加する。

(3)

更新SQLは変更しない。

  1. async-batch-daemon.xmlのBean定義の修正

(2)で作成した拡張インターフェースをbatchJobRequestMapperに設定する。

async-batch-daemon.xml
 <!--(1) -->
<bean id="batchJobRequestMapper"
      class="org.mybatis.spring.mapper.MapperFactoryBean"
      p:mapperInterface="org.terasoluna.batch.extend.repository.CustomizedBatchJobRequestMapper"
      p:sqlSessionFactory-ref="adminSqlSessionFactory" />
拡張ポイント
項番 説明

(1)

BatchJobRequestMapperの拡張インターフェースをFQCNでmapperInterfaceプロパティに設定する。

4.3.4.1.2. グループIDによる複数プロセスによる分散処理

AsyncBatchDaemon起動時に環境変数でグループIDを指定して、対象のジョブを絞り込む。

  1. ジョブ要求テーブルのカスタマイズ

ジョブ要求テーブルにグループIDカラム(group_id)を追加する。

グループIDカラムの追加 (PostgreSQLの場合)
CREATE TABLE IF NOT EXISTS batch_job_request (
    job_seq_id bigserial PRIMARY KEY,
    job_name varchar(100) NOT NULL,
    job_parameter varchar(200),
    group_id varchar(10) NOT NULL,
    job_execution_id bigint,
    polling_status varchar(10) NOT NULL,
    create_date timestamp NOT NULL,
    update_date timestamp
);
  1. BatchJobRequestMapperインターフェースの拡張インターフェース作成

  1. カスタマイズしたテーブルを使用したSQLMapの定義

優先度を順序条件にしたSQLをSQLMapに定義する。

SQLMap定義(CustomizedBatchJobRequestMapper.xml)
<!-- (1) -->
<mapper namespace="org.terasoluna.batch.extend.repository.CustomizedBatchJobRequestMapper">

    <select id="find" resultType="org.terasoluna.batch.async.db.model.BatchJobRequest">
        SELECT
            job_seq_id AS jobSeqId,
            job_name AS jobName,
            job_parameter AS jobParameter,
            job_execution_id AS jobExecutionId,
            polling_status AS pollingStatus,
            create_date AS createDate,
            update_date AS updateDate
        FROM
            batch_job_request
        WHERE
            polling_status = 'INIT'
        AND
            group_id = #{groupId}  <!--(2) -->
        ORDER BY
            job_seq_id ASC
        LIMIT #{pollingRowLimit}
    </select>

    <!-- ommited -->
</mapper>
拡張ポイント
項番 説明

(1)

BatchJobRequestMapperの拡張インターフェースをFQCNでnamespaceに設定する。

(2)

groupIdを検索条件に追加。

  1. async-batch-daemon.xmlのBean定義の修正

(2)で作成した拡張インターフェースをbatchJobRequestMapperに設定し、 jobRequestPollTaskに環境変数で与えられたグループIDをクエリパラメータとして設定する。

async-batch-daemon.xml
 <!--(1) -->
<bean id="batchJobRequestMapper"
      class="org.mybatis.spring.mapper.MapperFactoryBean"
      p:mapperInterface="org.terasoluna.batch.extend.repository.CustomizedBatchJobRequestMapper"
      p:sqlSessionFactory-ref="adminSqlSessionFactory" />

    <bean id="jobRequestPollTask"
          class="org.terasoluna.batch.async.db.JobRequestPollTask"
          c:transactionManager-ref="adminTransactionManager"
          c:jobOperator-ref="jobOperator"
          c:batchJobRequestMapper-ref="batchJobRequestMapper"
          c:daemonTaskExecutor-ref="daemonTaskExecutor"
          c:automaticJobRegistrar-ref="automaticJobRegistrar"
          p:optionalPollingQueryParams-ref="pollingQueryParam" /> <!-- (2) -->

   <bean id="pollingQueryParam"
         class="org.springframework.beans.factory.config.MapFactoryBean">
        <property name="sourceMap">
            <map>
                <entry key="groupId" value="${GROUP_ID}"/>  <!-- (3) -->
            </map>
        </property>
   </bean>
拡張ポイント
項番 説明

(1)

BatchJobRequestMapperの拡張インターフェースをFQCNでmapperInterfaceプロパティに設定する。

(2)

JobRequestPollTaskoptionalPollingQueryParamsプロパティに(3)で定義するMapを設定する。

(3)

環境変数で与えれらたグループID(GROUP_ID)をクエリパラメータのグループID(groupId)に設定する。

  1. 環境変数にグループIDを設定後、AsyncBatchDaemonを起動する。

AsyncBatchDaemonの起動
# Set environment variables
$ export GROUP_ID=G1

# Start AsyncBatchDaemon
$ java -cp dependency/* org.terasoluna.batch.async.db.AsyncBatchDaemon
4.3.4.2. 複数起動

以下の様な目的で、複数サーバ上で非同期バッチデーモンを起動させる場合がある。

  • 可用性向上

    • 非同期バッチジョブがいずれかのサーバで実行できればよく、ジョブが起動できないという状況をなくしたい場合

  • 性能向上

    • 複数サーバでバッチ処理の負荷を分散させたい場合

  • リソースの有効利用

    • サーバ性能に差がある場合に特定のジョブを最適なリソースのサーバに振り分ける場合

上記に示す観点のいずれかにもとづいて利用するのかを意識して運用設計を行うことが必要となる。

Ch04 AsyncJobWithDB MultipleActivation
複数起動の概略図
複数の非同期バッチデーモンが同一ジョブ要求レコードを取得した場合

JobRequestPollTaskは、楽観ロックによる排他制御を行っているため、ポーリングステータスをINITからPOLLEDへ更新できた非同期バッチデーモンが取得したレコードのジョブを実行できる。 排他された他の非同期バッチデーモンは、次のジョブ要求レコードを処理する。

4.3.5. Appendix

4.3.5.1. ジョブ定義のモジュール化について

ApplicationContextの構成でも簡単に説明したが、AutomaticJobRegistrarを用いることで以下の事象を回避することができる。

  • 同じBeanID(BeanName)を使用すると、Beanが上書きされてしまい、ジョブが意図しない動作をする。

    • その結果、意図しないエラーが発生する可能性が高くなる。

  • エラーを回避するために、ジョブ全体でBeanすべてのIDが一意になるように命名しなければいけなくなる。

    • ジョブ数が増えてくると管理するのが困難になり、不必要なトラブルが発生する可能性が高くなる。

AutomaticJobRegistrarを使用しない場合に起こる現象について説明をする。 ここで説明する内容は上記の問題を引き起こすので、非同期実行では使用しないこと。

Job1.xml
<!-- Reader -->
<!-- (1) -->
<bean id="reader" class="org.mybatis.spring.batch.MyBatisCursorItemReader"
      p:queryId="jp.terasoluna.batch.job.repository.EmployeeRepositoy.findAll"
      p:sqlSessionFactory-ref="jobSqlSessionFactory"/>

<!-- Writer -->
<!-- (2) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[basedir]}/input/employee.csv">
  <property name="lineAggregator">
    <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
      <property name="fieldExtractor">
        <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
              p:names="invoiceNo,salesDate,productId,customerId,quant,price"/>
      </property>
    </bean>
  </property>
</bean>


<!-- Job -->
<batch:job id="job1" job-repository="jobRepository">
  <batch:step id="job1.step">
    <batch:tasklet transaction-manager="transactionManager">
      <batch:chunk reader="reader" writer="writer" commit-interval="100" />
    </batch:tasklet>
  </batch:step>
</batch:job>
Job2.xml
<!-- Reader -->
<!-- (3) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[basedir]}/input/invoice.csv">
  <property name="lineMapper">
    <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
      <property name="lineTokenizer">
        <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
              p:names="invoiceNo,salesDate,productId,customerId,quant,price"/>
      </property>
      <property name="fieldSetMapper" ref="invoiceFieldSetMapper"/>
    </bean>
  </property>
</bean>

<!-- Writer -->
<!-- (4) -->
<bean id="writer" class="org.mybatis.spring.batch.MyBatisBatchItemWriter"
      p:statementId="jp.terasoluna.batch.job.repository.InvoiceRepository.create"
      p:sqlSessionFactory-ref="jobSqlSessionFactory"/>

<!-- Job -->
<batch:job id="job2" job-repository="jobRepository">
  <batch:step id="job2.step">
    <batch:tasklet transaction-manager="transactionManager">
      <batch:chunk reader="reader" writer="writer" commit-interval="100" />
    </batch:tasklet>
  </batch:step>
</batch:job>
BeanIdが上書きされる定義
<bean id="automaticJobRegistrar"
      class="org.springframework.batch.core.configuration.support.AutomaticJobRegistrar">
    <property name="applicationContextFactories">
        <bean class="org.springframework.batch.core.configuration.support.ClasspathXmlApplicationContextsFactoryBean">
            <property name="resources">
                <list>
                    <value>classpath:/META-INF/jobs/other/async/*.xml</value>  <!-- (5) -->
                </list>
            </property>
        </bean>
    </property>
    <property name="jobLoader">
        <bean class="org.springframework.batch.core.configuration.support.DefaultJobLoader"
              p:jobRegistry-ref="jobRegistry"/>
    </property>
</bean>

<bean class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor"
    p:jobRegistry-ref="jobRegistry" />

<import resource="classpath:/META-INF/jobs/async/*.xml" />   <!-- (6) -->
設定のポイント一覧
項番 説明

(1)

Job1ではデータベースから読み込むItemReaderをreaderというBeanIDで定義する。

(2)

Job1ではファイルへ書き込むItemWriterをwriterというBeanIDで定義する。

(3)

Job2ではファイルから読み込むItemReaderをreaderというBeanIDで定義する。

(4)

Job2ではデータベースへ書き込むItemWriterをwriterというBeanIDで定義する。

(5)

AutomaticJobRegistrarは対象となるジョブ以外のジョブ定義を読む込むように設定する。

(6)

Springのimportを使用して、対象のJob定義を読み込むようにする。

この場合、Job1.xml,Job2.xmlの順に読み込まれたとすると、Job1.xmlで定義されたいたreader,writerはJob2.xmlの定義で上書きされる。
その結果、Job1を実行すると、Job2のreader,writerが使用されて期待した処理が行われなくなる。

4.4. 非同期実行(Webコンテナ)

4.4.1. Overview

Webコンテナ内でジョブを非同期で実行するための方法について説明する。

本機能は、チャンクモデルとタスクレットモデルとで同じ使い方になる。

Webコンテナによるジョブの非同期実行とは

ジョブを含めたWebアプリケーションをWebコンテナにデプロイし、 送信されたリクエストの情報を元にジョブを実行することを指す。
ジョブの実行ごとに1つのスレッドを割り当てた上で並列に動作するため、 他のジョブやリクエストに対する処理とは独立して実行できる。

提供機能

TERASOLUNA Batch 5.xでは、非同期実行(Webコンテナ)向けの実装は提供しない。
本ガイドラインにて実現方法を提示するのみとする。
これは、Webアプリケーションの起動契機はHTTP/SOAP/MQなど多様であるため、 ユーザにて実装することが適切と判断したためである。

利用前提
  • アプリケーションの他にWebコンテナが必要となる。

  • ジョブの実装以外に必要となる、Webアプリケーション、クライアントは動作要件に合わせて別途実装する。

  • ジョブの実行状況および結果はJobRepositoryに委ねる。 また、Webコンテナ停止後にもJobRepositoryからジョブの実行状況および結果を参照可能とするため、 インメモリデータベースではなく、永続性が担保されているデータベースを使用する。

活用シーン

非同期実行(DBポーリング) - Overviewと同様である。

非同期実行(DBポーリング)との違い

アーキテクチャ上、非同期実行時の即時性と、要求管理テーブルの有無、の2点が異なる。
非同期実行(DBポーリング)は要求管理テーブルに登録された複数のジョブが一定の周期で非同期実行される。
それに対し、本機能は要求管理テーブルを必要とせず代わりにWebコンテナ上で非同期実行を受け付ける。
Webリクエスト送信により直ちに実行するため、起動までの即時性が求められるショートバッチに向いている。

4.4.2. Architecture

本方式による非同期ジョブはWebコンテナ上にデプロイされたアプリケーション(war)として動作するが、 ジョブ自身はWebコンテナのリクエスト処理とは非同期(別スレッド)で動作する。

sequence of async web
非同期実行(Webコンテナ)の処理シーケンス図
ジョブの起動
  1. Webクライアントは実行対象のジョブをWebコンテナに要求する。

  2. JobControllerはSpring BatchのJobOperatorに対しジョブの実行開始を依頼する。

  3. ThreadPoolTaskExecutorによって非同期でジョブを実行する。

  4. 実行された対象のジョブを一意に判別するためのジョブ実行ID(job execution id)を返却する。

  5. JobControllerはWebクライアントに対し、ジョブ実行IDを含むレスポンスを返却する。

  6. 目的のジョブを実行する。

    • ジョブの結果はJobRepositoryに反映される。

  7. Jobが実行結果を返却する。これはクライアントへ直接通知できない。

ジョブの実行結果確認
  1. Webクライアントはジョブ実行IDをJobControllerをWebコンテナに送信する。

  2. JobControllerはジョブ実行IDを用いJobExplorerにジョブの実行結果を問い合わせる。

  3. JobExplorerはジョブの実行結果を返却する。

  4. JobControllerはWebクライアントに対しレスポンスを返却する。

    • レスポンスにはジョブ実行IDを設定する。

Webコンテナによるリクエスト受信後、ジョブ実行ID払い出しまでがリクエスト処理と同期するが、 以降のジョブ実行はWebコンテナとは別のスレッドプールで非同期に行われる。
これは再度リクエストで問い合わせを受けない限り、Webクライアント側では非同期ジョブの 実行状態が検知できないことを意味する。

このためWebクライアント側では1回のジョブ実行で、リクエストを「ジョブの起動」で1回、 「結果の確認」が必要な場合は加えてもう1回、Webコンテナにリクエストを送信する必要がある。
特に初回の「ジョブの起動」時に見え方が異なる異常検知については、 後述のジョブ起動時における異常発生の検知についてで説明する。

JobRepositoryJobExplorerを使用して直接RDBMSを参照し、ジョブの実行状態を確認することもできる。 ジョブの実行状態・結果を参照する機能の詳細については、ジョブの管理を参照のこと。

ジョブ実行ID(job execution id)の取り扱いについて

ジョブ実行IDは起動対象が同じジョブ、同じジョブパラメータであっても、ジョブ起動ごとに異なるシーケンス値が払い出される。
リクエスト送信により受付が行われたジョブ実行IDはJobRepositoryにより外部RDBMSで永続化される。
しかし、Webクライアントの障害などによりこのIDが消失した場合、ジョブ実行状況の特定・追跡が困難となる。
このため、Webクライアント側ではレスポンスとして返却されたジョブ実行IDをログに記録するなど、ジョブ実行IDの消失に備えておくこと。

4.4.2.1. ジョブ起動時における異常発生の検知について

Webクライアントからジョブの起動リクエストを送信後、ジョブ実行ID払い出しを境にして異常検知の見え方が異なる。

  • ジョブ起動時のレスポンスにて異常がすぐ検知できるもの

    • 起動対象のジョブが存在しない。

    • ジョブパラメータの形式誤り。

  • ジョブ起動後、Webコンテナに対しジョブ実行状態・結果の問い合わせが必要となるもの

    • ジョブの実行ステータス

    • 非同期ジョブ実行で使用されるスレッドプールが枯渇したことによるジョブの起動失敗

「ジョブ起動時の異常」は Spring MVCコントローラ内で発生する例外として検知できる。 ここでは説明を割愛するので、別途 TERASOLUNA Server 5.x 開発ガイドラインの 例外のハンドリングの実装を参照のこと。

また、ジョブパラメータとして利用するリクエストの入力チェックは必要に応じて Spring MVC のコントローラ内で行うこと。
具体的な実装方法については、TERASOLUNA Server 5.x 開発ガイドラインの 入力チェックを参照のこと。

スレッドプール枯渇によるジョブの起動失敗はジョブ起動時に補足できない

スレッドプール枯渇によるジョブの起動失敗は、JobOperatorから例外があがってこないため、別途確認する必要がある。 確認方法の1つは、ジョブの実行状態確認時にJobExplorerを用い、以下の条件に合致しているかどうかである。

  • ステータスがFAILEDである

  • jobExecution.getExitStatus().getExitDescription()にて、 org.springframework.core.task.TaskRejectedExceptionの例外スタックトレースが記録されている

4.4.2.2. 非同期実行(Webコンテナ)のアプリケーション構成

本機能は非同期実行(DBポーリング)と同様、 非同期実行特有の構成としてSpring プロファイルのasyncAutomaticJobRegistrarを使用している。

一方で、これら機能を非同期実行(Webコンテナ)使用する上で、いくつかの事前知識と設定が必要となる。 ApplicationContextの構成を参照。
具体的なasyncプロファイルとAutomaticJobRegistrarの設定方法については 非同期実行(Webコンテナ)によるアプリケーションの実装方法についてで後述する。

4.4.2.2.1. ApplicationContextの構成

上述のとおり、非同期実行(Webコンテナ)のアプリケーション構成として、複数のアプリケーションモジュールが含まれている。
それぞれのアプリケーションコンテキストとBean定義についての種類、および関係性を把握しておく必要がある。

Package structure of async web
ApplicationContextの構成
BeanDefinitions structure of async web
Bean定義ファイルの構成

非同期実行(Webコンテナ)におけるApplicationContextでは、 バッチアプリケーションのApplicationContextはWebのコンテキスト内に取り込まれる。
個々のジョブコンテキストはこのWebコンテキストからAutomaticJobRegistrarによりモジュール化され、 Webコンテキストの子コンテキストとして動作する。

以下、それぞれのコンテキストを構成するBean定義ファイルについて説明する。

Bean定義ファイル一覧
項番 説明

(1)

共通Bean定義ファイル。
アプリケーション内では親コンテキストとなり、子コンテキストであるジョブ間で一意に共有される。

(2)

ジョブBean定義から必ずインポートされるBean定義ファイル。
Spring プロファイルが非同期実行時に指定されるasyncの場合は(1)のlaunch-context.xmlを読み込まない。

(3)

ジョブごとに作成するBean定義ファイル。
AutomaticJobRegistrarによりモジュラー化され、アプリケーション内ではそれぞれ独立した子コンテキストとして使用される。

(4)

DispatcherServletから読み込まれる。
ジョブBean定義のモジュラー化を行うAutomaticJobRegistrarや、ジョブの非同期・並列実行で使用されるスレッドプールであるtaskExecutorなど、非同期実行特有のBeanを定義する。
また、非同期実行では(1)のlaunch-context.xml
を直接インポートし親コンテキストとして一意に共有化される。

(5)

ContextLoaderListenerにより、Webアプリケーション内で共有される親コンテキストとなる。

4.4.3. How to use

ここでは、Webアプリケーション側の実装例として、TERASOLUNA Server Framework for Java (5.x)を用いて説明する。
あくまで説明のためであり、TERASOLUNA Server 5.xは非同期実行(Webコンテナ)の必須要件ではないことに留意してほしい。

4.4.3.1. 非同期実行(Webコンテナ)によるアプリケーションの実装概要

以下の構成を前提とし説明する。

  • Webアプリケーションプロジェクトとバッチアプリケーションプロジェクトは独立し、 webアプリケーションからバッチアプリケーションを参照する。

    • Webアプリケーションプロジェクトから生成するwarファイルは、 バッチアプリケーションプロジェクトから生成されるjarファイルを含むこととなる

非同期実行の実装はArchitectureに従い、Webアプリケーション内の Spring MVCコントローラが、JobOperatorによりジョブを起動する。

Web/バッチアプリケーションプロジェクトの分離について

アプリケーションビルドの最終成果物はWebアプリケーションのwarファイルであるが、 開発プロジェクトはWeb/バッチアプリケーションで分離して実装を行うとよい。
これはバッチアプリケーション単体で動作可能なライブラリとなるため、開発プロジェクト上の試験を 容易にする他、作業境界とライブラリ依存関係を明確にする効果がある。

以降、Web/バッチの開発について、以下2つを利用する前提で説明する。

  • TERASOLUNA Batch 5.xによるバッチアプリケーションプロジェクト

  • TERASOLUNA Server 5.xによるWebアプリケーションプロジェクト

バッチアプリケーションプロジェクトの作成および具体的なジョブの実装方法については、 プロジェクトの作成タスクレットモデルジョブの作成チャンクモデルジョブの作成 を参照のこと。 ここでは、Webアプリケーションからバッチアプリケーションを起動することに終始する。

ここでは Maven archetype:generate を用い、 以下のバッチアプリケーションプロジェクトを作成しているものとして説明する。

ジョブプロジェクト作成例
名称

groupId

org.terasoluna.batch.sample

archetypeId

asyncbatch

version

1.0-SNAPSHOT

package

org.terasoluna.batch.sample

また説明の都合上、ブランクプロジェクトに初めから登録されているジョブを使用する。

説明に用いるジョブ
名称 説明

ジョブ名

job01

ジョブパラメータ

param1=value1

非同期実行(Webコンテナ)ジョブ設計の注意点

非同期実行(Webコンテナ)の特性として個々のジョブは短時間で完了しWebコンテナ上でステートレスに 動作するケースが適している。
また複雑さを避ける上では、ジョブ定義を単一のステップのみで構成し、ステップの終了コードによるフローの分岐や 並列処理・多重処理を定義しないことが望ましい。

ジョブ実装を含むjarファイルが作成可能な状態として、Webアプリケーションの作成を行う。

Webアプリケーションの実装

TERASOLUNA Server 5.xが提供するブランクプロジェクトを用い、Webアプリケーションの実装方法を説明する。 詳細は、TERASOLUNA Server 5.x 開発ガイドライン Webアプリケーション向け開発プロジェクトの作成 を参照。

ここでは非同期実行アプリケーションプロジェクトと同様、以下の名称で作成したものとして説明する。

Webコンテナプロジェクト作成例
名称

groupId

org.terasoluna.batch.sample

archetypeId

asyncapp

version

1.0-SNAPSHOT

package

org.terasoluna.batch.sample

groupIdの命名について

プロジェクトの命名は任意であるが、Maven マルチプロジェクトとしてバッチアプリケーションを Webアプリケーションの子モジュールとする場合、groupIdは統一しておくと管理しやすい。
ここでは両者のgroupIdorg.terasoluna.batch.sampleとしている。

4.4.3.2. 各種設定
バッチアプリケーションをWebアプリケーションの一部に含める

pom.xmlを編集し、バッチアプリケーションをWebアプリケーションの一部に含める。

バッチアプリケーションをjar としてNEXUSやMavenローカルリポジトリに登録し、 Webアプリケーションとは別プロジェクトとする場合はこの手順は不要である。
ただし、Mavenによりビルドされる対象が別プロジェクトとなり、バッチアプリケーションの修正を行ってもWebアプリケーションのビルド時に反映されないため注意すること。
バッチアプリケーションの修正をWebアプリケーションに反映させるためには同リポジトリに登録する必要がある。

directory structure
ディレクトリ構成
asyncapp/pom.xml
<project>
  <!-- omitted -->
  <modules>
    <module>asyncapp-domain</module>
    <module>asyncapp-env</module>
    <module>asyncapp-initdb</module>
    <module>asyncapp-web</module>
    <module>asyncapp-selenium</module>
    <module>asyncbatch</module> <!-- (1) -->
  </modules>
</project>
asyncapp/asyncbatch/pom.xml
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.terasoluna.batch.sample</groupId> <!-- (2) -->
  <artifactId>asyncbatch</artifactId>
  <version>1.0-SNAPSHOT</version> <!-- (2) -->
  <!-- (1) -->
  <parent>
    <groupId>org.terasoluna.batch.sample</groupId>
    <artifactId>asyncapp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>
  </parent>
  <!-- omitted -->
</project>
削除・追加内容
項番 説明

(1)

Webアプリケーションを親とし、バッチアプリケーションを子とするための設定を追記する。

(2)

子モジュール化にともない、不要となる記述を削除する。

依存ライブラリの追加

バッチアプリケーションをWebアプリケーションの依存ライブラリとして追加する。

asyncapp/async-web/pom.xml
<project>
  <!-- omitted -->
  <dependencies>
  <!-- (1) -->
    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>asyncbatch</artifactId>
        <version>${project.version}</version>
    </dependency>
    <!-- omitted -->
  </dependencies>
  <!-- omitted -->
</project>
追加内容
項番 説明

(1)

バッチアプリケーションをWebアプリケーションの依存ライブラリとして追加する。

4.4.3.3. Webアプリケーションの実装

ここではWebアプリケーションとして、以下TERASOLUNA Server 5.x 開発ガイドラインを参考に、RESTful Webサービスを作成する。

4.4.3.3.1. Webアプリケーションの設定

まず、Webアプリケーションのブランクプロジェクトから、各種設定ファイルの追加・削除・編集を行う。

説明の都合上、バッチアプリケーションの実装形態としてRESTful Web Service を用いた実装を行っている。
従来のWebアプリケーション(Servlet/JSP)やSOAPを使用した場合でも同様な手順となるので、適宜読み替えること。

AppendBeanDefinitionsOnBlank
ブランクプロジェクトから追加・削除するBean定義ファイル
asyncapp/asyncapp-web/src/main/resources/META-INF/spring/spring-mvc-rest.xml の記述例
<!-- omitted -->
<!-- (1) -->
<import resource="classpath:META-INF/spring/launch-context.xml"/>

<bean id="jsonMessageConverter"
      class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"
      p:objectMapper-ref="objectMapper"/>

<bean id="objectMapper"
      class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
  <property name="dateFormat">
      <bean class="com.fasterxml.jackson.databind.util.StdDateFormat"/>
  </property>
</bean>

<mvc:annotation-driven>
  <mvc:message-converters register-defaults="false">
    <ref bean="jsonMessageConverter"/>
  </mvc:message-converters>
</mvc:annotation-driven>

<mvc:default-servlet-handler/>

<!-- (2) -->
<context:component-scan base-package="org.terasoluna.batch.sample.app.api"/>

<!-- (3) -->
<bean class="org.springframework.batch.core.configuration.support.AutomaticJobRegistrar">
    <property name="applicationContextFactories">
        <bean class="org.springframework.batch.core.configuration.support.ClasspathXmlApplicationContextsFactoryBean">
            <property name="resources">
                <list>
                  <value>classpath:/META-INF/jobs/**/*.xml</value>
                </list>
            </property>
        </bean>
    </property>
    <property name="jobLoader">
        <bean class="org.springframework.batch.core.configuration.support.DefaultJobLoader"
              p:jobRegistry-ref="jobRegistry"/>
    </property>
</bean>

<!-- (4) -->
<task:executor id="taskExecutor" pool-size="3" queue-capacity="10"/>

<!-- (5) -->
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"
      p:jobRepository-ref="jobRepository"
      p:taskExecutor-ref="taskExecutor"/>
<!-- omitted -->
asyncapp/asyncapp-web/src/main/webapp/WEB-INF/web.xml の記述例
<!-- omitted -->
<servlet>
    <servlet-name>restApiServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <!-- (6) -->
        <param-value>classpath*:META-INF/spring/spring-mvc-rest.xml</param-value>
    </init-param>
    <!-- (7) -->
    <init-param>
        <param-name>spring.profiles.active</param-name>
        <param-value>async</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>restApiServlet</servlet-name>
    <url-pattern>/api/v1/*</url-pattern>
</servlet-mapping>
<!-- omitted -->
RESTful Web Service の有効化例
項番 説明

(1)

バッチアプリケーション内にあるlaunch-context.xmlのimportし、必須となるBean定義を取り込む。

(2)

コントローラを動的にスキャンするためのパッケージを記述する。

(3)

個々のジョブBean定義ファイルをモジュラー化することにより子コンテキストとして動的ロードを行うAutomaticJobRegistrarのBean定義を記述する。

(4)

非同期で実行するジョブが用いるTaskExecutorを定義する。
JobLauncherのTaskExecutorAsyncTaskExecutor実装クラスを設定することで非同期実行が可能となる。 AsyncTaskExecutor実装クラスの1つであるThreadPoolTaskExecutorを利用する。

また、並列起動可能なスレッドの多重度を指定ことができる。
この例では3スレッドがジョブの実行に割り当てられ、それを超えたリクエストは10までがキューイングされる。 キューイングされたジョブは未開始の状態ではあるが、REST リクエストは成功とみなされる。
さらにキューイングの上限を超えたジョブのリクエストはorg.springframework.core.task.TaskRejectedExceptionが発生し、 ジョブの起動要求が拒否される。

(5)

(4)のtaskExecutorを有効化するため、launch-context.xmlで定義されているjobLauncherをオーバーライドする。

(6)

DispatcherServletが読み込むBean定義として、
上述で記載したspring-mvc-rest.xmlを指定する。

(7)

Spring Framework のプロファイルとして、非同期バッチを表すasyncを明示する。

asyncプロファイルの指定をしなかった場合

この場合、Webアプリケーション横断で共有すればよいlaunch-context.xmlに定義されたBeanが、ジョブごとに重複して生成される。
重複した場合でも機能上動作するため誤りに気づきにくく、予期しないリソース枯渇や性能劣化が発生する恐れがある。 必ず指定すること。

スレッドプールのサイジング

スレッドプールの上限が過剰である場合、膨大なジョブが並走することとなり、 アプリケーション全体のスループットが劣化する恐れがある。 サイジングを行ったうえで適正な上限値を定めること。
非同期実行のスレッドプールとは別に、Webコンテナのリクエストスレッドや 同一筐体内で動作している他のアプリケーションも含めて検討する必要がある。

また、スレッドプール枯渇に伴うTaskRejectException発生の確認、および再実行は Webクライアントから別途リクエストを送信する必要がある。 そのため、スレッドプール枯渇時、ジョブ起動を待機させるqueue-capacityは必ず設定すること。

RESTful Web Service API の定義

REST APIで使用するリクエストの例として、 ここでは「ジョブの起動」、「ジョブの状態確認」の2つを定義する。

REST API 定義例
項番 API パス HTTPメソッド 要求/応答 電文形式 電文の説明

(1)

ジョブの起動

/api/v1/job/ジョブ名

POST

リクエスト

JSON

ジョブパラメータ

レスポンス

JSON

ジョブ実行ID
ジョブ名
メッセージ

(2)

ジョブの実行状態確認

/api/v1/job/ジョブ実行ID

GET

リクエスト

N/A

N/A

レスポンス

JSON

ジョブ実行ID
ジョブ名
ジョブ実行ステータス
ジョブ終了コード
ステップ実行ID
ステップ名
ステップ終了コード

4.4.3.3.2. コントローラで使用するJavaBeansの実装

JSON電文としてRESTクライアントに返却される以下3クラスを作成する。

  • ジョブ起動操作 JobOperationResource

  • ジョブの実行状態 JobExecutionResource

  • ステップの実行状態 StepExecutionResource

これらクラスはJobOperationResourceのジョブ実行ID(job execution id)を除きあくまで参考実装であり、フィールドの実装は任意である。

ジョブ起動操作情報実装例
// asyncapp/asyncapp-web/src/main/java/org/terasoluna/batch/sample/app/api/jobinfo/JobOperationResource.java
package org.terasoluna.batch.sample.app.api.jobinfo;

public class JobOperationResource {

    private String jobName = null;

    private String jobParams = null;

    private Long jobExecutionId = null;

    private String errorMessage = null;

    private Exception error = null;

    // Getter and setter are omitted.
}
ジョブ実行情報実装例
// asyncapp/asyncapp-web/src/main/java/org/terasoluna/batch/sample/app/api/jobinfo/JobExecutionResource.java
package org.terasoluna.batch.sample.app.api.jobinfo;

// omitted.

public class JobExecutionResource {

    private Long jobExecutionId = null;

    private String jobName = null;

    private Long stepExecutionId = null;

    private String stepName = null;

    private List<StepExecutionResource> stepExecutions = new ArrayList<>();

    private String status = null;

    private String exitStatus = null;

    private String errorMessage;

    private List<String> failureExceptions = new ArrayList<>();

    // Getter and setter are omitted.
}
ステップ実行情報実装例
// asyncapp/asyncapp-web/src/main/java/org/terasoluna/batch/sample/app/api/jobinfo/StepExecutionResource.java
package org.terasoluna.batch.sample.app.api.jobinfo;

public class StepExecutionResource {

  private Long stepExecutionId = null;

  private String stepName = null;

  private String status = null;

  private List<String> failureExceptions = new ArrayList<>();

    // Getter and setter are omitted.
}
4.4.3.3.3. コントローラの実装

@RestControllerを用い、RESTful Web Service のコントローラを実装する。
ここでは簡単のため、JobOperatorをコントローラにインジェクションし、ジョブの起動や実行状態の取得を行う。 もちろんTERASOLUNA Server 5.xに従って、コントローラからServiceをはさんでJobOperatorを起動してもよい。

ジョブ起動時に渡されるジョブパラメータについて

起動時にJobOperator#start()の第二引数で渡されるジョブパラメータはStringである。 ジョブパラメータが複数ある場合、同期実行のCommandLineJobRunnerとは異なり、カンマ区切りで渡す必要がある。 具体的には以下の形式をとる。
{ジョブパラメータ1}={値1},{ジョブパラメータ2}={値2},…​

これは、非同期実行(DBポーリング)におけるジョブパラメータの指定方法と同様である。

コントローラ実装例
// asyncapp/asyncapp-web/src/main/java/org/terasoluna/batch/sample/app/api/JobController.java
package org.terasoluna.batch.sample.app.api;

// omitted

// (1)
@RequestMapping("job")
@RestController
public class JobController {

    // (2)
    @Inject
    JobOperator jobOperator;

    // (2)
    @Inject
    JobExplorer jobExplorer;

    @RequestMapping(value = "{jobName}", method = RequestMethod.POST)
    public ResponseEntity<JobOperationResource> launch(@PathVariable("jobName") String jobName,
            @RequestBody JobOperationResource requestResource) {

        JobOperationResource responseResource = new JobOperationResource();
        responseResource.setJobName(jobName);
        try {
            // (3)
            Long jobExecutionId = jobOperator.start(jobName, requestResource.getJobParams());
            responseResource.setJobExecutionId(jobExecutionId);
            return ResponseEntity.ok().body(responseResource);
        } catch (NoSuchJobException | JobInstanceAlreadyExistsException | JobParametersInvalidException e) {
            responseResource.setError(e);
            return ResponseEntity.badRequest().body(responseResource);
        }
    }

    @RequestMapping(value = "{jobExecutionId}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    public JobExecutionResource getJob(@PathVariable("jobExecutionId") Long jobExecutionId) {

        JobExecutionResource responseResource = new JobExecutionResource();
        responseResource.setJobExecutionId(jobExecutionId);

        // (4)
        JobExecution jobExecution = jobExplorer.getJobExecution(jobExecutionId);

        if (jobExecution == null) {
            responseResource.setErrorMessage("Job execution not found.");
        } else {
            mappingExecutionInfo(jobExecution, responseResource);
        }

        return responseResource;
    }

    private void mappingExecutionInfo(JobExecution src, JobExecutionResource dest) {
      dest.setJobName(src.getJobInstance().getJobName());
      for (StepExecution se : src.getStepExecutions()) {
          StepExecutionResource ser = new StepExecutionResource();
          ser.setStepExecutionId(se.getId());
          ser.setStepName(se.getStepName());
          ser.setStatus(se.getStatus().toString());
          for (Throwable th : se.getFailureExceptions()) {
              ser.getFailureExceptions().add(th.toString());
          }
          dest.getStepExecutions().add(ser);
      }
      dest.setStatus(src.getStatus().toString());
      dest.setExitStatus(src.getExitStatus().toString());
    }
}
コントローラの実装
項番 説明

(1)

@RestControllerを指定する。 さらに@RequestMapping("job")により、web.xmlのサーブレットマッピングとあわせると、 REST APIの基底パスはcontextName/api/v1/job/となる。

(2)

JobOperatorJobExplorerのフィールドインジェクションを記述する。

(3)

JobOperator を使用して新規に非同期ジョブを起動する。
返り値としてジョブ実行IDを受け取り、REST クライアントに返却する。

(4)

JobExplorer を使用し、ジョブ実行IDを基にジョブの実行状態(JobExecution)を取得する。
あらかじめ設計された電文フォーマットに変換した上でRESTクライアントに返却する。

4.4.3.3.4. Web/バッチアプリケーションモジュール設定の統合

バッチアプリケーションモジュール(asyncbatch)は単体で動作可能なアプリケーションとして動作する。 そのため、バッチアプリケーションモジュール(asyncbatch)は、Webアプリケーションモジュール(asyncapp-web)との間で競合・重複する設定が存在する。 これらは、必要に応じて統合する必要がある。

  1. ログ設定ファイルlogback.xmlの統合
    Web/バッチ間でLogback定義ファイルが複数定義されている場合、正常に動作しない。
    asyncbatch/src/main/resources/logback.xmlの記述内容はasyncapp-env/src/main/resources/の同ファイルに統合した上で削除する。

  2. データソース、MyBatis設定ファイルは統合しない
    データソース、MyBatis設定ファイルの定義はWeb/バッチ間では、以下関係によりアプリケーションコンテキストの定義が独立するため、統合しない。

    • バッチのasyncbatchモジュールはサーブレットに閉じたコンテキストとして定義される。

    • Webのasyncapp-domainasyncapp-envモジュールはアプリケーション全体で使用されるコンテキストとして定義される。

Webとバッチモジュールによるデータソース、MyBatis設定の相互参照

Webとバッチモジュールによるコンテキストのスコープが異なるため、 特にWebモジュールからバッチのデータソース、MyBatis設定、Mapperインターフェースは参照できない。
RDBMSスキーマ初期化もそれぞれ異なるモジュールの設定に応じて独立して行われるため、相互干渉により 意図しない初期化が行われないよう配慮すること。

REST コントローラ特有のCSRF対策設定

Webブランクプロジェクトの初期設定では、RESTコントローラに対しリクエストを送信するとCSRFエラーとして ジョブの実行が拒否される。 そのため、ここでは以下方法によりCSRF対策を無効化した前提で説明している。

ここで作成されるWebアプリケーションはインターネット上には公開されず、CSRFを攻撃手段として 悪用しうる第三者からのRESTリクエスト送信が発生しない前提でCSRF対策を無効化している。 実際のWebアプリケーションでは動作環境により要否が異なる点に注意すること。

4.4.3.3.5. ビルド

Mavenコマンドでビルドし、warファイルを作成する。

$ cd asyncapp
$ ls
asyncbatch/  asyncapp-web/  pom.xml
$ mvn clean package
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] TERASOLUNA Server Framework for Java (5.x) Web Blank Multi Project (MyBatis3)
[INFO] TERASOLUNA Batch Framework for Java (5.x) Blank Project
[INFO] asyncapp-web
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building TERASOLUNA Server Framework for Java (5.x) Web Blank Multi Project (MyBatis3) 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------

(omitted)

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] TERASOLUNA Server Framework for Java (5.x) Web Blank Multi Project (MyBatis3) SUCCESS [  0.226 s]
[INFO] TERASOLUNA Batch Framework for Java (5.x) Blank Project SUCCESS [  6.481s]
[INFO] asyncapp-web ....................................... SUCCESS [  5.400 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12.597 s
[INFO] Finished at: 2017-02-10T22:32:43+09:00
[INFO] Final Memory: 38M/250M
[INFO] ------------------------------------------------------------------------
$
4.4.3.3.6. デプロイ

TomcatなどのWebコンテナを起動し、ビルドで生成されたwarファイルをデプロイする。 詳細な手順は割愛する。

4.4.3.4. REST Clientによるジョブの起動と実行結果確認

ここではREST クライアントとしてcurlコマンドを使用し、非同期ジョブを起動する。

$ curl -v \
  -H "Accept: application/json" -H "Content-type: application/json" \
  -d '{"jobParams": "param1=value1"}' \
  http://localhost:8080/asyncapp-web/api/v1/job/job01
* timeout on name lookup is not supported
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8088 (#0)
> POST /asyncapp-web/api/v1/job/job01 HTTP/1.1
> Host: localhost:8088
> User-Agent: curl/7.51.0
> Accept: application/json
> Content-type: application/json
> Content-Length: 30
>
* upload completely sent off: 30 out of 30 bytes
< HTTP/1.1 200
< X-Track: 0267db93977b4552880a4704cf3e4565
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 10 Feb 2017 13:55:46 GMT
<
{"jobName":"job01","jobParams":null,"jobExecutionId":3,"error":null,"errorMessag
e":null}* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
$

上記より、ジョブ実行ID:jobExecutionId = 3として、ジョブが実行されていることが確認できる。
続けてこのジョブ実行IDを使用し、ジョブの実行結果を取得する。

$ curl -v http://localhost:8080/asyncapp-web/api/v1/job/3
* timeout on name lookup is not supported
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8088 (#0)
> GET /asyncapp-web/api/v1/job/3 HTTP/1.1
> Host: localhost:8088
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 200
< X-Track: 7d94bf4d383745efb20cbf37cb6a8e13
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 10 Feb 2017 14:07:44 GMT
<
{"jobExecutionId":3,"jobName":"job01","stepExecutions":[{"stepExecutionId":5,"st
epName":"job01.step01","status":"COMPLETED","failureExceptions":[]}],"status":"C
OMPLETED","exitStatus":"exitCode=COMPLETED;exitDescription=","errorMessage":null
}* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
$

exitCode=COMPLETEDであることより、ジョブが正常終了していることが確認できる。

シェルスクリプトなどでcurlの実行結果を判定する場合

上記の例ではREST APIによる応答電文まで表示させている。 curlコマンドでHTTPステータスのみを確認する場合はcurl -s URL -o /dev/null -w "%{http_code}\n"とすることで、HTTPステータスが標準出力に表示される。
ただし、ジョブ実行IDはレスポンスボディ部のJSONを解析する必要があるため、必要に応じてREST クライアントアプリケーションを作成すること。

4.4.4. How to extend

4.4.4.1. ジョブの停止とリスタート

非同期ジョブの停止・リスタートは複数実行しているジョブの中から停止・リスタートする必要がある。 また、同名のジョブが並走している場合に、問題が発生しているジョブのみを対象にする必要もある。 よって、対象とするジョブ実行が特定でき、その状態が確認できる必要がある。
ここではこの前提を満たす場合、非同期実行の停止・リスタートを行うための実装について説明する。

以降、コントローラの実装JobControllerに対して、 ジョブの停止(stop)やリスタート(restart)を追加する方法について説明する。

ジョブの停止・リスタートはJobOperatorを用いた実装をしなくても実施できる。
詳細はジョブの管理を参照し、目的に合う方式を検討すること。
停止・リスタートの実装例
// asyncapp/asyncapp-web/src/main/java/org/terasoluna/batch/sample/app/api/JobController.java
package org.terasoluna.batch.sample.app.api;

// omitted

@RequestMapping("job")
@RestController
public class JobController {

    // omitted.

    @RequestMapping(value = "stop/{jobExecutionId}", method = RequestMethod.PUT)
    @Deprecated
    public ResponseEntity<JobOperationResource> stop(
            @PathVariable("jobExecutionId") Long jobExecutionId) {

      JobOperationResource responseResource = new JobOperationResource();
      responseResource.setJobExecutionId(jobExecutionId);
      boolean result = false;
      try {
          // (1)
          result = jobOperator.stop(jobExecutionId);
          if (!result) {
              responseResource.setErrorMessage("stop failed.");
              return ResponseEntity.badRequest().body(responseResource);
          }
          return ResponseEntity.ok().body(responseResource);
      } catch (NoSuchJobExecutionException | JobExecutionNotRunningException e) {
          responseResource.setError(e);
          return ResponseEntity.badRequest().body(responseResource);
      }
    }

    @RequestMapping(value = "restart/{jobExecutionId}",
                    method = RequestMethod.PUT)
    @Deprecated
    public ResponseEntity<JobOperationResource> restart(
            @PathVariable("jobExecutionId") Long jobExecutionId) {

        JobOperationResource responseResource = new JobOperationResource();
        responseResource.setJobExecutionId(jobExecutionId);
        try {
            // (2)
            Long id = jobOperator.restart(jobExecutionId);
            responseResource.setJobExecutionId(id);
            return ResponseEntity.ok().body(responseResource);
        } catch (JobInstanceAlreadyCompleteException |
                  NoSuchJobExecutionException | NoSuchJobException |
                  JobRestartException | JobParametersInvalidException e) {
            responseResource.setErrorMessage(e.getMessage());
            return ResponseEntity.badRequest().body(responseResource);
        }
    }

    // omitted.
}
コントローラによる停止・リスタート実装例
項番 説明

(1)

JobOperator#stop()を呼び出すことにより、実行中のジョブに対し停止を指示する。

(2)

JobOperator#restart()を呼び出すことにより、異常終了・停止したステップから再実行させる。

4.4.4.2. 複数起動

ここでの複数起動とは、Webコンテナを複数起動し、それぞれがジョブ要求を待ち受けることを指す。

非同期ジョブの実行管理は外部RDBMSによって行われるため、各アプリケーションの接続先となる 外部RDBMSを共有することで、同一筐体あるいは別筐体にまたがって非同期ジョブ起動を待ち受けることができる。

用途としては特定のジョブに対する負荷分散や冗長化などがあげられる。 しかし、Webアプリケーションの実装 で述べたように、Webコンテナを複数起動し並列性を高めるだけでこれらの効果が容易に得られるわけではない。 効果を得るためには、一般的なWebアプリケーションと同様の対処が求められる場合がある。 以下にその一例を示す。

  • Webアプリケーションの特性上、1リクエスト処理はステートレスに動作するが、 バッチの非同期実行はジョブの起動と結果の確認を合わせて設計しなければ、かえって障害耐性が低下する恐れもある。
    たとえば、ジョブ起動用Webコンテナを冗長化した場合でもクライアント側の障害によりジョブ起動後にジョブ実行IDを ロストすることでジョブの途中経過や結果の確認は困難となる。

  • 複数のWebコンテナにかかる負荷を分散させるために、クライアント側にリクエスト先を振り分ける機能を実装したり、 ロードバランサを導入したりする必要がある。

このように、複数起動の適性は一概に定めることができない。 そのため、目的と用途に応じてロードバランサの利用やWebクライアントによるリクエスト送信制御方式などを検討し、 非同期実行アプリケーションの性能や耐障害性を落とさない設計が必要となる。

4.5. リスナー

4.5.1. Overview

リスナーとは、ジョブやステップを実行する前後に処理を挿入するためのインターフェースである。

本機能は、チャンクモデルとタスクレットモデルとで使い方が異なるため、それぞれについて説明する。

リスナーには多くのインターフェースがあるため、それぞれの役割について説明する。 その後に、設定および実装方法について説明をする。

4.5.1.1. リスナーの種類

Spring Batchでは、実に多くのリスナーインターフェースが定義されている。 ここではそのすべてを説明するのではなく、利用頻度が高いものを中心に扱う。

まず、リスナーは2種類に大別される。

JobListener

ジョブの実行に対して処理を挟み込むためのインターフェース

StepListener

ステップの実行に対して処理を挟み込むためのインターフェース

JobListenerについて

Spring Batchには、 JobListener という名前のインターフェースは存在しない。 StepListenerとの対比のため 、本ガイドラインでは便宜的に定義している。
Java Batch(jBatch)には、javax.batch.api.listener.JobListenerというインターフェースが存在するので、実装時には間違えないように注意すること。 また、StepListenerもシグネチャが異なる同名インターフェース(javax.batch.api.listener.StepListener)が存在するので、同様に注意すること。

4.5.1.1.1. JobListener

JobListenerのインターフェースは、JobExecutionListenerの1つのみとなる。

JobExecutionListener

ジョブの開始前、終了後に処理を挟み込む。

JobExecutionListenerインターフェース
public interface JobExecutionListener {
  void beforeJob(JobExecution jobExecution);
  void afterJob(JobExecution jobExecution);
}
4.5.1.1.2. StepListener

StepListenerのインターフェースは以下のように多くの種類がある。

StepListener

以降に紹介する各種リスナーのマーカーインターフェース。

StepExecutionListener

ステップ実行の開始前、終了後に処理を挟み込む。

StepExecutionListenerインターフェース
public interface StepExecutionListener extends StepListener {
  void beforeStep(StepExecution stepExecution);
  ExitStatus afterStep(StepExecution stepExecution);
}
ChunkListener

1つのチャンクを処理する前後と、エラーが発生した場合に処理を挟み込む。

ChunkListenerインターフェース
public interface ChunkListener extends StepListener {
  static final String ROLLBACK_EXCEPTION_KEY = "sb_rollback_exception";
  void beforeChunk(ChunkContext context);
  void afterChunk(ChunkContext context);
  void afterChunkError(ChunkContext context);
}
ROLLBACK_EXCEPTION_KEYの用途

afterChunkErrorメソッドにて、発生した例外を取得したい場合に利用する。 Spring Batchはチャンク処理中にエラーが発生した場合、ChunkContextsb_rollback_exceptionというキー名で 例外を格納した上でChunkListenerを呼び出すため、以下の要領でアクセスできる。

使用例
public void afterChunkError(ChunkContext context) {
    logger.error("Exception occurred while chunk. [context:{}]", context,
            context.getAttribute(ChunkListener.ROLLBACK_EXCEPTION_KEY));
}
ItemReadListener

ItemReaderが1件のデータを取得する前後と、エラーが発生した場合に処理を挟み込む。

ItemReadListenerインターフェース
public interface ItemReadListener<T> extends StepListener {
  void beforeRead();
  void afterRead(T item);
  void onReadError(Exception ex);
}
ItemProcessListener

ItemProcessorが1件のデータを加工する前後と、エラーが発生した場合に処理を挟み込む。

ItemProcessListenerインターフェース
public interface ItemProcessListener<T, S> extends StepListener {
  void beforeProcess(T item);
  void afterProcess(T item, S result);
  void onProcessError(T item, Exception e);
}
ItemWriteListener

ItemWriterが1つのチャンクを出力する前後と、エラーが発生した場合に処理を挟み込む。

ItemWriteListenerインターフェース
public interface ItemWriteListener<S> extends StepListener {
  void beforeWrite(List<? extends S> items);
  void afterWrite(List<? extends S> items);
  void onWriteError(Exception exception, List<? extends S> items);
}

本ガイドラインでは、以下のリスナーについては説明をしない。

  • リトライ系リスナー

  • スキップ系リスナー

これらのリスナーは例外ハンドリングでの使用を想定したものであるが、 本ガイドラインではこれらのリスナーを用いた例外ハンドリングは行わない方針である。 詳細は、例外ハンドリングを参照。

4.5.2. How to use

リスナーの実装と設定方法について説明する。

4.5.2.1. リスナーの実装

リスナーの実装と設定方法について説明する。

  1. リスナーインターフェースをimplementsして実装する。

  2. コンポーネントにメソッドベースでアノテーションを付与して実装する。

どちらで実装するかは、リスナーの役割に応じて選択する。基準は後述する。

4.5.2.1.1. インターフェースを実装する場合

各種リスナーインターフェースをimplementsして実装する。必要に応じて、複数のインターフェースを同時に実装してもよい。 以下に実装例を示す。

JobExecutionListenerの実装例
@Component
public class JobExecutionLoggingListener implements JobExecutionListener { // (1)

    private static final Logger logger =
            LoggerFactory.getLogger(JobExecutionLoggingListener.class);

    @Override
    public void beforeJob(JobExecution jobExecution) { // (2)
        // do nothing.
    }

    @Override
    public void afterJob(JobExecution jobExecution) { // (3)

        logger.info("job finished.[JobName:{}][ExitStatus:{}]",
                jobExecution.getJobInstance().getJobName(),
                jobExecution.getExitStatus().getExitCode());  // (4)

        // per step execution
        // (5)
        jobExecution.getStepExecutions().forEach(stepExecution -> {
            Object errorItem = stepExecution.getExecutionContext().get("ERROR_ITEM");
            if (errorItem != null) {
                logger.error("detected error on this item processing. " +
                        "[step:{}] [item:{}]", stepExecution.getStepName(),
                        errorItem);
            }
        });
    }
}
リスナーの設定例
<batch:job id="chunkJobWithListener" job-repository="jobRepository">
     <batch:step id="chunkJobWithListener.step01">
         <batch:tasklet transaction-manager="jobTransactionManager">
             <batch:chunk reader="reader" processor="processor"
                          writer="writer" commit-interval="10"/>
             <batch:listeners>
                 <batch:listener ref="loggingEachProcessInStepListener"/>
             </batch:listeners>
         </batch:tasklet>
     </batch:step>
     <batch:listeners>
         <batch:listener ref="jobExecutionLoggingListener"/> <!-- (6) -->
     </batch:listeners>
 </batch:job>
説明
項番 説明

(1)

JobExecutionListenerimplementsして実装する。

(2)

JobExecutionListenerが定義しているbeforeJobメソッドを実装する。
この例では、ジョブ開始前には何もしない。

(3)

JobExecutionListenerが定義しているafteJobメソッドを実装する。
この例では、ジョブ終了時にジョブの最終状態と例外情報をログ出力する。

(4)

ジョブ名と終了コードをINFOログ出力する。必要な情報は、引数のJobExecutionからそれぞれ取得する。

(5)

ステップごとに発生した例外をログ出力する。引数のJobExecutionから紐づくStepExecutionを取得し実現する。
ここでは、例外の起因となった入力データをExecutionContextからERROR_ITEMというキーで取得している。 ExecutionContextに設定する例はジョブ単位の例外ハンドリングを参照。

(6)

Bean定義の<listeners>タグで、(1)で実装したリスナーを設定する。
設定方法の詳細は、リスナーの設定で説明する。

リスナーのサポートクラス

複数のリスナーインターフェースをimplementsした場合、処理が不要な部分についても空実装をする必要がある。 この作業を簡略化するため、あらかじめ空実装を施したサポートクラスがSpring Batchには用意されている。 インターフェースではなく、サポートクラスを活用してもよいが、その場合implementsではなくextendsになるため注意すること。

サポートクラス
  • org.springframework.batch.core.listener.ItemListenerSupport

  • org.springframework.batch.core.listener.StepListenerSupport

4.5.2.1.2. アノテーションを付与する場合

各種リスナーインターフェースに対応したアノテーションを付与する。必要に応じて、複数のアノテーションを同時に実装してもよい。

リスナーインターフェースとの対応表
リスナーインターフェース アノテーション

JobExecutionListener

@beforeJob
@afterJob

StepExecutionListener

@BeforeStep
@AfterStep

ChunkListener

@BeforeChunk
@AfterChunk
@afterChunkError

ItemReadListener

@BeforeRead
@AfterRead
@OnReadError

ItemProcessListener

@beforeProcess
@afterProcess
@onProcessError

ItemWriteListener

@BeforeWrite
@AfterWrite
@OnWriteError

これらアノテーションはコンポーネント化された実装のメソッドに付与することで目的のスコープで動作する。 以下に実装例を示す。

アノテーションを付与したItemProcessorの実装例
@Component
public class AnnotationAmountCheckProcessor implements
        ItemProcessor<SalesPlanDetail, SalesPlanDetail> {

    private static final Logger logger =
            LoggerFactory.getLogger(AnnotationAmountCheckProcessor.class);

    @Override
    public SalesPlanDetail process(SalesPlanDetail item) throws Exception {
        if (item.getAmount().signum() == -1) {
            throw new IllegalArgumentException("amount is negative.");
        }
        return item;
    }

    // (1)
    /*
    @BeforeProcess
    public void beforeProcess(Object item) {
        logger.info("before process. [Item :{}]", item);
    }
    */

    // (2)
    @AfterProcess
    public void afterProcess(Object item, Object result) {
        logger.info("after process. [Result :{}]", result);
    }

    // (3)
    @OnProcessError
    public void onProcessError(Object item, Exception e) {
        logger.error("on process error.", e);
    }
}
リスナーの設定例
<batch:job id="chunkJobWithListenerAnnotation" job-repository="jobRepository">
    <batch:step id="chunkJobWithListenerAnnotation.step01">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="reader"
                         processor="annotationAmountCheckProcessor"
                         writer="writer" commit-interval="10"/>  <! -- (4) -->
        </batch:tasklet>
    </batch:step>
</batch:job>
説明
項番 説明

(1)

アノテーションで実装する場合は、処理が必要なタイミングのアノテーションのみを付与すればよい。
この例では、ItemProcessの処理前には何もする必要がないため、@beforeProcessを付与した実装は不要となる。

(2)

ItemProcessの処理後に行う処理を実装する。
この例では処理結果をログを出力している。

(3)

ItemProcessでエラーが発生したときの処理を実装する。
この例では発生した例外をログを出力している。

(4)

アノテーションでリスナー実装がされているItemProcessを<chunk>タグに設定する。
リスナーインターフェースとは異なり、<listener>タグで設定しなくても、自動的にリスナーが登録される。

アノテーションを付与するメソッドの制約

アノテーションを付与するメソッドはどのようなメソッドでもよいわけではない。 対応するリスナーインターフェースのメソッドと、シグネチャを一致させる必要がある。 この点は、各アノテーションのjavadocに明記されている。

JobExecutionListenerをアノテーションで実装したときの注意

JobExecutionListenerは、他のリスナーとスコープが異なるため、上記の設定では自動的にリスナー登録がされない。 そのため、<listener>タグで明示的に設定する必要がある。詳細は、リスナーの設定を参照。

Tasklet実装へのアノテーションによるリスナー実装

Tasklet実装へのアノテーションによるリスナー実装した場合、以下の設定では一切リスナーが起動しないため注意する。

Taskletの場合
<batch:job id="taskletJobWithListenerAnnotation" job-repository="jobRepository">
    <batch:step id="taskletJobWithListenerAnnotation.step01">
        <batch:tasklet transaction-manager="jobTransactionManager"
                       ref="annotationSalesPlanDetailRegisterTasklet"/>
    </batch:step>
</batch:job>

タスクレットモデルの場合は、インターフェースとアノテーションの使い分けに従ってリスナーインターフェースを利用するのがよい。

4.5.2.2. リスナーの設定

リスナーは、Bean定義の<listeners>.<listener>タグによって設定する。 XMLスキーマ定義では様々な箇所に記述できるが、インターフェースの種類によっては意図とおり動作しないものが存在するため、 以下の位置に設定すること。

リスナーを設定する位置
<!-- for chunk mode -->
<batch:job id="chunkJob" job-repository="jobRepository">
    <batch:step id="chunkJob.step01">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="(1)"
                         processor="(1)"
                         writer="(1)" commit-interval="10"/>
            <batch:listeners>
                <batch:listener ref="(2)"/>
            </batch:listeners>
        </batch:tasklet>
    </batch:step>
    <batch:listeners>
        <batch:listener ref="(3)"/>
    </batch:listeners>
</batch:job>

<!-- for tasklet mode -->
<batch:job id="taskletJob" job-repository="jobRepository">
    <batch:step id="taskletJob.step01">
        <batch:tasklet transaction-manager="jobTransactionManager" ref="tasklet">
            <batch:listeners>
                <batch:listener ref="(2)"/>
            </batch:listeners>
        </batch:tasklet>
    </batch:step>
    <batch:listeners>
        <batch:listener ref="(3)"/>
    </batch:listeners>
</batch:job>
設定値の説明
項番 説明

(1)

StepListenerに属するアノテーションによる実装を含んだコンポーネントを設定する。
アノテーションの場合、必然的にこの場所に設定することになる。

(2)

StepListenerに属するリスナーインターフェース実装を設定する。

(3)

JobListenerに属するリスナーを設定する。
インターフェースとアノテーション、どちらの実装でもここに設定する必要がある。

4.5.2.2.1. 複数リスナーの設定

<batch:listeners>タグには複数のリスナーを設定することができる。

複数のリスナーを登録したときに、リスナーがどのような順番で起動されるかを以下に示す。

  • ItemProcessListener実装

    • listenerA, listenerB

  • JobExecutionListener実装

    • listenerC, listenerD

複数リスナーの設定例
<batch:job id="chunkJob" job-repository="jobRepository">
    <batch:step id="chunkJob.step01">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="reader"
                         processor="pocessor"
                         writer="writer" commit-interval="10"/>
            <batch:listeners>
                <batch:listener ref="listenerA"/>
                <batch:listener ref="listenerB"/>
            </batch:listeners>
        </batch:tasklet>
    </batch:step>
    <batch:listeners>
        <batch:listener ref="listenerC"/>
        <batch:listener ref="listenerD"/>
    </batch:listeners>
</batch:job>
Listener execution order
リスナーの起動順序
  • 前処理に該当する処理は、リスナーの登録順に起動される。

  • 後処理またはエラー処理に該当する処理は、リスナー登録の逆順に起動される。

4.5.2.3. インターフェースとアノテーションの使い分け

リスナーインターフェースとアノテーションによるリスナーの使い分けを説明する。

リスナーインターフェース

job、step、chunkにおいて共通する横断的な処理の場合に利用する。

アノテーション

ビジネスロジック固有の処理を行いたい場合に利用する。
原則として、ItemProcessorに対してのみ実装する。

5. データの入出力

5.1. トランザクション制御

5.1.1. Overview

本節では、ジョブにおけるトランザクション制御について以下の順序で説明する。

本機能は、チャンクモデルとタスクレットモデルとで使い方が異なるため、それぞれについて説明する。

5.1.1.1. 一般的なバッチ処理におけるトランザクション制御のパターンについて

一般的に、バッチ処理は大量件数を処理するため、処理の終盤で何かしらのエラーが発生した場合に全件処理しなおしとなってしまうと バッチシステムのスケジュールに悪影響を与えてしまう。
これを避けるために、1ジョブの処理内で一定件数ごとにトランザクションを確定しながら処理を進めていくことで、 エラー発生時の影響を局所化することが多い。
(以降、一定件数ごとにトランザクションを確定する方式を「中間コミット方式」、コミット単位にデータをひとまとめにしたものを「チャンク」と呼ぶ。)

中間コミット方式のポイントを以下にまとめる。

  1. エラー発生時の影響を局所化する。

    • 更新時にエラーが発生しても、エラー箇所直前のチャンクまで更新が確定している。

  2. リソースを一定量しか使わない。

    • 処理対象データの大小問わず、チャンク分のリソースしか使用しないため安定する。

ただし、中間コミット方式があらゆる場面で有効な方法というわけではない。
システム内に一時的とはいえ処理済みデータと未処理データが混在することになる。 その結果、リカバリ処理時に未処理データを識別することが必要となるため、リカバリが複雑になる可能性がある。 これを避けるには、中間コミット方式ではなく、全件を1トランザクションで確定させるしかない。
(以降、全件を1トランザクションで確定する方式を「一括コミット方式」と呼ぶ。)

とはいえ、何千万件というような大量件数を一括コミット方式で処理してしまうと、 コミットを行った際に全件をデータベース反映しようとして高負荷をかけてしまうような事態が発生する。 そのため、一括コミット方式は小規模なバッチ処理には向いているが、大規模バッチで採用するには注意が必要となる。 よって、この方法も万能な方法というわけではない。

つまり、「影響の局所化」と「リカバリの容易さ」はトレードオフの関係にある。 「中間コミット方式」と「一括コミット方式」のどちらを使うかは、ジョブの性質に応じてどちらを優先すべきかを決定して欲しい。
もちろん、バッチシステム内のジョブすべてをどちらか一方で実現する必要はない。 基本的には「中間コミット方式」を採用するが、特殊なジョブのみ「一括コミット方式」を採用する(または、その逆とする)ことは自然である。

以下に、「中間コミット方式」と「一括コミット方式」のメリット・デメリット、採用ポイントをまとめる。

方式別特徴一覧
コミット方式 メリット デメリット 採用ポイント

中間コミット方式

エラー発生時の影響を局所化する

リカバリ処理が複雑になる可能性がある

大量データを一定のマシンリソースで処理したい場合

一括コミット方式

データの整合性を担保する

大量件数処理時に高負荷になる可能性がある

永続化リソースに対する処理結果をAll or Nothingとしたい場合
小規模のバッチ処理に向いている

データベースの同一テーブルへ入出力する際の注意点

データベースの仕組み上、コミット方式を問わず、 同一テーブルへ入出力する処理で大量データを取り扱う際に注意が必要な点がある。

  • 読み取り一貫性を担保するための情報が出力(UPDATEの発行)により失われた結果、 入力(SELECT)にてエラーが発生することがある。

これを回避するには、以下の対策がある。

  • 情報を確保する領域を大きくする。

    • 拡張する際には、リソース設計にて十分検討の上実施してほしい。

    • 拡張方法は使用するデータベースに依存するため、マニュアルを参照すること。

  • 入力データを分割し多重処理を行う。

5.1.2. Architecture

5.1.2.1. Spring Batchにおけるトランザクション制御

ジョブのトランザクション制御はSpring Batchがもつ仕組みを活用する。

以下に2種類のトランザクションを定義する。

フレームワークトランザクション

Spring Batchが制御するトランザクション

ユーザトランザクション

ユーザが制御するトランザクション

5.1.2.1.1. チャンクモデルにおけるトランザクション制御の仕組み

チャンクモデルにおけるトランザクション制御は、中間コミット方式のみとなる。 一括コミット方式は実現できない。

チャンクモデルにおける一括コミット方式についてはJIRAにレポートされている。
https://jira.spring.io/browse/BATCH-647
結果、chunk completion policyをカスタマイズしてチャンクサイズを動的に変更することで解決している。 しかし、この方法では全データを1チャンクに格納してしまいメモリを圧迫してしまうため、方式として採用することはできない。

この方式の特徴は、チャンク単位にトランザクションが繰り返し行われることである。

正常系でのトランザクション制御

正常系でのトランザクション制御を説明する。

Transaction Control Chunk Model Normal Process
正常系のシーケンス図
シーケンス図の説明
  1. ジョブからステップが実行される。

    • 入力データがなくなるまで、以降の処理を繰り返す。

    • チャンク単位で、フレームワークトランザクションを開始する。

    • チャンクサイズに達するまで2から5までの処理を繰り返す。

  2. ステップは、ItemReaderから入力データを取得する。

  3. ItemReaderは、ステップに入力データを返却する。

  4. ステップは、ItemProcessorで入力データに対して処理を行う。

  5. ItemProcessorは、ステップに処理結果を返却する。

  6. ステップはチャンクサイズ分のデータをItemWriterで出力する。

  7. ItemWriterは、対象となるリソースへ出力を行う。

  8. ステップはフレームワークトランザクションをコミットする。

異常系でのトランザクション制御

異常系でのトランザクション制御を説明する。

Transaction Control Chunk Model Abnormal Process
異常系のシーケンス図
シーケンス図の説明
  1. ジョブからステップが実行される。

    • 入力データがなくなるまで以降の処理を繰り返す。

    • チャンク単位でのフレームワークトランザクションを開始する。

    • チャンクサイズに達するまで2から5までの処理を繰り返す。

  2. ステップは、ItemReaderから入力データを取得する。

  3. ItemReaderは、ステップに入力データを返却する。

  4. ステップは、ItemProcessorで入力データに対して処理を行う。

  5. ItemProcessorは、ステップに処理結果を返却する。

  6. ステップはチャンクサイズ分のデータをItemWriterで出力する。

  7. ItemWriterは、対象となるリソースへ出力を行う。

2から7までの処理過程で 例外が発生する と、

  1. ステップはフレームワークトランザクションをロールバックする。

5.1.2.1.2. タスクレットモデルにおけるトランザクション制御の仕組み

タスクレットモデルにおけるトランザクション制御は、 一括コミット方式と中間コミット方式のいずれかを利用できる。

一括コミット方式

Spring Batchがもつトランザクション制御の仕組みを利用する

中間コミット方式

ユーザにてトランザクションを直接操作する

タスクレットモデルにおける一括コミット方式

Spring Batchがもつトランザクション制御の仕組みについて説明する。

この方式の特徴は、1つのトランザクション内で繰り返しデータ処理を行うことである。

正常系でのトランザクション制御

正常系でのトランザクション制御を説明する。

Single Transaction Control Tasklet Model Normal Process
正常系のシーケンス図
シーケンス図の説明
  1. ジョブからステップが実行される。

    • ステップはフレームワークトランザクションを開始する。

  2. ステップはタスクレットを実行する。

    • 入力データがなくなるまで3から7までの処理を繰り返す。

  3. タスクレットは、Repositoryから入力データを取得する。

  4. Repositoryは、タスクレットに入力データを返却する。

  5. タスクレットは、入力データを処理する。

  6. タスクレットは、Repositoryへ出力データを渡す。

  7. Repositoryは、対象となるリソースへ出力を行う。

  8. タスクレットはステップへ処理終了を返却する。

  9. ステップはフレームワークトランザクションをコミットする。

異常系でのトランザクション制御

異常系でのトランザクション制御を説明する。

Single Transaction Control Tasklet Model Abormal Process
異常系のシーケンス図
シーケンス図の説明
  1. ジョブからステップが実行される。

    • ステップはフレームワークトランザクションを開始する。

  2. ステップはタスクレットを実行する。

    • 入力データがなくなるまで3から7までの処理を繰り返す。

  3. タスクレットは、Repositoryから入力データを取得する。

  4. Repositoryは、タスクレットに入力データを返却する。

  5. タスクレットは、入力データを処理する。

  6. タスクレットは、Repositoryへ出力データを渡す。

  7. Repositoryは、対象となるリソースへ出力を行う。

2から7までの処理過程で 例外が発生する と、

  1. タスクレットはステップへ例外をスローする。

  2. ステップはフレームワークトランザクションをロールバックする。

タスクレットモデルにおける中間コミット方式

ユーザにてトランザクションを直接操作する仕組みについて説明する。

この方式の特徴は、フレームワークトランザクション内で新規のユーザトランザクションを開始して操作することである。

正常系でのトランザクション制御

正常系でのトランザクション制御を説明する。

Chunk Transaction Control Tasklet Model Normal Process
正常系のシーケンス図
シーケンス図の説明
  1. ジョブからステップが実行される。

    • ステップは フレームワークトランザクション を開始する。

  2. ステップはタスクレットを実行する。

    • 入力データがなくなるまで3から10までの処理を繰り返す。

  3. タスクレットは、TransacitonManagerより ユーザトランザクション を開始する。

    • フレームワークのトランザクションと分離させるため、 REQUIRES_NEW でユーザトランザクションを実行する。

    • チャンクサイズに達するまで4から6までの処理を繰り返す。

  4. タスクレットは、Repositoryから入力データを取得する。

  5. Repositoryは、タスクレットに入力データを返却する。

  6. タスクレットは、入力データを処理する。

  7. タスクレットは、Repositoryへ出力データを渡す。

  8. Repositoryは、対象となるリソースへ出力を行う。

  9. タスクレットは、TransacitonManagerにより ユーザトランザクション のコミットを実行する。

  10. TransacitonManagerは、対象となるリソースへコミットを発行する。

  11. タスクレットはステップへ処理終了を返却する。

  12. ステップは フレームワークトランザクション をコミットする。

ここでは1件ごとにリソースへ出力しているが、 チャンクモデルと同様に、チャンク単位で一括更新し処理スループットの向上を狙うことも可能である。 その際に、SqlSessionTemplateexecutorTypeBATCHに設定することで、BatchUpdateを利用することもできる。 これは、MyBatisのItemWriterを利用する場合と同様の動作になるため、MyBatisのItemWriterを利用して更新してもよい。 MyBatisのItemWriterについて、詳細は ItemWriterにおけるデータベースアクセスを参照。

異常系でのトランザクション制御

異常系でのトランザクション制御を説明する。

Chunk Transaction Control Tasklet Model Abormal Process
異常系のシーケンス図
シーケンス図の説明
  1. ジョブからステップが実行される。

    • ステップは フレームワークトランザクション を開始する。

  2. ステップはタスクレットを実行する。

    • 入力データがなくなるまで3から11までの処理を繰り返す。

  3. タスクレットは、TransacitonManagerより ユーザトランザクション を開始する。

    • フレームワークのトランザクションと分離させるため、 REQUIRES_NEW でユーザトランザクションを実行する。

    • チャンクサイズに達するまで4から6までの処理を繰り返す。

  4. タスクレットは、Repositoryから入力データを取得する。

  5. Repositoryは、タスクレットに入力データを返却する。

  6. タスクレットは、入力データを処理する。

  7. タスクレットは、Repositoryへ出力データを渡す。

  8. Repositoryは、対象となるリソースへ出力を行う。

3から8までの処理過程で 例外が発生する と、

  1. タスクレットは、発生した例外に対する処理を行う。

  2. タスクレットは、TransacitonManagerにより ユーザトランザクション のロールバックを実行する。

  3. TransacitonManagerは、対象となるリソースへロールバックを発行する。

  4. タスクレットはステップへ例外をスローする。

  5. ステップは フレームワークトランザクション をロールバックする。

処理の継続について

ここでは、例外をハンドリングして処理をロールバック後、処理を異常終了しているが、 継続して次のチャンクを処理することも可能である。 いずれの場合も、途中でエラーが発生したことをステップのステータス・終了コードを変更することで後続の処理に通知する必要がある。

フレームワークトランザクションについて

ここでは、ユーザトランザクションをロールバック後に例外をスローしてジョブを異常終了させているが、 ステップへ処理終了を返却しジョブを正常終了させることも出来る。 この場合、フレームワークトランザクションは、 コミット される。

5.1.2.1.3. モデル別トランザクション制御の選定方針

TERASOLUNA Batch 5.xの基盤となるSpring Batchでは、チャンクモデルでは中間コミット方式しか実現できない。 しかし、タスクレットモデルでは、中間コミット方式、一括コミット方式のいずれも実現できる。

よって、TERASOLUNA Batch 5.xでは、一括コミット方式が必要な際は、タスクレットモデルにて実装する。

5.1.2.2. 起動方式ごとのトランザクション制御の差

起動方式によってはジョブの起動前後にSpring Batchの管理外となるトランザクションが発生する。 ここでは、2つの非同期実行処理方式におけるトランザクションについて説明する。

5.1.2.2.1. DBポーリングのトランザクションについて

DBポーリングが行うジョブ要求テーブルへの処理については、Spring Batch管理外のトランザクション処理が行われる。 また、ジョブで発生した例外については、ジョブ内で対応が完結するため、JobRequestPollTaskが行うトランザクションには影響を与えない。

下図にトランザクションに焦点を当てた簡易的なシーケンス図を示す。

With Database polling transaction
DBポーリング処理のトランザクション
シーケンス図の説明
  1. 非同期バッチデーモンでJobRequestPollTaskが周期実行される。

  2. JobRequestPollTaskは、Spring Batch管理外のトランザクションを開始する。

  3. JobRequestPollTaskは、ジョブ要求テーブルから非同期実行対象ジョブを取得する。

  4. JobRequestPollTaskは、Spring Batch管理外のトランザクションをコミットする。

  5. JobRequestPollTaskは、Spring Batch管理外のトランザクションを開始する。

  6. JobRequestPollTaskは、ジョブ要求テーブルのポーリングステータスをINITからPOLLEDへ更新する。

  7. JobRequestPollTaskは、Spring Batch管理外のトランザクションをコミットする。

  8. JobRequestPollTaskは、ジョブを実行する。

  9. ジョブ内では、管理用DB(JobRepository)へのトランザクション管理はSpring Batchが行う。

  10. ジョブ内では、ジョブ用DBへのトランザクション管理はSpring Batchが行う。

  11. JobRequestPollTaskにjob_execution_idが返却される

  12. JobRequestPollTaskは、Spring Batch管理外のトランザクションを開始する。

  13. JobRequestPollTaskは、ジョブ要求テーブルのポーリングステータスをPOLLEDからEXECUTEへ更新する。

  14. JobRequestPollTaskは、Spring Batch管理外のトランザクションをコミットする。

SELECT発行時のコミットについて

データベースによっては、SELECT発行時に暗黙的にトランザクションを開始する場合がある。 そのため、明示的にコミットを発行することでトランザクションを確定させ、他のトランザクションと明確に区別し影響を与えないようにしている。

5.1.2.2.2. WebAPサーバ処理のトランザクションについて

WebAPが対象とするリソースへの処理については、Spring Batch管理外のトランザクション処理が行われる。 また、ジョブで発生した例外については、ジョブ内で対応が完結するため、WebAPが行うトランザクションには影響を与えない。

下図にトランザクションに焦点を当てた簡易的なシーケンス図を示す。

With Web Application transaction
WebAPサーバ処理のトランザクション
シーケンス図の説明
  1. クライアントからリクエストによりWebAPの処理が実行される

  2. WebAPは、Spring Batch管理外のトランザクションを開始する。

  3. WebAPは、ジョブ実行前にWebAPでのリソースに対して読み書きを行う。

  4. WebAPは、ジョブを実行する。

  5. ジョブ内では、管理用DB(JobRepository)へのトランザクション管理はSpring Batchが行う。

  6. ジョブ内では、ジョブ用DBへのトランザクション管理はSpring Batchが行う。

  7. WebAPにjob_execution_idが返却される

  8. WebAPは、ジョブ実行後にWebAPでのリソースに対して読み書きを行う。

  9. WebAPは、Spring Batch管理外のトランザクションをコミットする。

  10. WebAPは、クライアントにレスポンスを返す。

5.1.3. How to use

ここでは、1ジョブにおけるトランザクション制御について、以下の場合に分けて説明する。

データソースとは、データの格納先(データベース、ファイル等)を指す。 単一データソースとは1つのデータソースを、複数データソースとは2つ以上のデータソースを指す。

単一データソースを処理するケースは、データベースのデータを加工するケースが代表的である。
複数データソースを処理するケースは、以下のようにいくつかバリエーションがある。

  • 複数のデータベースの場合

  • データベースとファイルの場合

5.1.3.1. 単一データソースの場合

1つのデータソースに対して入出力するジョブのトランザクション制御について説明する。

以下にTERASOLUNA Batch 5.xでの設定例を示す。

データソースの設定(META-INF/spring/launch-context.xml)
<!-- Job-common definitions -->
<bean id="jobDataSource" class="org.apache.commons.dbcp2.BasicDataSource"
      destroy-method="close"
      p:driverClassName="${jdbc.driver}"
      p:url="${jdbc.url}"
      p:username="${jdbc.username}"
      p:password="${jdbc.password}"
      p:maxTotal="10"
      p:minIdle="1"
      p:maxWaitMillis="5000"
      p:defaultAutoCommit="false" />
トランザクションマネージャの設定(META-INF/spring/launch-context.xml)
<!-- (1) -->
<bean id="jobTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
      p:dataSource-ref="jobDataSource"
      p:rollbackOnCommitFailure="true" />
項番 説明

(1)

トランザクションマネージャのBean定義
データソースは上記で定義したjobDataSourceを設定する。
コミットに失敗した場合はロールバックをするように設定済み。

5.1.3.1.1. トランザクション制御の実施

ジョブモデルおよびコミット方式により制御方法が異なる。

チャンクモデルの場合

チャンクモデルの場合は、中間コミット方式となり、Spring Batchにトランザクション制御を委ねる。 ユーザにて制御することは一切行わないようにする。

設定例(ジョブ定義)
<batch:job id="jobSalesPlan01" job-repository="jobRepository">
    <batch:step id="jobSalesPlan01.step01">
        <batch:tasklet transaction-manager="jobTransactionManager"> <!-- (1) -->
            <batch:chunk reader="detailCSVReader"
                         writer="detailWriter"
                         commit-interval="10" /> <!-- (2) -->
        </batch:tasklet>
    </batch:step>
</batch:job>
項番 説明

(1)

<batch:tasklet>タグのtransaction-manager属性に定義済みのjobTransactionManagerを設定する。
ここに設定したトランザクションマネージャでチャンクコミット方式のトランザクションを制御する。

(2)

commit-interval属性にチャンクサイズを設定する。この例では10件処理するごとに1回コミットが発行される。

タスクレットモデルの場合

タスクレットモデルの場合は、一括コミット方式、中間コミット方式でトランザクション制御の方法が異なる。

一括コミット方式

Spring Batchにトランザクション制御を委ねる。

設定例(ジョブ定義)
<batch:job id="jobSalesPlan01" job-repository="jobRepository">
    <batch:step id="jobSalesPlan01.step01">
        <!-- (1) -->
        <batch:tasklet transaction-manager="jobTransactionManager"
                       ref="salesPlanSingleTranTask" />
    </batch:step>
</batch:job>
項番 説明

(1)

<batch:tasklet>タグのtransaction-manager属性に定義済みのjobTransactionManagerを設定する。
ここに設定したトランザクションマネージャで一括コミット方式のトランザクションを制御する。

中間コミット方式

ユーザにてトランザクション制御を行う。

  • 処理の途中でコミットを発行する場合は、TransacitonManagerをInjectして手動で行う。

設定例(ジョブ定義)
<batch:job id="jobSalesPlan01" job-repository="jobRepository">
    <batch:step id="jobSalesPlan01.step01">
        <!-- (1) -->
        <batch:tasklet transaction-manager="jobTransactionManager"
                       ref="salesPlanChunkTranTask" />
    </batch:step>
</batch:job>
実装例
@Component()
public class SalesPlanChunkTranTask implements Tasklet {

    @Inject
    ItemStreamReader<SalesPlanDetail> itemReader;

     // (2)
    @Inject
    @Named("jobTransactionManager")
    PlatformTransactionManager transactionManager;

    @Inject
    SalesPlanDetailRepository repository;

    private static final int CHUNK_SIZE = 10;

    @Override
    public RepeatStatus execute(StepContribution contribution,
                                ChunkContext chunkContext) throws Exception {

        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition
                .PROPAGATION_REQUIRES_NEW);  // (3)
        TransactionStatus status = null;

        try {
            // omitted

            itemReader.open(executionContext);

            while ((item = itemReader.read()) != null) {

                if (count % CHUNK_SIZE == 0) {
                    status = transactionManager.getTransaction(definition); // (4)
                }
                count++;

                // omitted

                repository.create(item);
                if (count % CHUNK_SIZE == 0) {
                    transactionManager.commit(status);  // (5)
                }
            }
        } catch (Exception e) {
            logger.error("Exception occurred while reading.", e);
            transactionManager.rollback(status);    // (6)
            throw e;
        } finally {
            if (!status.isCompleted()) {
                transactionManager.commit(status);   // (7)
            }
            itemReader.close();
        }

        return RepeatStatus.FINISHED;
    }
}
項番 説明

(1)

<batch:tasklet>タグのtransaction-manager属性に定義済みのjobTransactionManagerを設定する。
1つのトランザクションマネージャをフレームワーク/ユーザの両方で利用するが、以降の要領により独立して扱うことが出来る。

(2)

トランザクションマネージャをInjectする。
@NamedアノテーションでjobTransactionManagerを指定して利用するBeanを特定させる。

(3)

フレームワークのトランザクションとは分離させるため、PROPAGATION_REQUIRES_NEWを指定する。

(4)

チャンクの開始時にトランザクションを開始する。

(5)

チャンク終了時にトランザクションをコミットする。

(6)

例外発生時にはトランザクションをロールバックする。

(7)

最後のチャンクについて、トランザクションをコミットする。

TransacitonManagerのPropagationについて

タスクレットモデルでは、Spring Batchが制御しているトランザクション内で新たにトランザクション制御を行う。 そのため、InjectするTransacitonManagerのPropagationを REQUIRES_NEW にする必要がある。

ItemWriterによる更新

上記の例では、Repositoryを使用しているが、ItemWriterを利用してデータを更新することもできる。 ItemWriterを利用することで実装がシンプルになる効果があり、特にファイルを更新する場合はFlatFileItemWriterを利用するとよい。

5.1.3.1.2. 非トランザクショナルなデータソースに対する補足

ファイルの場合はトランザクションの設定や操作は不要である。

FlatFileItemWriterを利用する場合、擬似的なトランザクション制御が行える。 これは、リソースへの書き込みを遅延し、コミットタイミングで実際に書き出すことで実現している。 正常時にはチャンクサイズに達したときに、実際のファイルにチャンク分データを出力し、例外が発生するとそのチャンクのデータ出力が行われない。

FlatFileItemWriterは、transactionalプロパティでトランザクション制御の有無を切替えられる。デフォルトはtrueでトランザクション制御が有効になっている。 transactionalプロパティがfalseの場合、FlatFileItemWriterは、トランザクションとは無関係にデータの出力を行う。

一括コミット方式を採用する場合、transactionalプロパティをfalseにすることを推奨する。 上記の説明にあるとおりコミットのタイミングでリソースへ書き出すため、それまではメモリ内に全出力分のデータを保持することになる。 そのため、データ量が多い場合にはメモリ不足になりエラーとなる可能性が高くなるためである。

ファイルしか扱わないジョブにおけるTransacitonManagerの設定について

以下に示すジョブ定義のように、batch:tasklettransaction-manager属性はxsdスキーマにおいて必須のため省略できない。

TransacitonManager設定箇所の抜粋
<batch:tasklet transaction-manager="jobTransactionManager">
<batch:chunk reader="reader" writer="writer" commit-interval="100" />
</batch:tasklet>

そのため、jobTransactionManagerを常に指定すること。この時、以下の挙動となる。

  • transactionalがtrueの場合

    • 指定したTransacitonManagerに同期してリソースに出力する。

  • transactionalがfalseの場合

    • 指定したTransacitonManagerのトランザクション処理は空振りし、トランザクションと関係なくリソースに出力する。

この時、jobTransactionManagerが参照するリソース(たとえば、データベース)に対してトランザクションが発行されるが、 テーブルアクセスは伴わないため実害がない。

また、実害がある場合や空振りでも参照するトランザクションを発行したくない場合は、リソースを必要としないResourcelessTransactionManagerを使用することができる。

ResourcelessTransactionManagerの使用例
<batch:tasklet transaction-manager="resourcelessTransactionManager">
<batch:chunk reader="reader" writer="writer" commit-interval="100" />
</batch:tasklet>

<bean id="resourcelessTransactionManager"
      class="org.springframework.batch.support.transaction.ResourcelessTransactionManager"/>
5.1.3.2. 複数データソースの場合

複数データソースに対して入出力するジョブのトランザクション制御について説明する。 入力と出力で考慮点が異なるため、これらを分けて説明する。

5.1.3.2.1. 複数データソースからの取得

複数データソースからのデータを取得する場合、処理の軸となるデータと、それに付随する追加データを分けて取得する。 以降は、処理の軸となるデータを処理対象レコード、それに付随する追加データを付随データと呼ぶ。

Spring Batchの構造上、ItemReaderは1つのリソースから処理対象レコードを取得することを前提としているためである。 これは、リソースの種類を問わず同じ考え方となる。

  1. 処理対象レコードの取得

    • ItemReaderにて取得する。

  2. 付随データの取得

    • 付随データは、そのデータに対す変更の有無と件数に応じて、以下の取得方法を選択する必要がある。これは、択一ではなく、併用してもよい。

      • ステップ実行前に一括取得

      • 処理対象レコードに応じて都度取得

ステップ実行前に一括取得する場合

以下を行うListenerを実装し、以降のStepからデータを参照する。

  • データを一括して取得する

  • スコープがJobまたはStepのBeanに情報を格納する

    • Spring BatchのExecutionContextを活用してもよいが、 可読性や保守性のために別途データ格納用のクラスを作成してもよい。 ここでは、簡単のためExecutionContextを活用した例で説明する。

マスタデータなど、処理対象データに依存しないデータを読み込む場合にこの方法を採用する。 ただし、マスタデータと言えど、メモリを圧迫するような大量件数が対象である場合は、都度取得したほうがよいかを検討すること。

一括取得するListenerの実装
@Component
// (1)
public class BranchMasterReadStepListener extends StepExecutionListenerSupport {

    @Inject
    BranchRepository branchRepository;

    @Override
    public void beforeStep(StepExecution stepExecution) {   // (2)

        List<Branch> branches = branchRepository.findAll(); //(3)

        Map<String, Branch> map = branches.stream()
                .collect(Collectors.toMap(Branch::getBranchId,
                        UnaryOperator.identity()));  // (4)

        stepExecution.getExecutionContext().put("branches", map); // (5)
    }
}
一括取得するListenerの定義
<batch:job id="outputAllCustomerList01" job-repository="jobRepository">
    <batch:step id="outputAllCustomerList01.step01">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="reader"
                         processor="retrieveBranchFromContextItemProcessor"
                         writer="writer" commit-interval="10"/>
            <batch:listeners>
                <batch:listener ref="branchMasterReadStepListener"/> <!-- (6) -->
            </batch:listeners>
        </batch:tasklet>
    </batch:step>
</batch:job>
一括取得したデータを後続ステップのItemProcessorで参照する例
@Component
public class RetrieveBranchFromContextItemProcessor implements
        ItemProcessor<Customer, CustomerWithBranch> {

    private Map<String, Branch> branches;

    @BeforeStep       // (7)
    @SuppressWarnings("unchecked")
    public void beforeStep(StepExecution stepExecution) {
        branches = (Map<String, Branch>) stepExecution.getExecutionContext()
                .get("branches"); // (8)
    }

    @Override
    public CustomerWithBranch process(Customer item) throws Exception {
        CustomerWithBranch newItem = new CustomerWithBranch(item);
        newItem.setBranch(branches.get(item.getChargeBranchId()));    // (9)
        return newItem;
    }
}
項番 説明

(1)

StepExecutionListenerインターフェースを実装する。
ここでは実装を簡易にするため、StepExecutionListenerインターフェースを実装したStepExecutionListenerSupportからの拡張としている。

(2)

ステップ実行前にデータを取得するため、beforeStepメソッドを実装する。

(3)

マスタデータを取得する処理を実装する。

(4)

後続処理が利用しやすいようにList型からMap型へ変換を行う。

(5)

ステップのコンテキストに取得したマスタデータをbranchesという名前で設定する。

(6)

対象となるジョブへ作成したListenerを登録する。

(7)

ItemProcessorのステップ実行前にマスタデータを取得するため、@BeforeStepアノテーションでListener設定を行う。

(8)

@BeforeStepアノテーションが付与されたメソッド内で、ステップのコンテキストから(5)で設定されたマスタデータを取得する。

(9)

ItemProcessorのprocessメソッド内で、マスタデータからデータ取得を行う。

コンテキストへ格納するオブジェクト

コンテキスト(ExecutionContext)へ格納するオブジェクトは、java.io.Serializableを実装したクラスでなければならない。 これは、ExecutionContextJobRepositoryへ格納されるためである。

処理対象レコードに応じて都度取得する場合

業務処理のItemProcessorとは別に、都度取得専用のItemProcessorにて取得する。 これにより、各ItemProcessorの処理を簡素化する。

  1. 都度取得用のItemProcessorを定義し、業務処理と分離する。

    • この際、テーブルアクセス時はMyBatisをそのまま使う。

  2. 複数のItemProcessorをCompositeItemProcessorを使用して連結する。

    • ItemProcessorはdelegates属性に指定した順番に処理されることに留意する。

都度取得用のItemProcessorの実装例
@Component
public class RetrieveBranchFromRepositoryItemProcessor implements
        ItemProcessor<Customer, CustomerWithBranch> {

    @Inject
    BranchRepository branchRepository;  // (1)

    @Override
    public CustomerWithBranch process(Customer item) throws Exception {
        CustomerWithBranch newItem = new CustomerWithBranch(item);
        newItem.setBranch(branchRepository.findOne(
                item.getChargeBranchId())); // (2)
        return newItem; // (3)
    }
}
都度取得用と業務処理要のItemProcessorの定義例
<bean id="compositeItemProcessor"
      class="org.springframework.batch.item.support.CompositeItemProcessor">
    <property name="delegates">
        <list>
            <ref bean="retrieveBranchFromRepositoryItemProcessor"/> <!-- (4) -->
            <ref bean="businessLogicItemProcessor"/>  <!-- (5) -->
        </list>
    </property>
</bean>
項番 説明

(1)

MyBatisを利用した都度データ取得用のRepositoryをInjectする。

(2)

入力データ(処理対象レコード)に対して、Repositoryから付随データを取得する。

(3)

処理対象レコードと付随データを一緒にしたデータを返却する。
このデータが次のItemProcessorへの入力データになることに注意する。

(4)

都度取得用のItemProcessorを設定する。

(5)

ビジネスロジックのItemProcessorを設定する。

5.1.3.2.2. 複数データソースへの出力(複数ステップ)

データソースごとにステップを分割し、各ステップで単一データソースを処理することで、ジョブ全体で複数データソースを処理する。

  • 1ステップ目で加工したデータをテーブルに格納し、2ステップ目でファイルに出力する、といった要領となる。

  • 各ステップがシンプルになりリカバリしやすい反面、2度手間になる可能性がある。

    • この結果、以下のような弊害を生む場合は、1ステップで複数データソースを処理することを検討する。

      • 処理時間が伸びてしまう

      • 業務ロジックが冗長となる

5.1.3.2.3. 複数データソースへの出力(1ステップ)

一般的に、複数のデータソースに対するトランザクションを1つにまとめる場合は、2phase-commitによる分散トランザクションを利用する。 しかし、以下の様なデメリットがあることも同時に知られている。

  • XAResourceなど分散トランザクションAPIにミドルウエアが対応している必要があり、それにもとづいた特殊な設定が必要になる

  • バッチプログラムのようなスタンドアロンJavaで、分散トランザクションのJTA実装ライブラリを追加する必要がある

  • 障害時のリカバリが難しい

Spring Batchでも分散トランザクションを活用することは可能だが、JTAによるグローバルトランザクションを使用する方法では、プロトコルの特性上、性能面のオーバーヘッドがかかる。 より簡易に複数データソースをまとめて処理する方法として、 Best Efforts 1PCパターン による実現手段を推奨する。

Best Efforts 1PCパターンとは

端的に言うと、複数データソースをローカルトランザクションで扱い、同じタイミングで逐次コミットを発行する、という手法を指す。 下図に概念図を示す。

Best Efforts 1PC Overview
Best Efforts 1PCパターンの概念図
図の説明
  1. ユーザがChainedTransactionManagerにトランザクション開始を指示する。

  2. ChainedTransactionManagerは、登録されているトランザクションマネージャを逐次トランザクションを開始する。

  3. ユーザは各リソースへトランザクショナルな操作を行う。

  4. ユーザがChainedTransactionManagerにコミットを指示する。

  5. ChainedTransactionManagerは、登録されているトランザクションマネージャを逐次コミットを発行する。

    • トランザクション開始と逆順にコミット(またはロールバック)される

この方法は分散トランザクションではないため、2番目以降のトランザクションマネージャにおけるcommit/rollback時に障害(例外)が発生した場合に、 データの整合性が保てない可能性がある。 そのため、トランザクション境界で障害が発生した場合のリカバリ方法を設計する必要があるが、リカバリ頻度を低減し、リカバリ手順を簡素しやすくなる効果がある。

複数のトランザクショナルリソースを同時に処理する場合

複数のデータベースを同時に処理する場合や、データベースとMQを処理する場合などに活用する。

以下のように、ChainedTransactionManagerを使用して複数トランザクションマネージャを1つにまとめて定義することで1phase-commitとして処理する。 なお、ChainedTransactionManagerはSpring Dataが提供するクラスである。

pom.xml
<dependencies>
    <!-- omitted -->
    <!-- (1) -->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-commons</artifactId>
    </dependency>
<dependencies>
chainedTransactionManagerの使用例
<!-- Chained Transaction Manager -->
<!-- (2) -->
<bean id="chainedTransactionManager"
      class="org.springframework.data.transaction.ChainedTransactionManager">
   <constructor-arg>
        <!-- (3) -->
        <list>
            <ref bean="transactionManager1"/>
            <ref bean="transactionManager2"/>
        </list>
    </constructor-arg>
</bean>

<batch:job id="jobSalesPlan01" job-repository="jobRepository">
    <batch:step id="jobSalesPlan01.step01">
        <!-- (4) -->
        <batch:tasklet transaction-manager="chainedTransactionManager">
            <!-- omitted -->
        </batch:tasklet>
    </batch:step>
</batch:job>
項番 説明

(1)

ChainedTransactionManagerを利用するために、依存関係を追加する。

(2)

ChainedTransactionManagerのBean定義を行う。

(3)

まとめたい複数のトランザクションマネージャをリストで定義する。

(4)

ジョブが利用するトランザクションマネージャに(1)で定義したBeanIDを指定する。

トランザクショナルリソースと非トランザクショナルリソースを同時に処理する場合

この方法は、データベースとファイルを同時に処理する場合に活用する。

データベースについては単一データソースの場合と同様。

ファイルについてはFlatFileItemWriterのtransactionalプロパティをtrueに設定することで、前述の「Best Efforts 1PCパターン」と同様の効果となる。
詳細は非トランザクショナルなデータソースに対する補足を参照。

この設定は、データベースのトランザクションをコミットする直前までファイルへの書き込みを遅延させるため、2つのデータソースで同期がとりやすくなる。 ただし、この場合でもデータベースへのコミット後、ファイル出力処理中に異常が発生した場合はデータの整合性が保てない可能性があるため、 リカバリ方法を設計する必要がある。

5.1.3.3. 中間方式コミットでの注意点

非推奨ではあるがItemWriterで処理データをスキップする場合は、チャンクサイズが設定値か強制変更される。 そのことがトランザクションに非常に大きく影響することに注意する。詳細は、 スキップを参照。

5.2. データベースアクセス

5.2.1. Overview

TERASOLUNA Batch 5.xでは、データベースアクセスの方法として、MyBatis3(以降、「MyBatis」と呼ぶ)を利用する。 MyBatisによるデータベースアクセスの基本的な利用方法は、TERASOLUNA Server 5.x 開発ガイドラインの以下を参照してほしい。

本節では、TERASOLUNA Batch 5.x特有の使い方を中心に説明する。

本機能は、チャンクモデルとタスクレットモデルとで使い方が異なるため、それぞれについて説明する。

5.2.2. How to use

TERASOLUNA Batch 5.xでのデータベースアクセス方法を説明する。

TERASOLUNA Batch 5.xでのデータベースアクセスは、以下の2つの方法がある。
これらはデータベースアクセスするコンポーネントによって使い分ける。

  1. MyBatis用のItemReaderおよびItemReaderを利用する。

    • チャンクモデルでのデータベースアクセスによる入出力で使用する。

      • org.mybatis.spring.batch.MyBatisCursorItemReader

      • org.mybatis.spring.batch.MyBatisBatchItemWriter

  2. Mapperインターフェースを利用する

    • チャンクモデルでのビジネスロジック処理で使用する。

      • ItemProcessor実装で利用する。

    • タスクレットモデルでのデータベースアクセス全般で使用する。

      • Tasklet実装で利用する。

5.2.2.1. 共通設定

データベースアクセスにおいて必要な共通設定について説明を行う。

5.2.2.1.1. データソースの設定

TERASOLUNA Batch 5.xでは、2つのデータソースを前提としている。 launch-context.xmlでデフォルト設定している2つのデータソースを示す。

データソース一覧
データソース名 説明

adminDataSource

Spring BatchやTERASOLUNA Batch 5.xが利用するデータソース
JobRepositoryや非同期実行(DBポーリング)で利用している。

jobDataSource

ジョブが利用するデータソース

以下に、launch-context.xmlと接続情報のプロパティを示す。
これらをユーザの環境に合わせて設定すること。

resources\META-INF\spring\launch-context.xml
<!-- (1) -->
<bean id="adminDataSource" class="org.apache.commons.dbcp2.BasicDataSource"
      destroy-method="close"
      p:driverClassName="${admin.jdbc.driver}"
      p:url="${admin.jdbc.url}"
      p:username="${admin.jdbc.username}"
      p:password="${admin.jdbc.password}"
      p:maxTotal="10"
      p:minIdle="1"
      p:maxWaitMillis="5000"
      p:defaultAutoCommit="false"/>

<!-- (2) -->
<bean id="jobDataSource" class="org.apache.commons.dbcp2.BasicDataSource" 
      destroy-method="close"
      p:driverClassName="${jdbc.driver}"
      p:url="${jdbc.url}"
      p:username="${jdbc.username}"
      p:password="${jdbc.password}"
      p:maxTotal="10"
      p:minIdle="1"
      p:maxWaitMillis="5000"
      p:defaultAutoCommit="false" />
batch-application.properties
# (3)
# Admin DataSource settings.
admin.h2.jdbc.driver=org.h2.Driver
admin.h2.jdbc.url=jdbc:h2:mem:batch;DB_CLOSE_DELAY=-1
admin.h2.jdbc.username=sa
admin.h2.jdbc.password=

# (4)
# Job DataSource settings.
jdbc.driver=org.postgresql.Driver
jdbc.url=jdbc:postgresql://localhost:5432/postgres
jdbc.username=postgres
jdbc.password=postgres
説明
項番 説明

(1)

adminDataSourceの定義。(3)の接続情報が設定される。

(2)

jobDataSourceの定義。(4)の接続情報が設定される。

(3)

adminDataSourceで利用するデータベースへの接続情報
この例では、H2を利用している。

(4)

jobDataSourceで利用するデータベースへの接続情報
この例では、PostgreSQLを利用している。

5.2.2.1.2. MyBatisの設定

TERASOLUNA Batch 5.xで、MyBatisの設定をする上で重要な点について説明をする。

バッチ処理を実装する際の重要なポイントの1つとして「大量のデータを一定のリソースで効率よく処理する」が挙げられる。
これに関する設定を説明する。

  • fetchSize

    • 一般的なバッチ処理では、大量のデータを処理する際の通信コストを低減するために、 JDBCドライバに適切なfetchSizeを指定することが必須である。 fetchSizeとは、JDBCドライバとデータベース間とで1回の通信で取得するデータ件数を設定するパラメータである。 この値は出来る限り大きい値を設定することが望ましいが、大きすぎるとメモリを圧迫するため、注意が必要である。 ユーザにてチューニングする必要がある箇所と言える。

    • MyBatisでは、全クエリ共通の設定としてdefaultFetchSizeを設定することができ、さらにクエリごとのfetchSize設定で上書きできる。

  • executorType

    • 一般的なバッチ処理では、同一トランザクション内で同じSQLを全データ件数/fetchSizeの回数分実行することになる。 この際、都度ステートメントを作成するのではなく再利用することで効率よく処理できる。

    • MyBatisの設定における、defaultExecutorTypeREUSEを設定することでステートメントの再利用ができ、 処理スループット向上に寄与する。

    • 大量のデータを一度に更新する場合、JDBCのバッチ更新を利用することで性能向上が期待できる。
      そのため、MyBatisBatchItemWriterで利用するSqlSessionTemplateには、
      executorTypeに(REUSEではなく)BATCHが設定されている。

TERASOLUNA Batch 5.xでは、同時に2つの異なるExecutorTypeが存在する。 一方のExecutorTypeで実装する場合が多いと想定するが、併用時は特に注意が必要である。 この点は、ItemReader・ItemWriter以外のデータベースアクセスにて詳しく説明する。

MyBatisのその他のパラメータ

その他のパラメータに関しては以下リンクを参照し、 アプリケーションの特性にあった設定を行うこと。
http://www.mybatis.org/mybatis-3/configuration.html

以下にデフォルト提供されている設定を示す。

META-INF/spring/launch-context.xml
<bean id="jobSqlSessionFactory"
      class="org.mybatis.spring.SqlSessionFactoryBean"
      p:dataSource-ref="jobDataSource">
    <!-- (1) -->
    <property name="configuration">
        <bean class="org.apache.ibatis.session.Configuration"
              p:localCacheScope="STATEMENT"
              p:lazyLoadingEnabled="true"
              p:aggressiveLazyLoading="false"
              p:defaultFetchSize="1000"
              p:defaultExecutorType="REUSE"/>
    </property>
</bean>

<!-- (2) -->
<bean id="batchModeSqlSessionTemplate"
      class="org.mybatis.spring.SqlSessionTemplate"
      c:sqlSessionFactory-ref="jobSqlSessionFactory"
      c:executorType="BATCH"/>
説明
項番 説明

(1)

MyBatisの各種設定を行う。
デフォルトでは、fetchSizeを1000に設定している。

(2)

MyBatisBatchItemWriterのために、executorTypeBATCHSqlSessionTemplateを定義している。

adminDataSourceを利用したSqlSessionFactoryの定義箇所について

同期実行をする場合は、adminDataSourceを利用したSqlSessionFactoryは不要であるため、定義がされていない。 非同期実行(DBポーリング)を利用する場合、ジョブ要求テーブルへアクセスするために META-INF/spring/async-batch-daemon.xml内に定義されている。

META-INF/spring/async-batch-daemon.xml
<bean id="adminSqlSessionFactory"
      class="org.mybatis.spring.SqlSessionFactoryBean"
      p:dataSource-ref="adminDataSource" >
    <property name="configuration">
        <bean class="org.apache.ibatis.session.Configuration"
              p:localCacheScope="STATEMENT"
              p:lazyLoadingEnabled="true"
              p:aggressiveLazyLoading="false"
              p:defaultFetchSize="1000"
              p:defaultExecutorType="REUSE"/>
    </property>
</bean>
5.2.2.1.3. Mapper XMLの定義

TERASOLUNA Batch 5.x特有の説明事項はないので、TERASOLUNA Server 5.x 開発ガイドラインの データベースアクセス処理の実装を参照してほしい。

5.2.2.1.4. MyBatis-Springの設定

MyBatis-Springが提供するItemReaderおよびItemWriterを使用する場合、MapperのConfigで使用するMapper XMLを設定する必要がある。

設定方法としては、以下の2つが考えられる。

  1. 共通設定として、すべてのジョブで使用するMapper XMLを登録する。

    • META-INF/spring/launch-context.xmlにすべてのMapper XMLを記述することになる。

  2. 個別設定として、ジョブ単位で利用するMapper XMLを登録する。

    • META-INF/jobs/配下のBean定義に、個々のジョブごとに必要なMapper XMLを記述することになる。

共通設定をしてしまうと、同期実行をする際に実行するジョブのMapper XMLだけでなく、その他のジョブが使用するMapper XMLも読み込んでしまうために以下に示す弊害が生じる。

  • ジョブの起動までに時間がかかる

  • メモリリソースの消費が大きくなる

これを回避するために、TERASOLUNA Batch 5.xでは、個別設定として、個々のジョブ定義でそのジョブが必要とするMapper XMLだけを指定する設定方法を採用する。

基本的な設定方法については、TERASOLUNA Server 5.x 開発ガイドラインの MyBatis-Springの設定を参照してほしい。

TERASOLUNA Batch 5.xでは、複数のSqlSessionFactoryおよびSqlSessionTemplateが定義されているため、 どれを利用するか明示的に指定する必要がある。
基本的にはjobSqlSessionFactoryを指定すればよい。

以下に設定例を示す。

META-INF/jobs/common/jobCustomerList01.xml
<!-- (1) -->
<mybatis:scan
    base-package="org.terasoluna.batch.functionaltest.app.repository.mst"
    factory-ref="jobSqlSessionFactory"/>
説明
項番 説明

(1)

<mybatis:scan>factory-ref属性にjobSqlSessionFactoryを設定する。

5.2.2.2. ItemReaderにおけるデータベースアクセス

ここではItemReaderによるデータベースアクセスについて説明する。

5.2.2.2.1. MyBatisのItemReader

MyBatis-Springが提供するItemReaderとして下記の2つが存在する。

  • org.mybatis.spring.batch.MyBatisCursorItemReader

  • org.mybatis.spring.batch.MyBatisPagingItemReader

MyBatisPagingItemReaderは、TERASOLUNA Server 5.x 開発ガイドラインの Entityのページネーション検索(SQL絞り込み方式)で 説明している仕組みを利用したItemReaderである。
一定件数を取得した後に再度SQLを発行するため、データの一貫性が保たれない可能性がある。 そのため、バッチ処理で利用するには危険であることから、TERASOLUNA Batch 5.xでは原則使用しない。
TERASOLUNA Batch 5.xではMyBatisCursorItemReaderのみを利用する。

TERASOLUNA Batch 5.xでは、MyBatis-Springの設定で説明したとおり、 mybatis:scanによって動的にMapper XMLを登録する方法を採用している。 そのため、Mapper XMLに対応するインターフェースを用意する必要がある。 詳細については、TERASOLUNA Server 5.x 開発ガイドラインの データベースアクセス処理の実装を参照。

MyBatisCursorItemReaderの利用例を以下に示す。

META-INF/jobs/common/jobCustomerList01.xml
<!-- (1) -->
<mybatis:scan
    base-package="org.terasoluna.batch.functionaltest.app.repository.mst"
    factory-ref="jobSqlSessionFactory"/>

<!-- (2) (3) (4) -->
<bean id="reader"
      class="org.mybatis.spring.batch.MyBatisCursorItemReader" scope="step"
      p:queryId="org.terasoluna.batch.functionaltest.app.repository.mst.CustomerRepository.findAll"
      p:sqlSessionFactory-ref="jobSqlSessionFactory"/>
org/terasoluna/batch/functionaltest/app/repository/mst/CustomerRepository.xml
<!-- (5) -->
<mapper namespace="org.terasoluna.batch.functionaltest.app.repository.mst.CustomerRepository">

    <!-- (6) -->
    <select id="findAll"
            resultType="org.terasoluna.batch.functionaltest.app.model.mst.Customer">
        <![CDATA[
        SELECT
            customer_id AS customerId,
            customer_name AS customerName,
            customer_address AS customerAddress,
            customer_tel AS customerTel,
            charge_branch_id AS chargeBranchId,
            create_date AS createDate,
            update_date AS updateDate
        FROM
            customer_mst
        ORDER by
            charge_branch_id ASC, customer_id ASC
        ]]>
    </select>

    <!-- omitted -->
</mapper>
org.terasoluna.batch.functionaltest.app.repository.mst.CustomerRepository
public interface CustomerRepository {
    // (7)
    List<Customer> findAll();

    // omitted
}
説明
項番 説明

(1)

Mapper XMLの登録を行う。

(2)

MyBatisCursorItemReaderを定義する。

(3)

queryIdのプロパティに、(6)で定義しているSQLのIDを(5)のnamespace + <メソッド名>で指定する。

(4)

sqlSessionFactoryのプロパティに、アクセスするデータベースのSqlSessionFactoryを指定する。

(5)

Mapper XMLを定義する。namespaceの値とインターフェースのFQCNを一致させること。

(6)

SQLを定義する。

(7)

(6)で定義したSQLのIDに対応するメソッドをインターフェースに定義する。

5.2.2.3. ItemWriterにおけるデータベースアクセス

ここではItemWriterによるデータベースアクセスについて説明する。

5.2.2.3.1. MyBatisのItemWriter

MyBatis-Springが提供するItemWriterは以下の1つのみである。

  • org.mybatis.spring.batch.MyBatisBatchItemWriter

基本的な設定については、MyBatisのItemReaderと同じである。 MyBatisBatchItemWriterでは、MyBatisの設定で説明した batchModeSqlSessionTemplateを指定する必要がる。

MyBatisBatchItemWriterの定義例を以下に示す。

META-INF/jobs/common/jobSalesPlan01.xml
<!-- (1) -->
<mybatis:scan
    base-package="org.terasoluna.batch.functionaltest.app.repository.plan"
    factory-ref="jobSqlSessionFactory"/>

<!-- (2) (3) (4) -->
<bean id="detailWriter" class="org.mybatis.spring.batch.MyBatisBatchItemWriter"
      p:statementId="org.terasoluna.batch.functionaltest.app.repository.plan.SalesPlanDetailRepository.create"
      p:sqlSessionTemplate="batchModeSqlSessionTemplate"/>

<!-- omitted -->
org/terasoluna/batch/functionaltest/app/repository/plan/SalesPlanDetailRepository.xml
<!-- (5) -->
<mapper namespace="org.terasoluna.batch.functionaltest.app.repository.plan.SalesPlanDetailRepository">

    <!-- (6) -->
    <insert id="create"
            parameterType="org.terasoluna.batch.functionaltest.app.model.plan.SalesPlanDetail">
        <![CDATA[
        INSERT INTO
            sales_plan_detail(branch_id, year, month, customer_id, amount)
        VALUES (
            #{branchId}, #{year}, #{month}, #{customerId}, #{amount}
        )
        ]]>
    </insert>

    <!-- omitted -->
</mapper>
org.terasoluna.batch.functionaltest.app.repository.plan.SalesPlanDetailRepository
public interface SalesPlanDetailRepository {

    // (7)
    void create(SalesPlanDetail salesPlanDetail);

    // omitted
}
説明
項番 説明

(1)

Mapper XMLの登録を行う。

(2)

MyBatisBatchItemWriterを定義する。

(3)

statementIdのプロパティに、(6)で定義しているSQLのIDを(5)のnamespace + <メソッド名>で指定する。

(4)

sqlSessionTemplateのプロパティに、アクセスするデータベースのSessionTemplateを指定する。
指定するSessionTemplateは、executorTypeBATCHに設定されていることが必須である。

(5)

Mapper XMLを定義する。namespaceの値とインターフェースのFQCNを一致させること。

(6)

SQLを定義する。

(7)

(6)で定義したSQLのIDに対応するメソッドをインターフェースに定義する。

5.2.2.4. ItemReader・ItemWriter以外のデータベースアクセス

ItemReader・ItemWriter以外のデータベースアクセスについて説明する。

ItemReader・ItemWriter以外でデータベースアクセスするには、Mapperインターフェースを利用する。 Mapperインターフェースを利用するにあたって、TERASOLUNA Batch 5.xでは以下の制約を設けている。

Mapperインターフェースの利用可能な箇所
処理 ItemProcessor Tasklet リスナー

参照

利用可

利用可

利用可

更新

条件付で利用可

利用可

利用不可

ItemProcessorでの制約

MyBatisには、同一トランザクション内で2つ以上のExecutorTypeで実行してはいけないという制約がある。
「ItemWriterにMyBatisBatchItemWriterを使用する」と「ItemProcessorでMapperインターフェースを使用し参照更新をする」を 同時に満たす場合は、この制約に抵触する。
制約を回避するには、ItemProcessorではExecutorTypeBATCHのMapperインターフェースによって データベースアクセスすることになる。
加えて、MyBatisBatchItemWriterではSQL実行後のステータスチェックにより、自身が発行したSQLかどうかチェックしているのだが、 当然ItemProcessorによるSQL実行は管理できないためエラーが発生してしまう。
よって、MyBatisBatchItemWriterを利用している場合は、Mapperインターフェースによる更新はできなくなり、参照のみとなる。

MyBatisBatchItemWriterのエラーチェックを無効化する設定ができるが、予期せぬ動作が起きる可能性があるため無効化は禁止する。

Taskletでの制約

Taskletでは、Mapperインターフェースを利用することが基本であるため、ItemProcessorのような影響はない。
MyBatisBatchItemWriterをInjectして利用することも考えられるが、その場合はMapperインターフェース自体を BATCH設定で処理すればよい。つまり、Taskletでは、MyBatisBatchItemWriterをInjectして使う必要は基本的にない。

リスナーでの制約

リスナーでもItemProcessorでの制約と同じ制約が成立する。 加えて、リスナーでは、更新を必要とするユースケースが考えにくい。よって、リスナーでは、更新系処理を禁止する。

リスナーで想定される更新処理の代替
ジョブの状態管理

Spring BatchのJobRepositoryによって行われている

データベースへのログ出力

ログのAppenderで実施すべき。ジョブのトランザクションとも別管理する必要がある。

5.2.2.4.1. ItemProcessorでのデータベースアクセス

ItemProcessorでのデータベースアクセス例を説明する。

ItemProcessorでの実装例
@Component
public class UpdateItemFromDBProcessor implements
        ItemProcessor<SalesPerformanceDetail, SalesPlanDetail> {

    // (1)
    @Inject
    CustomerRepository customerRepository;

    @Override
    public SalesPlanDetail process(SalesPerformanceDetail readItem) throws Exception {

        // (2)
        Customer customer = customerRepository.findOne(readItem.getCustomerId());

        // (3)
        SalesPlanDetail writeItem = new SalesPlanDetail();
        writeItem.setBranchId(customer.getChargeBranchId());
        writeItem.setYear(readItem.getYear());
        writeItem.setMonth(readItem.getMonth());
        writeItem.setCustomerId(readItem.getCustomerId());
        writeItem.setAmount(readItem.getAmount());
        return writeItem;
    }
}
Bean定義
<!-- (2) -->
<mybatis:scan
        base-package="org.terasoluna.batch.functionaltest.app.repository"
        template-ref="batchModeSqlSessionTemplate"/>

<bean id="reader" class="org.mybatis.spring.batch.MyBatisCursorItemReader"
      p:queryId"org.terasoluna.batch.functionaltest.app.repository.performance.SalesPerformanceDetailRepository.findAll"
      p:sqlSessionFactory-ref="jobSqlSessionFactory"/>

<!-- (3) -->
<bean id="writer" class="org.mybatis.spring.batch.MyBatisBatchItemWriter"
      p:statementId="org.terasoluna.batch.functionaltest.app.repository.plan.SalesPlanDetailRepository.create"
      p:sqlSessionTemplate-ref="batchModeSqlSessionTemplate"/>

<batch:job id="DBAccessByItemProcessor" job-repository="jobRepository">
    <batch:step id="DBAccessByItemProcessor.step01">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <!-- (4) -->
            <batch:chunk reader="reader"
                         processor="updateItemFromDBProcessor"
                         writer="writer" commit-interval="10"/>
        </batch:tasklet>
    </batch:step>
</batch:job>

MapperインターフェースとMapper XMLは省略する。

説明

項番

説明

(1)

MapperインターフェースをInjectする。

(2)

Mapper XMLの登録を行う。
template-ref属性にBATCH設定されているbatchModeSqlSessionTemplateを指定することで、 ItemProcessorでのデータベースアクセスはBATCHとなる。 ここで、factory-ref="jobSqlSessionFactory"としてしまうと、前述の制約に抵触し、 MyBatisBatchItemWriter実行時に例外が発生してしまう。

(3)

MyBatisBatchItemWriterを定義する。
sqlSessionTemplateプロパティにBATCH設定されているbatchModeSqlSessionTemplateを指定する。

(4)

MapperインターフェースをInjectしたItemProcessorを設定する。

MyBatisCursorItemReader設定の補足

以下に示す定義例のように、MyBatisCursorItemReaderとMyBatisBatchItemWriterで異なるExecutorTypeを使用しても問題ない。 これは、MyBatisCursorItemReaderによるリソースのオープンが、トランザクション開始前に行われているからである。

<bean id="reader" class="org.mybatis.spring.batch.MyBatisCursorItemReader"
      p:queryId="xxx"
      p:sqlSessionFactory-ref="jobSqlSessionFactory"/>

<bean id="writer" class="org.mybatis.spring.batch.MyBatisBatchItemWriter"
      p:statementId="yyy"
      p:sqlSessionTemplate-ref="batchModeSqlSessionTemplate"/>
5.2.2.4.2. Taskletでのデータベースアクセス

Taskletでのデータベースアクセス例を説明する。

Taskletでの実装例
@Component
public class OptimisticLockTasklet implements Tasklet {

    // (1)
    @Inject
    ExclusiveControlRepository repository;

    // omitted

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {

        Branch branch = repository.branchFindOne(branchId); // (2)
        ExclusiveBranch exclusiveBranch = new ExclusiveBranch();

        exclusiveBranch.setBranchId(branch.getBranchId());
        exclusiveBranch.setBranchName(branch.getBranchName() + " - " + identifier);
        exclusiveBranch.setBranchAddress(branch.getBranchAddress() + " - " + identifier);
        exclusiveBranch.setBranchTel(branch.getBranchTel());
        exclusiveBranch.setCreateDate(branch.getUpdateDate());
        exclusiveBranch.setUpdateDate(new Timestamp(System.currentTimeMillis()));
        exclusiveBranch.setOldBranchName(branch.getBranchName());

        int result = repository.branchExclusiveUpdate(exclusiveBranch); // (3)

        return RepeatStatus.FINISHED;
    }
}
Bean定義
<!-- (4) -->
<mybatis:scan
        base-package="org.terasoluna.batch.functionaltest.ch05.exclusivecontrol.repository"
        factory-ref="jobSqlSessionFactory"/>

<batch:job id="taskletOptimisticLockCheckJob" job-repository="jobRepository">
    <batch:step id="taskletOptimisticLockCheckJob.step01">
        <batch:tasklet transaction-manager="jobTransactionManager"
                       ref="optimisticLockTasklet"> <!-- (5) -->
        </batch:tasklet>
    </batch:step>
</batch:job>

MapperインターフェースとMapper XMLは省略する。

説明

項番

説明

(1)

MapperインターフェースをInjectする。

(2)

Mapperインターフェースで検索処理を実行する。

(3)

Mapperインターフェースで更新処理を実行する。

(4)

Mapper XMLの登録を行う。
factory-ref属性にREUSE設定されているjobSqlSessionFactoryを指定する。

(5)

MapperインターフェースをInjectしTaskletを設定する。

batchModeSqlSessionTemplateの利用

タスクレットモデルでの更新処理が多い場合は、factory-ref属性に`batchModeSqlSessionTemplateを設定する。 これにより、バッチ更新処理が行われるので、性能向上が期待できる。 ただし、バッチ更新の実行はflushを明示的に呼ぶ必要があるため、注意すること。 詳細は、 バッチモードのRepository利用時の注意点 を参照のこと。

5.2.2.4.3. リスナーでのデータベースアクセス

リスナーでのデータベースアクセスは他のコンポーネントと連携することが多い。 使用するリスナー及び実装方法によっては、Mapperインターフェースで取得したデータを、 他のコンポーネントへ引き渡す仕組みを追加で用意する必要がある。

ここでは一例として、StepExecutionListenerで ステップ実行前にデータを取得して、ItemProcessorで取得したデータを利用する例を示す。

リスナーでの実装例
public class CacheSetListener extends StepExecutionListenerSupport {

    // (1)
    @Inject
    CustomerRepository customerRepository;

    // (2)
    @Inject
    CustomerCache cache;

    @Override
    public void beforeStep(StepExecution stepExecution) {
        // (3)
        customerRepository.findAll().forEach(customer ->
                cache.addCustomer(customer.getCustomerId(), customer));
    }
}
ItemProcessorでの利用例
@Component
public class UpdateItemFromCacheProcessor implements
        ItemProcessor<SalesPerformanceDetail, SalesPlanDetail> {

    // (4)
    @Inject
    CustomerCache cache;

    @Override
    public SalesPlanDetail process(SalesPerformanceDetail readItem) throws Exception {
        Customer customer = cache.getCustomer(readItem.getCustomerId());  // (5)

        SalesPlanDetail writeItem = new SalesPlanDetail();

        // omitted
        writerItem.setCustomerName(customer.getCustomerName); // (6)

        return writeItem;
    }
}
キャッシュクラス
// (7)
@Component
public class CustomerCache {

    Map<String, Customer> customerMap = new HashMap<>();

    public Customer getCustomer(String customerId) {
        return customerMap.get(customerId);
    }

    public void addCustomer(String id, Customer customer) {
        customerMap.put(id, customer);
    }
}
Bean定義
<!-- omitted -->

<!-- (8) -->
<mybatis:scan
        base-package="org.terasoluna.batch.functionaltest.app.repository"
        template-ref="batchModeSqlSessionTemplate"/>
<!-- (9) -->
<bean id="cacheSetListener"
      class="org.terasoluna.batch.functionaltest.ch05.dbaccess.CacheSetListener"/>

<!-- omitted -->

<batch:job id="DBAccessByItemListener" job-repository="jobRepository">
    <batch:step id="DBAccessByItemListener.step01">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="reader"
                         processor="updateItemFromCacheProcessor"
                         writer="writer" commit-interval="10"/> <!-- (10) -->
            <!-- (11) -->
            <batch:listeners>
                <batch:listener ref="cacheSetListener"/>
            </batch:listeners>
        </batch:tasklet>
    </batch:step>
</batch:job>
説明

項番

説明

(1)

MapperインターフェースをInjectする。

(2)

Mapperインターフェースから取得したデータをキャッシュするためのBeanをInjectする。

(3)

リスナーにて、Mapperインターフェースからデータを取得してキャッシュする。
ここでは、StepExecutionListener#beforeStepにてステップ実行前にキャッシュを作成し、 以降の処理ではキャッシュを参照することで、I/Oを低減し処理効率を高めている。

(4)

(2)で設定したキャッシュと同じBeanをInjectする。

(5)

キャッシュから該当するデータを取得する。

(6)

更新データにキャッシュからのデータを反映する。

(7)

キャッシュクラスをコンポーネントとして実装する。
ここではBeanスコープはsingletonにしている。ジョブに応じて設定すること。

(8)

Mapper XMLの登録を行う。
template-ref属性にBATCHが設定されているbatchModeSqlSessionTemplateを指定する。

(9)

Mapperインターフェースを利用するリスナーを定義する。

(10)

キャッシュを利用するItemProcessorを指定する。

(11)

(9)で定義したリスナーを登録する。

リスナーでのSqlSessionFactoryの利用

上記の例では、batchModeSqlSessionTemplateを設定しているが、jobSqlSessionFactoryを設定してもよい。

チャンクのスコープ外で動作するリスナーについては、トランザクション外で処理されるため、 jobSqlSessionFactoryを設定しても問題ない。

5.3. ファイルアクセス

5.3.1. Overview

本節では、ファイルの入出力を行う方法について説明する。

本機能は、チャンクモデルとタスクレットモデルとで同じ使い方になる。

5.3.1.1. 扱えるファイルの種類
扱えるファイルの種類

TERASOLUNA Batch 5.xで扱えるファイルは以下のとおりである。
これは、Spring Batchにて扱えるものと同じである。

  • フラットファイル

  • XML

ここではフラットファイルの入出力を行うための方法について説明したのち、 XMLについてHow To Extendで説明する。

まず、TERASOLUNA Batch 5.xで扱えるフラットファイルの種類を示す。
フラットファイルにおける行をここではレコードと呼び、 ファイルの種類はレコードの形式にもとづく、とする。

レコード形式
形式 概要

可変長レコード

CSVやTSVに代表される区切り文字により各項目を区切ったレコード形式。各項目の長さが可変である。

固定長レコード

項目の長さ(バイト数)により各項目を区切ったレコード形式。各項目の長さが固定である。

単一文字列レコード

1レコードを1文字列として扱う形式。

扱えるファイルの構造

フラットファイルの基本構造は以下の2点から構成される。

  • レコード区分

  • レコードフォーマット

フラットファイルのフォーマットを構成する要素
要素 概要

レコード区分

レコードの種類、役割を指す。ヘッダ、データ、トレーラなどがある。
詳しくは後述する。

レコードフォーマット

ヘッダ、データ、トレーラレコードがそれぞれ何行あるのか、ヘッダ部~トレーラ部が複数回繰り返されるかなど、レコードの構造を指す。
シングルフォーマットとマルチフォーマットがある。詳しくは後述する。

TERASOLUNA Batch 5.xでは、各種レコード区分をもつシングルフォーマットおよびマルチフォーマットのフラットファイルを扱うことができる。

各種レコード区分およびレコードフォーマットについて説明する。

各種レコード区分の概要を以下に示す。

レコード区分ごとの特徴
レコード区分 概要

ヘッダレコード

ファイル(データ部)の先頭に付与されるレコードである。
フィールド名、ファイル共通の事項、データ部の集計情報などをもつ。

データレコード

ファイルの主な処理対象となるデータをもつレコードである。

トレーラ/フッタレコード

ファイル(データ部)の末尾に付与されるレコードである。
ファイル共通の事項、データ部の集計情報などをもつ。
シングルフォーマットの場合、フッタレコードと呼ばれることもある。

フッタ/エンドレコード

マルチフォーマットの場合にファイルの末尾に付与されるレコードである。
ファイル共通の事項、ファイル全体の集計情報などをもつ。

レコード区分を示すフィールドについて

ヘッダレコードやトレーラレコードをもつフラットファイルでは、レコード区分を示すフィールドをもたせる場合がある。
TERASOLUNA Batch 5.xでは特にマルチフォーマットファイルの処理において、レコード区分ごとに異なる処理を実施する場合などにレコード区分のフィールドを活用する。
レコード区分によって実行する処理を選択する場合の実装は、マルチフォーマットを参考にすること。

ファイルフォーマット関連の名称について

個々のシステムにおけるファイルフォーマットの定義によっては、 フッタレコードをエンドレコードと呼ぶなど等ガイドラインとは異なる名称が使われている場合がある。
適宜読み替えを行うこと。

シングルフォーマットおよびマルチフォーマットの概要を以下に示す。

シングルフォーマットおよびマルチフォーマットの概要
フォーマット 概要

シングルフォーマット

ヘッダn行 + データn行 + トレーラn行 の形式である。

マルチフォーマット

(ヘッダn行 + データn行 + トレーラn行)* n + フッタn行 の形式である。
シングルフォーマットを複数回繰り返した後にフッタレコードが付与されている形式である。

マルチフォーマットのレコード構成を図に表すと下記のようになる。

Multi format file layout
マルチフォーマットのレコード構成図

シングルフォーマット、マルチフォーマットフラットファイルの例を以下に示す。
なお、ファイルの内容説明に用いるコメントアウトを示す文字として//を使用する。

シングルフォーマット、レコード区分なしフラットファイル(CSV形式)の例
branchId,year,month,customerId,amount  // (1)
000001,2016,1,0000000001,100000000  // (2)
000001,2016,1,0000000002,200000000  // (2)
000001,2016,1,0000000003,300000000  // (2)
000001,3,600000000  // (3)
ファイルの内容の項目一覧
項番 説明

(1)

ヘッダレコードである。
データ部のフィールド名を示している。

(2)

データレコードである。

(3)

トレーラレコードである。
データ部の集計情報を保持している。

シングルフォーマット、レコード区分ありのフラットファイル(CSV形式)の例
// (1)
H,branchId,year,month,customerId,amount  // (2)
D,000001,2016,1,0000000001,100000000
D,000001,2016,1,0000000002,200000000
D,000001,2016,1,0000000003,300000000
T,000001,3,600000000
H,branchId,year,month,customerId,amount  // (2)
D,00002,2016,1,0000000004,400000000
D,00002,2016,1,0000000005,500000000
D,00002,2016,1,0000000006,600000000
T,00002,3,1500000000
H,branchId,year,month,customerId,amount  // (2)
D,00003,2016,1,0000000007,700000000
D,00003,2016,1,0000000008,800000000
D,00003,2016,1,0000000009,900000000
T,00003,3,2400000000
F,3,9,4500000000  // (3)
ファイルの内容の項目一覧
項番 説明

(1)

レコードの先頭にレコード区分を示すフィールドをもっている。
それぞれ下記のレコード区分を示す。
H:ヘッダレコード
D:データレコード
T:トレーラレコード
F:フッタレコード

(2)

branchIdが変わるごとにヘッダ、データ、トレーラを3回繰り返している。

(3)

フッタレコードである。
ファイル全体の集計情報を保持している。

データ部のフォーマットに関する前提

How to useでは、データ部のレイアウトは同一のフォーマットである事を前提して説明する。
これは、データ部のレコードはすべて同じ変換対象クラスへマッピングされることを意味する。

マルチフォーマットファイルの説明について
  • How to useでは、シングルフォーマットファイルについて説明する。

  • マルチフォーマットや上記の構造にフッタ部を含む構造をもつフラットファイルについては、How To Extendを参照すること

5.3.1.2. フラットファイルの入出力を行うコンポーネント

フラットファイルを扱うためのクラスを示す。

入力

フラットファイルの入力を行うために使用するクラスの関連は以下のとおりである。

Component relationship FlatFileItemReader class diagram
フラットファイルの入力を行うために使用するクラスの関連

各コンポーネントの呼び出し関係は以下のとおりである。

Component relationship FlatFileItemReader sequence diagram
各コンポーネントの呼び出し関係

各コンポーネントの詳細を以下に示す。

org.springframework.batch.item.file.FlatFileItemReader

フラットファイルを読み込みに使用するItemReaderの実装クラス。以下のコンポーネントを利用する。
簡単な処理の流れは以下のとおり。
1.BufferedReaderFactoryを使用してBufferedReaderを取得する。
2.取得したBufferedReaderを使用してフラットファイルから1レコードを読み込む。
3.LineMapperを使用して1レコードを対象Beanへマッピングする。

org.springframework.batch.item.file.BufferedReaderFactory

ファイルを読み込むためのBufferedReaderを生成する。

org.springframework.batch.item.file.LineMapper

1レコードを対象Beanへマッピングする。以下のコンポーネントを利用する。
簡単な処理の流れは以下のとおり。
1.LineTokenizerを使用して1レコードを各項目に分割する。
2.FieldSetMapperによって分割した項目をBeanのプロパティにマッピングする。

org.springframework.batch.item.file.transform.LineTokenizer

ファイルから取得した1レコードを各項目に分割する。
分割された各項目はFieldSetクラスに格納される。

org.springframework.batch.item.file.mapping.FieldSetMapper

分割した1レコード内の各項目を対象Beanのプロパティへマッピングする。

出力

フラットファイルの出力を行うために使用するクラスの関連は以下のとおりである。

Component relationship FlatFileItemWriter class diagram
フラットファイルの出力を行うために使用するクラスの関連

各コンポーネントの呼び出し関係は以下のとおりである。

Component relationship FlatFileItemWriter sequence diagram
各コンポーネントの呼び出し関係
org.springframework.batch.item.file.FlatFileItemWriter

フラットファイルへの書き出しに使用するItemWriterの実装クラス。以下のコンポーネントを利用する。 LineAggregator対象Beanを1レコードへマッピングする。

org.springframework.batch.item.file.transform.LineAggregator

対象Beanを1レコードへマッピングするために使う。 Beanのプロパティとレコード内の各項目とのマッピングはFieldExtractorで行う。

org.springframework.batch.item.file.transform.FieldExtractor

対象Beanのプロパティを1レコード内の各項目へマッピングする。

5.3.2. How to use

フラットファイルのレコード形式別に使い方を説明する。

その後、以下の項目について説明する。

5.3.2.1. 可変長レコード

可変長レコードファイルを扱う場合の定義方法を説明する。

5.3.2.1.1. 入力

下記の入力ファイルを読み込むための設定例を示す。

入力ファイル例
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000
変換対象クラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}

上記のファイルを読む込むための設定は以下のとおり。

Bean定義例
<!-- (1) (2) (3) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:encoding="MS932"
      p:strict="true">
  <property name="lineMapper">  <!-- (4) -->
    <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
      <property name="lineTokenizer">  <!-- (5) -->
        <!-- (6) (7) (8) -->
        <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
              p:names="branchId,year,month,customerId,amount"
              p:delimiter=","
              p:quoteCharacter='"'/>
      </property>
      <property name="fieldSetMapper">  <!-- (9) -->
        <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
              p:targetType="org.terasoluna.batch.functionaltest.app.model.plan.SalesPlanDetail"/>
      </property>
    </bean>
  </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

入力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

strict

trueを設定すると、入力ファイルが存在しない(開けない)場合に例外が発生する。

true

(4)

lineMapper

org.springframework.batch.item.file.mapping.DefaultLineMapperを設定する。
DefaultLineMapperは、設定されたLineTokenizerFieldSetMapperを用いてレコードを変換対象クラスへ変換する基本的な動作を提供するLineMapperである。

なし

(5)

lineTokenizer

org.springframework.batch.item.file.transform.DelimitedLineTokenizerを設定する。
DelimitedLineTokenizerは、区切り文字を指定してレコードを分割するLineTokenizerの実装クラス。
CSV形式の一般的書式とされるRFC-4180の仕様に定義されている、エスケープされた改行、区切り文字、囲み文字の読み込みに対応している。

なし

(6)

names

1レコードの各項目に名前を付与する。
FieldSetMapperで使われるFieldSetで設定した名前を用いて各項目を取り出すことができるようになる。
レコードの先頭から各名前をカンマ区切りで設定する。
BeanWrapperFieldSetMapperを利用する場合は、必須設定である。

なし

(7)

delimiter

区切り文字を設定する

カンマ

(8)

quoteCharacter

囲み文字を設定する

なし

(9)

fieldSetMapper

文字列や数字など特別な変換処理が不要な場合は、org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapperを利用し、 プロパティtargetTypeに変換対象クラスを指定する。 これにより、(5)で設定した各項目の名前と一致するフィールドに値を自動的に設定したインスタンスを生成する。
変換処理が必要な場合は、org.springframework.batch.item.file.mapping.FieldSetMapperの実装クラスを設定する。

なし

FieldSetMapperの独自実装について

FieldSetMapperを独自に実装する場合については、How To Extendを参照すること。

TSV形式ファイルの入力方法

TSVファイルの読み込みを行う場合には、区切り文字にタブを設定することで実現可能である。

TSVファイル読み込み時:区切り文字設定例(定数による設定)
<property name="delimiter">
    <util:constant
            static-field="org.springframework.batch.item.file.transform.DelimitedLineTokenizer.DELIMITER_TAB"/>
</property>

または、以下のようにしてもよい。

TSVファイル読み込み時:区切り文字設定例(文字参照による設定)
<property name="delimiter" value="&#09;"/>
5.3.2.1.2. 出力

下記の出力ファイルを書き出すための設定例を示す。

出力ファイル例
001,CustomerName001,CustomerAddress001,11111111111,001
002,CustomerName002,CustomerAddress002,11111111111,002
003,CustomerName003,CustomerAddress003,11111111111,003
変換対象クラス
public class Customer {

    private String customerId;
    private String customerName;
    private String customerAddress;
    private String customerTel;
    private String chargeBranchId;
    private Timestamp createDate;
    private Timestamp updateDate;

    // omitted getter/setter
}

上記のファイルを書き出すための設定は以下のとおり。

Bean定義例
<!-- Writer -->
<!-- (1) (2) (3) (4) (5) (6) (7) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:encoding="MS932"
      p:lineSeparator="&#x0A;"
      p:appendAllowed="true"
      p:shouldDeleteIfEmpty="true"
      p:shouldDeleteIfExists="false"
      p:transactional="true">
  <property name="lineAggregator">  <!-- (8) -->
    <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator"
          p:delimiter=",">  <!-- (9) -->
      <property name="fieldExtractor">  <!-- (10) -->
        <!-- (11) -->
        <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
              p:names="customerId,customerName,customerAddress,customerTel,chargeBranchId">
      </property>
    </bean>
  </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

出力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

lineSeparator

レコード区切り(改行コード)を設定する。

システムプロパティのline.separator

(4)

appendAllowed

trueの場合、既存のファイルに追記をする。

false

(5)

shouldDeleteIfEmpty

trueの場合、出力結果が空ファイルであれば削除する。

false

(6)

shouldDeleteIfExists

trueの場合、既にファイルが存在すれば削除する。
falseの場合、既にファイルが存在すれば例外をスローする。

true

(7)

transactional

トランザクション制御を行うかを設定する。詳細は、トランザクション制御を参照のこと。

true

(8)

lineAggregator

org.springframework.batch.item.file.transform.DelimitedLineAggregatorを設定する。
フィールドを囲み文字で囲む場合は、org.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregatorを設定する。
EnclosableDelimitedLineAggregatorの使用方法は後述する。

なし

(9)

delimiter

区切り文字を設定する。

カンマ

(10)

fieldExtractor

文字列や数字など特別な変換処理が不要な場合は、org.springframework.batch.item.file.transform.BeanWrapperFieldExtractorが利用できる。
変換処理が必要な場合は、org.springframework.batch.item.file.transform.FieldExtractorの実装クラスを設定する。
FieldExtractorの実装例は固定長ファイルの出力にて、全角文字のフォーマットを例に説明しているためそちらを参照すること。

なし

(11)

names

1レコードの各項目に名前を付与する。 レコードの先頭から各名前をカンマ区切りで設定する。

なし

EnclosableDelimitedLineAggregatorの使用方法

フィールドを囲み文字で囲む場合は、TERASOLUNA Batch 5.xが提供するorg.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregatorを使用する。
EnclosableDelimitedLineAggregatorの仕様は以下のとおり。

  • 囲み文字、区切り文字を任意に指定可能

    • デフォルトはCSV形式で一般的に使用される以下の値である

      • 囲み文字:"(ダブルクォート)

      • 区切り文字:,(カンマ)

  • フィールドに行頭復帰、改行、囲み文字、区切り文字が含まれている場合、囲み文字でフィールドを囲む

    • 囲み文字が含まれている場合、直前に囲み文字を付与しエスケープする

    • 設定によってすべてのフィールドを囲み文字で囲むことが可能

EnclosableDelimitedLineAggregatorの使用方法を以下に示す。

出力ファイル例
"001","CustomerName""001""","CustomerAddress,001","11111111111","001"
"002","CustomerName""002""","CustomerAddress,002","11111111111","002"
"003","CustomerName""003""","CustomerAddress,003","11111111111","003"
変換対象クラス
// 上記の例と同様
Bean定義例(lineAggregatorの設定のみ)
<property name="lineAggregator">  <!-- (1) -->
  <!-- (2) (3) (4) -->
  <bean class="org.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregator"
        p:delimiter=","
        p:enclosure='"'
        p:allEnclosing="true">
      <property name="fieldExtractor">
        <!-- omitted settings -->
      </property>
  </bean>
</property>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

lineAggregator

org.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregatorを設定する。

なし

(2)

delimiter

区切り文字を設定する。

カンマ

(3)

enclosure

囲み文字を設定する。
囲み文字がフィールドに含まれる場合は、エスケープ処理として囲み文字を2つ連結されたものへ置換される。

ダブルクォート

(4)

allEnclosing

trueの場合、すべてのフィールドが囲み文字で囲まれる。
falseの場合フィールド内に行頭復帰(CR)、改行(LF)、区切り文字、囲み文字が含まれるフィールドのみ囲み文字で囲まれる。

false

EnclosableDelimitedLineAggregatorの提供について

TERASOLUNA Batch 5.xでは、RFC-4180の仕様を満たすことを目的として拡張クラスorg.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregatorを提供している。

Spring Batchが提供しているorg.springframework.batch.item.file.transform.DelimitedLineAggregatorはフィールドを囲み文字で囲む処理に対応しておらず、RFC-4180の仕様を満たすことができないためである。 Spring Batch/BATCH-2463 を参照のこと。

CSV形式のフォーマットについて、CSV形式の一般的書式とされるRFC-4180では下記のように定義されている。

  • フィールドに改行、囲み文字、区切り文字が含まれていない場合、各フィールドはダブルクォート(囲み文字)で囲んでも囲わなくてもよい

  • 改行(CRLF)、ダブルクォート(囲み文字)、カンマ(区切り文字)を含むフィールドは、ダブルクォートで囲むべきである

  • フィールドがダブルクォート(囲み文字)で囲まれている場合、フィールドの値に含まれるダブルクォートは、その直前に1つダブルクォートを付加して、エスケープしなければならない

TSV形式ファイルの出力方法

TSVファイルの出力を行う場合には、区切り文字にタブを設定することで実現可能である。

TSVファイル出力時の区切り文字設定例(定数による設定)
<property name="delimiter">
    <util:constant
            static-field="org.springframework.batch.item.file.transform.DelimitedLineTokenizer.DELIMITER_TAB"/>
</property>

または、以下のようにしてもよい。

TSVファイル出力時の区切り文字設定例(文字参照による設定)
<property name="delimiter" value="&#09;"/>
5.3.2.2. 固定長レコード

固定長レコードファイルを扱う場合の定義方法を説明する。

5.3.2.2.1. 入力

下記の入力ファイルを読み込むための設定例を示す。

TERASOLUNA Batch 5.xでは、レコードの区切りを改行で判断する形式とバイト数で判断する形式 に対応している。

入力ファイル例1(レコードの区切りは改行)
売上012016 1   00000011000000000
売上022017 2   00000022000000000
売上032018 3   00000033000000000
入力ファイル例2(レコードの区切りはバイト数、32バイトで1レコード)
売上012016 1   00000011000000000売上022017 2   00000022000000000売上032018 3   00000033000000000
入力ファイル仕様
項番 フィールド名 データ型 バイト数

(1)

branchId

String

6

(2)

year

int

4

(3)

month

int

2

(4)

customerId

String

10

(5)

amount

BigDecimal

10

変換対象クラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}

上記のファイルを読む込むための設定は以下のとおり。

Bean定義例
<!-- (1) (2) (3) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:encoding="MS932"
      p:strict="true">
    <property name="bufferedReaderFactory">  <!-- (4) -->
        <bean class="org.springframework.batch.item.file.DefaultBufferedReaderFactory"/>
    </property>
    <property name="lineMapper">  <!-- (5) -->
        <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer">  <!-- (6) -->
                <!-- (7) -->
                <!-- (8) -->
                <!-- (9) -->
                <bean class="org.terasoluna.batch.item.file.transform.FixedByteLengthLineTokenizer"
                      p:names="branchId,year,month,customerId,amount"
                      c:ranges="1-6, 7-10, 11-12, 13-22, 23-32"
                      c:charset="MS932" />
            </property>
            <property name="fieldSetMapper">  <!-- (10) -->
              <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
                    p:targetType="org.terasoluna.batch.functionaltest.app.model.plan.SalesPlanDetail"/>
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

入力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

strict

trueを設定すると、入力ファイルが存在しない(開けない)場合に例外が発生する。

true

(4)

bufferedReaderFactory

レコードの区切りを改行で判断する場合は、デフォルト値であるorg.springframework.batch.item.file.DefaultBufferedReaderFactoryを使用する。 DefaultBufferedReaderFactoryが生成するBufferedReaderは改行までを1レコードとして取得する。

レコードの区切りをバイト数で判断する場合は、TERASOLUNA Batch 5.xが提供するorg.terasoluna.batch.item.file.FixedByteLengthBufferedReaderFactoryを設定する。 FixedByteLengthBufferedReaderFactoryが生成するBufferedReaderは指定したバイト数までを1レコードとして取得する。
FixedByteLengthBufferedReaderFactoryの詳しい仕様および使用方法は後述する。

DefaultBufferedReaderFactory

(5)

lineMapper

org.springframework.batch.item.file.mapping.DefaultLineMapperを設定する。

なし

(6)

lineTokenizer

TERASOLUNA Batch 5.xが提供するorg.terasoluna.batch.item.file.transform.FixedByteLengthLineTokenizerを設定する。

なし

(7)

names

1レコードの各項目に名前を付与する。
FieldSetMapperで使われるFieldSetで設定した名前を用いて各項目を取り出すことができるようになる。
レコードの先頭から各名前をカンマ区切りで設定する。
BeanWrapperFieldSetMapperを利用する場合は、必須設定である。

なし

(8)

ranges
(コンストラクタ引数)

区切り位置を設定する。レコードの先頭から区切り位置をカンマ区切りで設定する。
各区切り位置の単位はバイトであり、開始位置-終了位置形式で指定する。
区切り位置を設定した順番でレコードから指定された範囲を取得し、FieldSetに格納される。
(6)のnamesを指定した場合は区切り位置を設定した順番でnamesと対応付けてFieldSetに格納される。

なし

(9)

charset
(コンストラクタ引数)

(2)で指定した文字コードと同じ値を設定する。

なし

(10)

fieldSetMapper

文字列や数字など特別な変換処理が不要な場合は、org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapperを利用し、 プロパティtargetTypeに変換対象クラスを指定する。 これにより、(6)で設定した各項目の名前と一致するフィールドに値を自動的に設定したインスタンスを生成する。
変換処理が必要な場合は、org.springframework.batch.item.file.mapping.FieldSetMapperの実装クラスを設定する。

なし

FieldSetMapperの独自実装について

FieldSetMapperを独自に実装する場合については、How To Extendを参照すること。

FixedByteLengthBufferedReaderFactoryの使用方法

レコードの区切りをバイト数で判断するファイルを読み込む場合は、TERASOLUNA Batch 5.xが提供するorg.terasoluna.batch.item.file.FixedByteLengthBufferedReaderFactoryを使用する。

FixedByteLengthBufferedReaderFactoryを使用することで指定したバイト数までを1レコードとして取得することができる。
FixedByteLengthBufferedReaderFactoryの仕様は以下のとおり。

  • コンストラクタ引数としてレコードのバイト数を指定する

  • 指定されたバイト数を1レコードとしてファイルを読み込むFixedByteLengthBufferedReaderを生成する

FixedByteLengthBufferedReaderの使用は以下のとおり。

  • インスタンス生成時に指定されたバイト長を1レコードとしてファイルを読み込む

  • 改行コードが存在する場合、破棄せず1レコードのバイト長に含めて読み込みを行う

  • 読み込み時に使用するファイルエンコーディングはFlatFileItemWriterに設定したものがBufferedReader生成時に設定される

FixedByteLengthBufferedReaderFactoryの定義方法を以下に示す。

<property name="bufferedReaderFactory">
    <bean class="org.terasoluna.batch.item.file.FixedByteLengthBufferedReaderFactory"
        c:byteLength="32"/>  <!-- (1) -->

</property>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

byteLength
(コンストラクタ引数)

1レコードあたりのバイト数を設定する。

なし

固定長ファイルを扱う場合に使用するコンポーネント

固定長ファイルを扱う場合は、TERASOLUNA Batch 5.xが提供するコンポーネントを使うことを前提にしている。

FixedByteLengthBufferedReaderFactory

改行なし固定長ファイルから、指定した文字コードのバイト数で1レコードを読み込むBufferedReader生成クラス

FixedByteLengthLineTokenizer

マルチバイト文字列に対応したバイト数区切りのFixedLengthTokenizer拡張クラス

マルチバイト文字列を含むレコードを処理する場合

マルチバイト文字列を含むレコードを処理する場合は、FixedByteLengthLineTokenizerを必ず利用する。
Spring Batchが提供するFixedLengthTokenizerは、レコードをバイト数ではなく文字数で区切ってしまうため、期待どおりの項目切り出しが行われない恐れがある。 この点についてはJIRAの Spring Batch/BATCH-2540 で報告しているため、今後不要になる可能性がある。

FieldSetMapperの実装については、How To Extendを参照すること。
5.3.2.2.2. 出力

下記の出力ファイルを書き出すための設定例を示す。

固定長ファイルを書き出すためには、Beanから取得した値をフィールドのバイト数にあわせてフォーマットを行う必要がある。
フォーマットの実行方法は全角文字が含まれるか否かによって下記のように異なる。

  • 全角文字が含まれない場合(半角文字のみであり文字のバイト数が一定)

    • FormatterLineAggregatorにてフォーマットを行う。

    • フォーマットは、String.formatメソッドで使用する書式で設定する。

  • 全角文字が含まれる場合(文字コードによって文字のバイト数が一定ではない)

    • FieldExtractorの実装クラスにてフォーマットを行う。

まず、出力ファイルに全角文字が含まれない場合の設定例を示し、その後全角文字が含まれる場合の設定例を示す。

出力ファイルに全角文字が含まれない場合の設定について下記に示す。

出力ファイル例
   0012016 10000000001  10000000
   0022017 20000000002  20000000
   0032018 30000000003  30000000
出力ファイル仕様
項番 フィールド名 データ型 バイト数

(1)

branchId

String

6

(2)

year

int

4

(3)

month

int

2

(4)

customerId

String

10

(5)

amount

BigDecimal

10

フィールドのバイト数に満たない部分は半角スペース埋めとしている。

変換対象クラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}

上記のファイルを書き出すための設定は以下のとおり。

Bean定義
<!-- Writer -->
<!-- (1) (2) (3) (4) (5) (6) (7) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:encoding="MS932"
      p:lineSeparator="&#x0A;"
      p:appendAllowed="true"
      p:shouldDeleteIfEmpty="true"
      p:shouldDeleteIfExists="false"
      p:transactional="true">
    <property name="lineAggregator">  <!-- (8) -->
        <bean class="org.springframework.batch.item.file.transform.FormatterLineAggregator"
              p:format="%6s%4s%2s%10s%10s"/>  <!-- (9) -->
            <property name="fieldExtractor">  <!-- (10) -->
              <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
                    p:names="branchId,year,month,customerId,amount"/>  <!-- (11) -->
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

出力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

lineSeparator

レコード区切り(改行コード)を設定する。
改行なしにする場合は、空文字を設定する。

システムプロパティのline.separator

(4)

appendAllowed

trueの場合、既存のファイルに追記をする。

false

(5)

shouldDeleteIfEmpty

trueの場合、出力結果が空ファイルであれば削除する。

false

(6)

shouldDeleteIfExists

trueの場合、既にファイルが存在すれば削除する。
falseの場合、既にファイルが存在すれば例外をスローする。

true

(7)

transactional

トランザクション制御を行うかを設定する。詳細は、トランザクション制御を参照

true

(8)

lineAggregator

org.springframework.batch.item.file.transform.FormatterLineAggregatorを設定する。

なし

(9)

format

String.formatメソッドで使用する書式で出力フォーマットを設定する。

なし

(10)

fieldExtractor

文字列や数字など特別な変換処理、全角文字のフォーマットが不要な場合は、org.springframework.batch.item.file.transform.BeanWrapperFieldExtractorが利用できる。

値の変換処理や全角文字をフォーマットする等の対応が必要な場合は、org.springframework.batch.item.file.transform.FieldExtractorの実装クラスを設定する。
全角文字をフォーマットする場合におけるFieldExtractorの実装例は後述する。

PassThroughFieldExtractor

(11)

names

1レコードの各項目に名前を付与する。 レコードの先頭から各フィールドの名前をカンマ区切りで設定する。

なし

PassThroughFieldExtractorとは

FormatterLineAggregatorがもつプロパティfieldExtractorのデフォルト値はorg.springframework.batch.item.file.transform.PassThroughFieldExtractorである。

PassThroughFieldExtractorは、元のアイテムに対して処理を行わずに返すクラスであり、FieldExtractorにて何も処理を行わない場合に使用する。

アイテムが配列またはコレクションの場合はそのまま返されるが、それ以外の場合は、単一要素の配列にラップされる。

全角文字が含まれるフィールドに対してフォーマットを行う際の設定例

全角文字に対するフォーマットを行う場合、文字コードにより1文字あたりのバイト数が異なるため、FormatterLineAggregatorではなく、FieldExtractorの実装クラスを使用する。

FieldExtractorの実装クラスは以下の要領で実装する。

  • FieldExtractorクラスを実装し、extractメソッドをオーバーライドする

  • extractメソッドは以下の要領で実装する

    • item(処理対象のBean)から値を取得し、適宜変換処理等を行う

    • Object型の配列に格納し返す

FieldExtractorの実装クラスで行う全角文字を含むフィールドのフォーマットは以下の要領で実装する。

  • 文字コードに対するバイト数を取得する

  • 取得したバイト数を元にパディング・トリム処理で整形する

以下に全角文字を含むフィールドをフォーマットする場合の設定例を示す。

出力ファイル例
   0012016 10000000001  10000000
  番号2017 2 売上高002  20000000
 番号32018 3   売上003  30000000

出力ファイルの使用は上記の例と同様。

Bean定義(lineAggregatorの設定のみ)
<property name="lineAggregator">  <!-- (1) -->
    <bean class="org.springframework.batch.item.file.transform.FormatterLineAggregator"
          p:format="%s%4s%2s%s%10s"/>  <!-- (2) -->
        <property name="fieldExtractor">  <!-- (3) -->
            <bean class="org.terasoluna.batch.functionaltest.ch05.fileaccess.plan.SalesPlanFixedLengthFieldExtractor"/>
        </property>
    </bean>
</property>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

lineAggregator

org.springframework.batch.item.file.transform.FormatterLineAggregatorを設定する。

なし

(2)

format

String.formatメソッドで使用する書式で出力フォーマットを設定する。
全角文字が含まれないフィールドに対してのみ桁数の指定をしている。

なし

(3)

fieldExtractor

FieldExtractorの実装クラスを設定する。
実装例は後述する。

PassThroughFieldExtractor

変換対象クラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}
全角文字をフォーマットするFieldExtractorの実装例
public class SalesPlanFixedLengthFieldExtractor implements FieldExtractor<SalesPlanDetail> {
    // (1)
    @Override
    public Object[] extract(SalesPlanDetail item) {
        Object[] values = new Object[5];  // (2)

        // (3)
        values[0] = fillUpSpace(item.getBranchId(), 6);  // (4)
        values[1] = item.getYear();
        values[2] = item.getMonth();
        values[3] = fillUpSpace(item.getCustomerId(), 10);  // (4)
        values[4] = item.getAmount();

        return values; // (7)
    }

    // It is a simple impl for example
    private String fillUpSpace(String val, int num) {
        String charsetName = "MS932";
        int len;
        try {
            len = val.getBytes(charsetName).length;  // (5)
        } catch (UnsupportedEncodingException e) {
            // omitted exception handling
        }

        String fillStr = "";
        for (int i = 0; i < (num - len); i++) { // (6)
            fillStr += " ";
        }

        return fillStr + val;
    }
}
設定内容の項目一覧
項番 説明

(1)

FieldExtractorクラスを実装し、extractメソッドをオーバーライドする。
FieldExtractorの型引数には変換対象クラスを設定する。

(2)

変換処理等を行ったデータを格納するためのObject型配列を定義する。

(3)

引数で受けたitem(処理対象のBean)から値を取得し、適宜変換処理を行い、Object型の配列に格納する。

(4)

全角文字が含まれるフィールドに対してフォーマット処理を行う。
フォーマット処理の詳細は(5)、(6)を参照すること。

(5)

文字コードに対するバイト数を取得する。

(6)

取得したバイト数を元にパディング・トリム処理で整形する。
実装例では指定されたバイト数まで文字列の前に空白を付与している。

(7)

処理結果を保持しているObject型の配列を返す。

5.3.2.3. 単一文字列レコード

単一文字列レコードファイルを扱う場合の定義方法を説明する

5.3.2.3.1. 入力

下記の入力ファイルを読み込むための設定例を示す。

入力ファイル例
Summary1:4,000,000,000
Summary2:5,000,000,000
Summary3:6,000,000,000

上記のファイルを読む込むための設定は以下のとおり。

Bean定義
<!-- (1) (2) (3) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:encoding="MS932"
      p:strict="true">
    <property name="lineMapper">  <!-- (4) -->
        <bean class="org.springframework.batch.item.file.mapping.PassThroughLineMapper"/>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

入力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

strict

trueを設定すると、入力ファイルが存在しない(開けない)場合に例外が発生する。

true

(4)

lineMapper

org.springframework.batch.item.file.mapping.PassThroughLineMapperを設定する。
PassThroughLineMapperは渡されたレコードをそのまま文字列として返すLineMapperの実装クラスである。

なし

5.3.2.3.2. 出力

下記の出力ファイルを書き出すための設定例を示す。

出力ファイル例
Summary1:4,000,000,000
Summary2:5,000,000,000
Summary3:6,000,000,000
Bean定義
<!-- Writer -->
<!-- (1) (2) (3) (4) (5) (6) (7) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:encoding="MS932"
      p:lineSeparator="&#x0A;"
      p:appendAllowed="true"
      p:shouldDeleteIfEmpty="true"
      p:shouldDeleteIfExists="false"
      p:transactional="true">
    <property name="lineAggregator">  <!-- (8) -->
        <bean class="org.springframework.batch.item.file.transform.PassThroughLineAggregator"/>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

出力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

lineSeparator

レコード区切り(改行コード)を設定する。

システムプロパティのline.separator

(4)

appendAllowed

trueの場合、既存のファイルに追記をする。

false

(5)

shouldDeleteIfEmpty

trueの場合、出力結果が空ファイルであれば削除する。

false

(6)

shouldDeleteIfExists

trueの場合、既にファイルが存在すれば削除する。
falseの場合、既にファイルが存在すれば例外をスローする。

true

(7)

transactional

トランザクション制御を行うかを設定する。詳細は、トランザクション制御を参照。

true

(8)

lineAggregator

org.springframework.batch.item.file.transform.PassThroughLineAggregatorを設定する。
PassThroughLineAggregatorはitem(処理対象のBean)をそのまま文字列へ変換(item.toString()を実行)するLineAggregatorの実装クラスである。

なし

5.3.2.4. ヘッダとフッタ

ヘッダ・フッタがある場合の入出力方法を説明する。

ここでは行数指定にてヘッダ・フッタを読み飛ばす方法を説明する。
ヘッダ・フッタのレコード数が可変であり行数指定ができない場合は、マルチフォーマットの入力を参考にPatternMatchingCompositeLineMapperを使用すること。

5.3.2.4.1. 入力
ヘッダの読み飛ばし

ヘッダレコードを読み飛ばす方法には以下に示す2パターンがある。

  • FlatFileItemReaderlinesToSkipにファイルの先頭から読み飛ばす行数を設定

  • OSコマンドによる前処理でヘッダレコードを取り除く

入力ファイル例
sales_plan_detail_11
branchId,year,month,customerId,amount
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000

先頭から2行がヘッダレコードである。

上記のファイルを読む込むための設定は以下のとおり。

linesToSkipによる読み飛ばし
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:linesToSkip=value="2">  <!-- (1) -->
    <property name="lineMapper">
        <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

linesToSkip

読み飛ばすヘッダ行数を設定する。

0

OSコマンドによる読み飛ばし処理
# Remove number of lines in header from the top of input file
tail -n +`expr 2 + 1` input.txt > output.txt

tailコマンドを利用し、入力ファイルinput.txtの3行目以降を取得し、output.txtに出力している。 tailコマンドのオプション-n +Kに指定する値はヘッダレコードの数+1となるため注意すること。

ヘッダレコードとフッタレコードを読み飛ばすOSコマンド

headコマンドとtailコマンドをうまく活用することでヘッダレコードとフッタレコードを行数指定をして読み飛ばすことが可能である。

ヘッダレコードの読み飛ばし方

tailコマンドをオプション-n +Kを付与して実行することで、処理対象のK行目以降を取得する。

フッタレコードの読み飛ばし方

headコマンドをオプション-n -Kを付与して実行することで、処理対象の末尾からK行目より前を取得する。

ヘッダレコードとフッタレコードをそれぞれ読み飛ばすシェルスクリプト例を下記に示す。

ヘッダ/フッタから指定行数を取り除くシェルスクリプトの例
#!/bin/bash

if [ $# -ne 4 ]; then
  echo "The number of arguments must be 4, given is $#." 1>&2
  exit 1
fi

# Input file.
input=$1

# Output file.
output=$2

# Number of lines in header.
header=$3

# Number of lines in footer.
footer=$4

# Remove number of lines in header from the top of input file
# and number of lines in footer from the end,
# and save to output file.
tail -n +`expr ${header} + 1` ${input} | head -n -${footer} > ${output}
引数
項番 説明

(1)

入力ファイル

(2)

出力ファイル

(3)

読み飛ばすヘッダの行数

(4)

読み飛ばすフッタの行数

ヘッダ情報の取り出し

ヘッダレコードを認識し、ヘッダレコードの情報を取り出す方法を示す。

ヘッダ情報の取り出しは以下の要領で実装する。

設定
  • org.springframework.batch.item.file.LineCallbackHandlerの実装クラスにヘッダに対する処理を実装する

    • LineCallbackHandler#handleLine()内で取得したヘッダ情報をstepExecutionContextに格納する

  • FlatFileItemReaderskippedLinesCallbackLineCallbackHandlerの実装クラスを設定する

  • FlatFileItemReaderlinesToSkipにヘッダの行数を指定する

ファイル読み込みおよびヘッダ情報の取り出し
  • linesToSkipの設定によってスキップされるヘッダレコード1行ごとにLineCallbackHandler#handleLine()が呼び出される

    • ヘッダ情報がstepExecutionContextに格納される

取得したヘッダ情報を利用する
  • ヘッダ情報をstepExecutionContextから取得してデータ部の処理で利用する

ヘッダレコードの情報を取り出す際の実装例を示す。

Bean定義
<bean id="lineCallbackHandler"
      class="org.terasoluna.batch.functionaltest.ch05.fileaccess.module.HoldHeaderLineCallbackHandler"/>

<!-- (1) (2) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:linesToSkip="2"
      p:skippedLinesCallback-ref="lineCallbackHandler"
      p:resource="file:#{jobParameters[inputFile]}">
    <property name="lineMapper">
        <!-- omitted settings -->
    </property>
</bean>

<batch:job id="jobReadCsvSkipAndReferHeader" job-repository="jobRepository">
    <batch:step id="jobReadCsvSkipAndReferHeader.step01">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="reader"
                         processor="loggingHeaderRecordItemProcessor"
                         writer="writer" commit-interval="10"/>
            <batch:listeners>
                <batch:listener ref="lineCallbackHandler"/>  <!-- (3) -->
            </batch:listeners>
        </batch:tasklet>
    </batch:step>
</batch:job>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

linesToSkip

読み飛ばすヘッダ行数を設定する。

0

(2)

skippedLinesCallback

LineCallbackHandlerの実装クラスを設定する。
実装例は後述する。

なし

(2)

listener

StepExecutionListenerの実装クラスを設定する。
FlatFileItemReaderskippedLinesCallbackに指定するLineCallbackHandlerは自動でListenerとして登録されないため設定が必須となる。
詳しい理由は後述する。

なし

リスナー設定について

下記の2つの場合は自動でListenerとして登録されないため、ジョブ定義時にListenersにも定義を追加する必要がある。
(リスナの定義を追加しないと、StepExecutionListener#beforeStep()が実行されない)

  • FlatFileItemReaderskippedLinesCallbackに指定するLineCallbackHandlerStepExecutionListener

  • Taskletの実装クラスに実装するStepExecutionListener

    <batch:job id="jobReadCsvSkipAndReferHeader" job-repository="jobRepository">
        <batch:step id="jobReadCsvSkipAndReferHeader.step01">
            <batch:tasklet transaction-manager="jobTransactionManager">
                <batch:chunk reader="reader"
                             processor="loggingHeaderRecordItemProcessor"
                             writer="writer" commit-interval="10"/>
                <batch:listeners>
                    <batch:listener ref="loggingItemReaderListener"/>
                    <!-- mandatory -->
                    <batch:listener ref="lineCallbackHandler"/>
                </batch:listeners>
            </batch:tasklet>
        </batch:step>
    </batch:job>

LineCallbackHandlerは以下の要領で実装する。

  • StepExecutionListener#beforeStep()の実装

    • 下記のいずれかの方法でStepExecutionListener#beforeStep()を実装する

      • StepExecutionListenerクラスを実装し、beforeStepメソッドをオーバーライドする

      • beforeStepメソッドを実装し、@BeforeStepアノテーションを付与する

    • beforeStepメソッドにてStepExecutionを取得してクラスフィールドに保持する

  • LineCallbackHandler#handleLine()の実装

    • LineCallbackHandlerクラスを実装し、handleLineメソッドをオーバーライドする

      • handleLineメソッドはスキップする1行ごとに1回呼ばれる点に注意すること。

    • StepExecutionからstepExecutionContextを取得し、stepExecutionContextにヘッダ情報を格納する。

LineCallbackHandlerの実装例
@Component
public class HoldHeaderLineCallbackHandler implements LineCallbackHandler {  // (!)
    private StepExecution stepExecution;  // (2)

    @BeforeStep  // (3)
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;  // (4)
    }

    @Override  // (5)
    public void handleLine(String line) {
        this.stepExecution.getExecutionContext().putString("header", line);  // (6)
    }
}
設定内容の項目一覧
項番 説明

(1)

LineCallbackHandlerクラスを実装し、handleLineメソッドをオーバーライドする。

(2)

StepExecutionを保持するためのフィールドを定義する。

(3)

beforeStepメソッドを実装し、@BeforeStepアノテーションを付与する。
シグネチャはvoid beforeStep(StepExecution stepExecution)とする。
StepExecutionListenerクラスを実装し、beforeStepメソッドをオーバーライドする方法でもよい。

(4)

StepExecutionを取得してクラスフィールドに保持する。

(5)

LineCallbackHandlerクラスを実装し、handleLineメソッドをオーバーライドする。

(6)

StepExecutionからstepExecutionContextを取得し、headerというキーを指定してstepExecutionContextにヘッダ情報を格納する。
ここでは簡単のため、スキップする2行のうち、最後の1行だけを格納している。

ヘッダ情報をstepExecutionContextから取得してデータ部の処理で利用する例を示す。
ItemProcessorにてヘッダ情報を利用する場合を例にあげて説明する。
他のコンポーネントでヘッダ情報を利用する際も同じ要領で実現することができる。

ヘッダ情報を利用する処理は以下の要領で実装する。

  • LineCallbackHandlerの実装例と同様にStepExecutionListener#beforeStep()を実装する

  • beforeStepメソッドにてStepExecutionを取得してクラスフィールドに保持する

  • StepExecutionからstepExecutionContextおよびヘッダ情報を取得して利用する

ヘッダ情報の利用例
@Component
public class LoggingHeaderRecordItemProcessor implements
        ItemProcessor<SalesPlanDetail, SalesPlanDetail> {
    private StepExecution stepExecution;  // (1)

    @BeforeStep  // (2)
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;  // (3)
    }

    @Override
    public SalesPlanDetail process(SalesPlanDetail item) throws Exception {
        String headerData = this.stepExecution.getExecutionContext()
                .getString("header");  // (4)
        // omitted business logic
        return item;
    }
}
設定内容の項目一覧
項番 説明

(1)

StepExecutionを保持するためのフィールドを定義する。

(2)

beforeStepメソッドを実装し、@BeforeStepアノテーションを付与する。
シグネチャはvoid beforeStep(StepExecution stepExecution)とする。
StepExecutionListenerクラスを実装し、beforeStepメソッドをオーバーライドする方法でもよい。

(3)

StepExecutionを取得してクラスフィールドに保持する。

(4)

StepExecutionからstepExecutionContextを取得し、headerというキーを指定してstepExecutionContextからヘッダ情報を取得する。

Job/StepのExecutionContextの使用について

ヘッダ(フッタ)情報の取出しでは、読み込んだヘッダ情報をStepExecutionExecutionContextに格納しておき、使用する際にExecutionContextから取り出す方式をとる。

下記の例では1つのステップ内でヘッダ情報の取得および利用を行うためStepExecutionExecutionContextへヘッダ情報を格納している。 ヘッダ情報の取得および利用にてステップが分かれる場合はJobExecutionExecutionContextを利用すること。

Job/StepのExecutionContextに関する詳細は、Spring Batchのアーキテクチャを参照すること。

フッタの呼び飛ばし

Spring BatchおよびTERASOLUNA Batch 5.xでは、フッタレコードの読み飛ばし機能は提供していないため、OSコマンドで対応する。

入力ファイル例
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000
number of items,3
total of amounts,6000000000

末尾から2行がフッタレコードである。

上記のファイルを読む込むための設定は以下のとおり。

OSコマンドによる読み飛ばし処理
# Remove number of lines in footer from the end of input file
head -n -2 input.txt > output.txt

headコマンドを利用し、入力ファイルinput.txtの末尾から2行目より前を取得し、output.txtに出力している。

フッタレコードを読み飛ばすことは、Spring Batchでは機能をもっていないため、JIRAの Spring Batch/BATCH-2539 にて報告している。
よって、今後OSコマンドによる読み飛ばしだけではなく、Spring Batchでも対応できるようになる可能性がある。

フッタ情報の取り出し

Spring BatchおよびTERASOLUNA Batch 5.xでは、フッタレコードの読み飛ばし機能、フッタ情報の取得機能は提供していない。

そのため、処理を下記ようにOSコマンドによる前処理と2つのステップのに分割することで対応する。

  • OSコマンドによってフッタレコードを分割する

  • 1つめのステップにてフッタレコードを読み込み、フッタ情報をExecutionContextに格納する

  • 2つめのステップにてExecutionContextからフッタ情報を取得し、利用する

フッタ情報を取り出しは以下の要領で実装する。

OSコマンドによるフッタレコードの分割
  • OSコマンドを利用して入力ファイルをフッタ部とフッタ部以外に分割する

1つめのステップでフッタレコードを読み込み、フッタ情報を取得する
  • フッタレコードを読み込みjobExecutionContextに格納する

    • フッタ情報の格納と利用にてステップが異なるため、jobExecutionContextに格納する。

    • jobExecutionContextを利用する方法は、JobとStepのスコープに関する違い以外は、ヘッダ情報の取り出しにて説明したstepExecutionContextと同様である。

2つめのステップにて取得したフッタ情報を利用する
  • フッタ情報をjobExecutionContextから取得してデータ部の処理で利用する

以下に示すファイルのフッタ情報を取り出して利用する場合を例にあげて説明する。

入力ファイル例
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000
number of items,3
total of amounts,6000000000

末尾から2行がフッタレコードである。

OSコマンドによるフッタレコードの分割

上記のファイルをOSコマンドを利用してフッタ部とフッタ部以外に分割する設定は以下のとおり。

OSコマンドによる読み飛ばし処理
# Extract non-footer record from input file and save to output file.
head -n -2 input.txt > input_data.txt

# Extract footer record from input file and save to output file.
tail -n 2 input.txt > input_footer.txt

headコマンドを利用し、入力ファイルinput.txtのフッタ部以外をinput_data.txtへ、フッタ部をinput_footer.txtに出力している。

出力ファイル例は以下のとおり。

出力ファイル例(input_data.txt)
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000
出力ファイル例(input_footer.txt)
number of items,3
total of amounts,6000000000
フッタ情報の取得、利用

OSコマンドにて分割したフッタレコードからフッタ情報を取得、利用する方法を説明する。

フッタレコードを読み込むステップを前処理として主処理とステップを分割している。
ステップの分割に関する詳細は、フロー制御を参照すること。

下記の例ではフッタ情報を取得し、jobExecutionContextへフッタ情報を格納するまでの例を示す。
jobExecutionContextからフッタ情報を取得し利用する方法はヘッダ情報の取り出しと同じ要領で実現可能である。

データレコードの情報を保持するクラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}
フッタレコードの情報を保持するクラス
public class SalesPlanDetailFooter implements Serializable {

    // omitted serialVersionUID

    private String name;
    private String value;

    // omitted getter/setter
}

下記の要領でBean定義を行う。

  • フッタレコードを読み込むItemReaderを定義する

  • データレコードを読み込むItemReaderを定義する

  • フッタレコードを取得するビジネスロジックを定義する

    • 下記の例ではTaskletの実装クラスで実現している

  • ジョブを定義する

    • フッタ情報を取得する前処理ステップとデータレコードを読み込み朱処理を行うステップを定義する

Bean定義
<!-- ItemReader for reading footer records -->
<!-- (1) -->
<bean id="footerReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[footerInputFile]}">
    <property name="lineMapper">
        <!-- omitted other settings -->
    </property>
</bean>

<!-- ItemReader for reading data records -->
<!-- (2) -->
<bean id="dataReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[dataInputFile]}">
    <property name="lineMapper">
        <!-- omitted other settings -->
    </property>
</bean>

<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
  <!-- omitted settings -->
</bean>

<!-- Tasklet for reading footer records -->
<bean id="readFooterTasklet"
      class="org.terasoluna.batch.functionaltest.ch05.fileaccess.module.ReadFooterTasklet"/>

<batch:job id="jobReadAndWriteCsvWithFooter" job-repository="jobRepository">
    <!-- (3) -->
    <batch:step id="jobReadAndWriteCsvWithFooter.step01"
            next="jobReadAndWriteCsvWithFooter.step02">
        <batch:tasklet ref="readFooterTasklet"
                       transaction-manager="jobTransactionManager"/>
    </batch:step>
    <!-- (4) -->
    <batch:step id="jobReadAndWriteCsvWithFooter.step02">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="dataReader"
                         writer="writer" commit-interval="10"/>
        </batch:tasklet>
    </batch:step>
    <batch:listeners>
        <batch:listener ref="readFooterTasklet"/> <!-- (5) -->
    </batch:listeners>
</batch:job>
設定内容の項目一覧
項番 項目 設定内容 必須 デフォルト値

(1)

footerReader

フッタレコードを保持するファイルを読み込むためのItemReaderを定義する。
フッタ情報を取得するステップで実行されるreadFooterTaskletにてインジェクトして使用する。

(2)

dataReader

データレコードを保持するファイルを読み込むためのItemReaderを定義する。

(3)

前処理ステップ

フッタ情報を取得するステップを定義する。
処理はreadFooterTaskletに実装している。実装例は後述する。

(4)

主処理ステップ

データ情報を取得するとともにフッタ情報を利用するステップを定義する。
readerにはdataReaderを使用する。
例ではフッタ情報をjobExecutionContextから取得し利用する処理(ItemProcessor等)は実装していない。
フッタ情報を取得し利用する方法はヘッダ情報の取り出しと同じ要領で実現可能である。

(5)

listeners

readFooterTaskletを設定する。
この設定を行わないとreadFooterTasklet内に実装するJobExecutionListener#beforeJob()が実行されない。
詳しい理由は、ヘッダ情報の取り出しを参照すること。

なし

フッタレコードを保持するファイルを読み込み、jobExecutionContextに格納する処理を行う処理の例を示す。

Taskletの実装クラスとして実現する際の要領は以下のとおり。

  • Bean定義したfooterReader@Injectアノテーションと@Namedアノテーションを使用し名前指定でインジェクトする。

  • 読み込んだフッタ情報をjobExecutionContextに格納する

フッタ情報の取得
public class ReadFooterTasklet implements Tasklet {
    // (1)
    @Inject
    @Named("footerReader")
    ItemStreamReader<SalesPlanDetailFooter> itemReader;

    private JobExecution jobExecution;

    @BeforeJob
    public void beforeJob(JobExecution jobExecution) {
        this.jobExecution = jobExecution;
    }

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {
        ArrayList<SalesPlanDetailFooter> footers = new ArrayList<>();

        // (2)
        itemReader.open(chunkContext.getStepContext().getStepExecution()
                .getExecutionContext());

        SalesPlanDetailFooter footer;
        while ((footer = itemReader.read()) != null) {
            footers.add(footer);
        }

        // (3)
        jobExecution.getExecutionContext().put("footers", footers);

        return RepeatStatus.FINISHED;
    }
}
設定内容の項目一覧
項番 説明

(1)

Bean定義したfooterReader@Injectアノテーションと@Namedアノテーションを使用し名前指定でインジェクトする。

(2)

footerReaderを使用してフッタレコードを保持したファイルを読み込みフッタ情報を取得する。
Taskletの実装クラス内でBean定義したItemReaderを使用する方法はタスクレット指向ジョブの作成を参照すること。

(3)

JobExecutionからjobExecutionContextを取得し、footersというキーを指定してjobExecutionContextへフッタ情報を格納する。

5.3.2.4.2. 出力
ヘッダ情報の出力

フラットファイルでヘッダ情報を出力する際は以下の要領で実装する。

  • org.springframework.batch.item.file.FlatFileHeaderCallbackの実装を行う

  • 実装したFlatFileHeaderCallbackFlatFileItemWriterheaderCallbackに設定する

    • headerCallbackを設定するとFlatFileItemWriterの出力処理で、最初にFlatFileHeaderCallback#writeHeader()が実行される

FlatFileHeaderCallbackは以下の要領で実装する。

  • FlatFileHeaderCallbackクラスを実装し、writeFooterメソッドをオーバーライドする

  • 引数で受けるWriterを用いてフッタ情報を出力する。

下記にFlatFileHeaderCallbackクラスの実装例を示す。

FlatFileHeaderCallbackの実装例
@Component
// (1)
public class WriteHeaderFlatFileFooterCallback implements FlatFileHeaderCallback {
    @Override
    public void writeHeader(Writer writer) throws IOException {
        // (2)
        writer.write("omitted");
    }
}
設定内容の項目一覧
項番 説明

(1)

FlatFileHeaderCallbackクラスを実装し、writeHeaderメソッドをオーバーライドする。

(2)

引数で受けるWriterを用いてフッタ情報を出力する。
FlatFileHeaderCallback#writeHeader()の実行直後にFlatFileItemWriterが出力する処理を実行する。
そのため、ヘッダ情報末尾の改行は出力不要である。 出力される改行は、Bean定義時にFlatFileItemWriterに指定したものである。

Bean定義
<!-- (1) (2) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:headerCallback-ref="writeHeaderFlatFileFooterCallback"
      p:lineSeparator="&#x0A;"
      p:resource="file:#{jobParameters[outputFile]}">
    <property name="lineAggregator">
        <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

headerCallback

FlatFileHeaderCallbackの実装クラスを設定する。

(2)

lineSeparator

レコード区切り(改行コード)を設定する。

システムプロパティのline.separator

FlatFileHeaderCallback実装時にヘッダ情報末尾の改行は出力不要

FlatFileItemWriter内でFlatFileHeaderCallback#writeHeader()の実行直後にBean定義時に指定した改行を出力する処理が実行されるため、ヘッダ情報末尾の改行は出力不要である。

フッタ情報の出力

フラットファイルでフッタ情報を出力する際は以下の要領で実装する。

  • org.springframework.batch.item.file.FlatFileFooterCallbackの実装を行う

  • 実装したFlatFileFooterCallbackFlatFileItemWriterfooterCallbackに設定する

    • footerCallbackを設定するとFlatFileItemWriterの出力処理で、最後にFlatFileFooterCallback#writeFooter()が実行される

フラットファイルでフッタ情報を出力する方法について説明する。

FlatFileFooterCallbackは以下の要領で実装する。

  • 引数で受けるWriterを用いてフッタ情報を出力する。

  • FlatFileFooterCallbackクラスを実装し、writeFooterメソッドをオーバーライドする

下記にJobのExecutionContextからフッタ情報を取得し、ファイルへ出力するFlatFileFooterCallbackクラスの実装例を示す。

フッタレコードの情報を保持するクラス
public class SalesPlanDetailFooter implements Serializable {

    // omitted serialVersionUID

    private String name;
    private String value;

    // omitted getter/setter
}
FlatFileFooterCallbackの実装例
@Component
public class WriteFooterFlatFileFooterCallback implements FlatFileFooterCallback {  // (1)
    private JobExecution jobExecution;

    @BeforeJob
    public void beforeJob(JobExecution jobExecution) {
        this.jobExecution = jobExecution;
    }

    @Override
    public void writeFooter(Writer writer) throws IOException {
        @SuppressWarnings("unchecked")
        ArrayList<SalesPlanDetailFooter> footers = (ArrayList<SalesPlanDetailFooter>) this.jobExecution.getExecutionContext().get("footers");  // (2)

        BufferedWriter bufferedWriter = new BufferedWriter(writer);  // (3)
        // (4)
        for (SalesPlanDetailFooter footer : footers) {
            bufferedWriter.write(footer.getName() +" is " + footer.getValue());
            bufferedWriter.newLine();
            bufferedWriter.flush();
        }
    }
}
設定内容の項目一覧
項番 説明

(1)

FlatFileFooterCallbackクラスを実装し、writeFooterメソッドをオーバーライドする。

(2)

JobのExecutionContextからfootersというkeyを指定してフッタ情報を取得する。
例ではArrayListで複数のフッタ情報を取得している。

(3)

例では改行の出力にBufferedWriter.newLine()を使用するため、引数で受けるWriterを引数としてBufferedWriterを生成する。

(4)

引数で受けるWriterを用いてフッタ情報を出力する。

Bean定義
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:footerCallback-ref="writeFooterFlatFileFooterCallback">  <!-- (1) -->
    <property name="lineAggregator">
        <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

footerCallback

FlatFileFooterCallbackの実装クラスを設定する。

5.3.2.5. 複数ファイル

複数ファイルを扱う場合の定義方法を説明する。

5.3.2.5.1. 入力

同一レコード形式の複数ファイルを読み込む場合は、org.springframework.batch.item.file.MultiResourceItemReaderを利用する。
MultiResourceItemReaderは指定されたItemReaderを使用し正規表現で指定された複数のファイルを読み込むことができる。

MultiResourceItemReaderは以下の要領で定義する。

  • MultiResourceItemReaderのBeanを定義する

    • プロパティresourcesに読み込み対象のファイルを指定する

      • 正規表現で複数ファイルを指定する

    • プロパティdelegateにファイル読み込みに利用するItemReaderを指定する

下記に示す複数のファイルを読み込むMultiResourceItemReaderの定義例は以下のとおりである。

読み込み対象ファイル(ファイル名)
sales_plan_detail_01.csv
sales_plan_detail_02.csv
sales_plan_detail_03.csv
Bean定義
<!-- (1) (2) -->
<bean id="multiResourceReader"
      class="org.springframework.batch.item.file.MultiResourceItemReader"
      scope="step"
      p:resources="file:input/sales_plan_detail_*.csv"
      p:delegate-ref="reader"/>
</bean>

<!-- (3) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="lineMapper">
      <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

正規表現で複数の入力ファイルを設定する。

なし

(2)

delegate

実際にファイルを読み込み処理するItemReaderを設定する。

なし

(3)

実際にファイルを読み込み処理するItemReader

プロパティresourceは、MultiResourceItemReaderから自動的に設定されるためBean定義に設定は不要である。

MultiResourceItemReaderが使用するItemReaderにresourceの指定は不要である

MultiResourceItemReaderから委譲されるItemReaderresourceは、MultiResourceItemReaderから自動的に設定されるためBean定義に設定は不要である。

5.3.2.5.2. 出力

複数ファイルを扱う場合の定義方法を説明する。

一定の件数ごとに異なるファイルへ出力する場合は、org.springframework.batch.item.file.MultiResourceItemWriterを利用する。

MultiResourceItemWriterは指定されたItemWriterを使用して指定した件数ごとに複数ファイルへ出力することができる。
出力対象のファイル名は重複しないように一意にする必要があるが、そのための仕組みとしてResourceSuffixCreatorが提供されている。
ResourceSuffixCreatorはファイル名が一意となるようなサフィックスを生成するクラスである。

たとえば、出力対象ファイルをoutputDir/customer_list_01.csv(01の部分は連番)というファイル名にしたい場合は下記のように設定する。

  • MultiResourceItemWriteroutputDir/customer_list_と設定する

  • サフィックス01.csv(01の部分は連番)を生成する処理をResourceSuffixCreatorに実装する

    • 連番はMultiResourceItemWriterから自動で増分されて渡される値を使用することができる

  • 実施に使用されるItemWriterにはoutputDir/customer_list_01.csvが設定される

MultiResourceItemWriterは以下の要領で定義する。ResourceSuffixCreatorの実装方法は後述する。

  • ResourceSuffixCreatorの実装クラスを定義する

  • MultiResourceItemWriterのBeanを定義する

    • プロパティresourcesに出力対象のファイルを指定する

      • ResourceSuffixCreatorの実装クラスで付与するサフィックスまでを設定

    • プロパティresourceSuffixCreatorにサフィックスを生成するResourceSuffixCreatorの実装クラスを指定する

    • プロパティdelegateにファイル読み込みに利用するItemWriterを指定する

    • プロパティitemCountLimitPerResourceに1ファイルあたりの出力件数を指定する

Bean定義
<!-- (1) (2) (3) (4) -->
<bean id="multiResourceItemWriter"
      class="org.springframework.batch.item.file.MultiResourceItemWriter"
      scope="step"
      p:resource="file:#{jobParameters[outputDir]}"
      p:resourceSuffixCreator-ref="customerListResourceSuffixCreator"
      p:delegate-ref="writer"
      p:itemCountLimitPerResource="4"/>
</bean>

<!-- (5) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter">
    <property name="lineAggregator">
        <!-- omitted settings -->
    </property>
</bean>

<bean id="customerListResourceSuffixCreator"
      class="org.terasoluna.batch.functionaltest.ch05.fileaccess.module.CustomerListResourceSuffixCreator"/>  <!-- (6) -->
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

出力対象ファイルのサフィックスを付与する前の状態を設定する。
ItemWriterには、MultiResourceItemWriterが自動でサフィックスを付与したものが設定される。

なし

(2)

resourceSuffixCreator

ResourceSuffixCreatorの実装クラスを設定する。
デフォルト値は"." + indexというサフィックスを生成するorg.springframework.batch.item.file.SimpleResourceSuffixCreatorである。

SimpleResourceSuffixCreator

(3)

delegate

実際にファイルを読み込み処理するItemWriterを設定する。

なし

(4)

itemCountLimitPerResource

1ファイルあたりの出力件数を設定する。

Integer.MAX_VALUE

(5)

ResourceSuffixCreatorの実装クラス

サフィックスを生成するResourceSuffixCreatorの実装クラスを定義する。
実装方法は後述する。

(6)

実際にファイルを読み込み処理するItemWriter

プロパティresourceは、MultiResourceItemWriterから自動的に設定されるためBean定義に設定は不要である。

MultiResourceItemWriterが使用するItemWriterにresourceの指定は不要である

MultiResourceItemWriterから委譲されるItemWriterresourceは、MultiResourceItemWriterから自動的に設定されるためBean定義に設定は不要である。

ResourceSuffixCreatorは以下の要領で実装する。

  • ResourceSuffixCreatorクラスを実装し、getSuffixメソッドをオーバーライドする

  • 引数で受けるindexを用いてサフィックスを生成して返り値として返す

    • indexは初期値1で始まり出力対象ファイルごとにインデントされるint型の値である

ResourceSuffixCreatorの実装例
// (1)
public class CustomerListResourceSuffixCreator implements ResourceSuffixCreator {
    @Override
    public String getSuffix(int index) {
        return String.format("%02d", index) + ".csv";  // (2)
    }
}
設定内容の項目一覧
項番 説明

(1)

ResourceSuffixCreatorクラスを実装し、getSuffixメソッドをオーバーライドする。

(2)

引数で受けるindexを用いてサフィックスを生成して返り値として返す。 indexは初期値1で始まり出力対象ファイルごとにインデントされるint型の値である。

5.3.2.6. コントロールブレイク

コントロールブレイクの実現方法について説明する。

コントロールブレイクとは

コントロールブレイク処理(またはキーブレイク処理)とは、ソート済みのレコードを順次読み込み、 レコード内にある特定の項目(キー項目)が同じレコードを1つのグループとして処理する手法のことを指す。
主にデータを集計するときに用いられ、 キー項目が同じ値の間は集計を続け、キー項目が異なる値になる際に集計値を出力する、 というアルゴリズムになる。

コントロールブレイク処理をするためには、グループの変わり目を判定するために、レコードを先読みする必要がる。 org.springframework.batch.item.support.SingleItemPeekableItemReaderを使うことで先読みを実現できる。
また、コントロールブレイクはタスクレットモデルでのみ処理可能とする。 これは、チャンクが前提とする「1行で定義するデータ構造をN行処理する」や「一定件数ごとのトランザクション境界」といった点が、 コントロールブレイクの「グループの変わり目で処理をする」という点と合わないためである。

コントロールブレイク処理の実行タイミングと比較条件を以下に示す。

  • 対象レコード処理前にコントロールブレイク実施

    • 前回読み取ったレコードを保持し、前回レコードと現在読み込んだレコードとの比較

  • 対象レコード処理後にコントロールブレイク実施

    • SingleItemPeekableItemReaderにより次のレコードを先読みし、次レコードと現在読み込んだレコードとの比較

下記にの入力データから処理結果を出力するコントロールブレイクの実装例を示す。

入力データ
01,2016,10,1000
01,2016,11,1500
01,2016,12,1300
02,2016,12,900
02,2016,12,1200
処理結果
Header Branch Id : 01,,,
01,2016,10,1000
01,2016,11,1500
01,2016,12,1300
Summary Branch Id : 01,,,3800
Header Branch Id : 02,,,
02,2016,12,900
02,2016,12,1200
Summary Branch Id : 02,,,2100
コントロールブレイクの実装例
@Component
public class ControlBreakTasklet implements Tasklet {

    @Inject
    SingleItemPeekableItemReader<SalesPerformanceDetail> reader; // (1)

    @Inject
    ItemStreamWriter<SalesPerformanceDetail> writer;

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {

        // omitted.

        SalesPerformanceDetail previousData = null;   // (2)
        BigDecimal summary = new BigDecimal(0);  //(3)

        List<SalesPerformanceDetail> items = new ArrayList<>();   // (4)

        try {
            reader.open(executionContext);
            writer.open(executionContext);

            while (reader.peek() != null) {   // (5)
                SalesPerformanceDetail data = reader.read(); // (6)

                // (7)
                if (isBreakByBranchId(previousData, data)) {
                    SalesPerformanceDetail beforeBreakData =
                            new SalesPerformanceDetail();
                    beforeBreakData.setBranchId("Header Branch Id : "
                              + currentData.getBranchId());
                    items.add(beforeBreakData);
                }

                // omitted.
                items.add(data);  // (8)

                SalesPerformanceDetail nextData = reader.peek();  // (9)
                summary = summary.add(data.getAmount());

                // (10)
                SalesPerformanceDetail afterBreakData = null;
                if (isBreakByBranchId(nextData, data)) {
                    afterBreakData = new SalesPerformanceDetail();
                    afterBreakData.setBranchId("Summary Branch Id : "
                            + currentData.getBranchId());
                    afterBreakData.setAmount(summary);
                    items.add(afterBreakData);
                    summary = new BigDecimal(0);
                    writer.write(items);  // (11)
                    items.clear();
                }
                previousData = data;  // (12)
            }
        } finally {
            try {
                reader.close();
            } catch (ItemStreamException e) {
            }
            try {
                writer.close();
            } catch (ItemStreamException e) {
            }
        }
        return RepeatStatus.FINISHED;
    }
    // (13)
    private boolean isBreakByBranchId(SalesPerformanceDetail o1,
            SalesPerformanceDetail o2) {
        return (o1 == null || !o1.getBranchId().equals(o2.getBranchId()));
    }
}
設定内容の項目一覧
項番 説明

(1)

SingleItemPeekableItemReaderをInjectする。

(2)

前回読み取ったレコードを保持する変数を定義する。

(3)

グループごとの集計値を格納する変数を定義する。

(4)

コントロールブレイクの処理結果を含めたグループ単位のレコードを格納する変数を定義する。

(5)

入力データが無くなるまで処理を繰り返す。

(6)

処理対象のレコードを読み込む。

(7)

対象レコード処理前にコントロールブレイクを実施する。
ここではグループの先頭であれば見出しを設定して、(4)で定義した変数に格納する。

(8)

対象レコードへの処理結果を(4)で定義した変数に格納する。

(9)

次のレコードを先読みする。

(10)

対象レコード処理後にコントロールブレイクを実施する。 ここではグループの末尾であれば集計データをトレーラに設定して、(4)で定義した変数に格納する。

(11)

グループ単位で処理結果を出力する。

(12)

処理レコードを(2)で定義した変数に格納する。

(13)

キー項目が切り替わったか判定する。

Bean定義
<!-- (1) -->
<bean id="reader"
      class="org.springframework.batch.item.support.SingleItemPeekableItemReader"
      p:delegate-ref="delegateReader" />  <!-- (2) -->

<!-- (3) -->
<bean id="delegateReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}">
    <property name="lineMapper">
        <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer">
                <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
                      p:names="branchId,year,month,customerId,amount"/>
            </property>
            <property name="fieldSetMapper">
                <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
                      p:targetType="org.terasoluna.batch.functionaltest.app.model.performance.SalesPerformanceDetail"/>
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 説明

(1)

SingleItemPeekableItemReaderをBean定義する。TaskletへのInject対象。

(2)

delegateプロパティに実際にファイルを読み込むItemReaderのBeanを指定する。

(3)

実際にファイルを読み込むItemReaderのBeanを定義する。

5.3.3. How To Extend

ここでは、以下のケースについて説明する。

5.3.3.1. FieldSetMapperの実装

FieldSetMapperを自作で実装する方法について説明する。

FieldSetMapperの実装クラスは下記の要領で実装する。

  • FieldSetMapperクラスを実装し、mapFieldSetメソッドをオーバーライドする

  • 引数で受けたFieldSetから値を取得し、適宜変換処理を行い、変換対象のBeanに格納し返り値として返す

    • FieldSetクラスはJDBCにあるResultSetクラスのようにインデックスまたは名前と関連付けてデータを保持するクラスである

    • FieldSetクラスはLineTokenizerによって分割されたレコードの各フィールドの値を保持する

    • インデックスまたは名前を指定して値を格納および取得することができる

下記のような和暦フォーマットのDate型やカンマを含むBigDecimal型の変換を行うファイルを読み込む場合の実装例を示す。

入力ファイル例
"000001","平成28年1月1日","000000001","1,000,000,000"
"000002","平成29年2月2日","000000002","2,000,000,000"
"000003","平成30年3月3日","000000003","3,000,000,000"
入力ファイル仕様
項番 フィールド名 データ型 備考

(1)

branchId

String

(2)

日付

Date

和暦フォーマット

(3)

customerId

String

(4)

amount

BigDecimal

カンマを含む

変換対象クラス
public class UseDateSalesPlanDetail {

    private String branchId;
    private Date date;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}
FieldSetMapperの実装例
@Component
public class UseDateSalesPlanDetailFieldSetMapper implements FieldSetMapper<UseDateSalesPlanDetail> {  // (1)
    /**
     * {@inheritDoc}
     *
     * @param fieldSet {@inheritDoc}
     * @return Sales performance detail.
     * @throws BindException {@inheritDoc}
     */
    @Override
    public UseDateSalesPlanDetail mapFieldSet(FieldSet fieldSet) throws BindException {
        UseDateSalesPlanDetail item = new UseDateSalesPlanDetail();  // (2)

        item.setBranchId(fieldSet.readString("branchId"));  // (3)

        // (4)
        DateFormat japaneseFormat = new SimpleDateFormat("GGGGy年M月d日", new Locale("ja", "JP", "JP"));
        try {
            item.setDate(japaneseFormat.parse(fieldSet.readString("date")));
        } catch (ParseException e) {
            // omitted exception handling
        }

        // (5)
        item.setCustomerId(fieldSet.readString("customerId"));

        // (6)
        DecimalFormat decimalFormat = new DecimalFormat();
        decimalFormat.setParseBigDecimal(true);
        try {
            item.setAmount((BigDecimal) decimalFormat.parse(fieldSet.readString("amount")));
        } catch (ParseException e) {
            // omitted exception handling
        }

        return item;  // (7)
    }
}
設定内容の項目一覧
項番 説明

(1)

FieldSetMapperクラスを実装し、mapFieldSetメソッドをオーバーライドする。 FieldSetMapperの型引数には変換対象クラスを設定する。

(2)

変換処理等を行ったデータを格納するために変換対象クラスの変数を定義する。

(3)

引数で受けたFieldSetからbranchIdを取得し、変換対象クラスの変数へ格納する。
branchIdは変換処理が不要であるため、変換処理等は行っていない。

(4)

引数で受けたFieldSetからdateを取得し、変換対象クラスの変数へ格納する。
和暦フォーマットの日付をDate型へ変換するため、SimpleDateFormatでフォーマットを指定している。

(5)

引数で受けたFieldSetからcustomerIdを取得し、変換対象クラスの変数へ格納する。
customerIdは変換処理が不要であるため、変換処理等は行っていない。

(4)

引数で受けたFieldSetからamountを取得し、変換対象クラスの変数へ格納する。
カンマを含む値をBigDecimal型へ変換するため、DecimalFormatを使用している。

(7)

処理結果を保持している変換対象クラスを返す。

FieldSetクラスからの値取得

FieldSetクラスは、下記のような格納された値を取得するための様々なデータ型に対応したメソッドをもつ。
また、FieldSet生成時にフィールドの名前と関連付けられてデータを格納した場合は、名前指定でのデータ取得、名前を指定しない場合ではインデックスを指定してのデータ取得が可能である。

  • readString()

  • readInt()

  • readBigDecimal()

など

5.3.3.2. XMLファイル

XMLファイルを扱う場合の定義方法を説明する。

BeanとXML間の変換処理(O/X (Object/XML) マッピング)にはSpring Frameworkが提供するライブラリを使用する。
XMLファイルとオブジェクト間の変換処理を行うライブラリとして、XStreamやJAXBなどを利用したMarshallerおよびUnmarshallerを実装クラスが提供されている。
状況に応じて適しているものを使用すること。

JAXBとXStreamを例に特徴と採用する際のポイントを説明する。

JAXB
  • 変換対象のBeanはBean定義にて指定する

  • スキーマファイルを用いたバリデーションを行うことができる

  • 対外的にスキーマを定義しており、入力ファイルの仕様が厳密に決まっている場合に有用である

XStream
  • Bean定義にて柔軟にXMLの要素とBeanのフィールドをマッピングすることができる

  • 柔軟にBeanマッピングする必要がある場合に有用である

ここでは、JAXBを利用する例を示す。

5.3.3.2.1. 入力

XMLファイルの入力にはSpring Batchが提供するorg.springframework.batch.item.xml.StaxEventItemReaderを使用する。
StaxEventItemReaderは指定したUnmarshallerを使用してXMLファイルをBeanにマッピングすることでXMLファイルを読み込むことができる。

StaxEventItemReaderは以下の要領で定義する。

  • XMLのルートエレメントとなる変換対象クラスに@XmlRootElementを付与する

  • StaxEventItemReaderに以下のプロパティを設定する

    • プロパティresourceに読み込み対象ファイルを設定する

    • プロパティfragmentRootElementNameにルートエレメントとなる要素の名前を設定する

    • プロパティunmarshallerorg.springframework.oxm.jaxb.Jaxb2Marshallerを設定する

  • Jaxb2Marshallerには以下のプロパティを設定する

    • プロパティclassesToBeBoundに変換対象のクラスをリスト形式で設定する

    • スキーマファイルを用いたバリデーションを行う場合は、以下に示す2つのプロパティを設定する

      • プロパティschemaにバリデーションにて使用するスキーマファイルを設定する

      • プロパティvalidationEventHandlerにバリデーションにて発生したイベントを処理するValidationEventHandlerの実装クラスを設定する

下記の入力ファイルを読み込むための設定例を示す。

入力ファイル例
<?xml version="1.0" encoding="UTF-8"?>
<records>
    <SalesPlanDetail>
        <branchId>000001</branchId>
        <year>2016</year>
        <month>1</month>
        <customerId>0000000001</customerId>
        <amount>1000000000</amount>
    </SalesPlanDetail>
    <SalesPlanDetail>
        <branchId>000002</branchId>
        <year>2017</year>
        <month>2</month>
        <customerId>0000000002</customerId>
        <amount>2000000000</amount>
    </SalesPlanDetail>
    <SalesPlanDetail>
        <branchId>000003</branchId>
        <year>2018</year>
        <month>3</month>
        <customerId>0000000003</customerId>
        <amount>3000000000</amount>
    </SalesPlanDetail>
</records>
変換対象クラス
@XmlRootElement(name = "SalesPlanDetail")  // (1)
public class SalesPlanDetailToJaxb {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}
設定内容の項目一覧
項番 説明

(1)

XML のルートタグとするため@XmlRootElementアノテーションを付与する。
タグの名前にSalesPlanDetailを設定する。

上記のファイルを読む込むための設定は以下のとおり。

Bean定義
<!-- (1) (2) (3) -->
<bean id="reader"
      class="org.springframework.batch.item.xml.StaxEventItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:fragmentRootElementName="SalesPlanDetail"
      p:strict="true">
    <property name="unmarshaller">  <!-- (4) -->
        <!-- (5) (6) -->
        <bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller"
              p:schema="file:files/test/input/ch05/fileaccess/SalesPlanDetail.xsd"
              p:validationEventHandler-ref="salesPlanDetailValidationEventHandler">
            <property name="classesToBeBound">  <!-- (7) -->
                <list>
                    <value>org.terasoluna.batch.functionaltest.ch05.fileaccess.model.plan.SalesPlanDetailToJaxb</value>
                </list>
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

入力ファイルを設定する。

なし

(2)

fragmentRootElementName

ルートエレメントとなる要素の名前を設定する。
対象となるオブジェクトが複数ある場合には、fragmentRootElementNamesを利用する。

なし

(3)

strict

trueを設定すると、入力ファイルが存在しない(開けない)場合に例外が発生する。

true

(4)

unmarshaller

アンマーシャラを設定する。
JAXBを利用する場合は、org.springframework.oxm.jaxb.Jaxb2MarshallerのBeanを設定する。

なし

(5)

schema

バリデーションにて使用するスキーマファイルを設定する。

(6)

validationEventHandler

バリデーションにて発生したイベントを処理するValidationEventHandlerの実装クラスを設定する。
ValidationEventHandlerの実装例は後述する。

(7)

classesToBeBound

変換対象のクラスをリスト形式で設定する。

なし

ValidationEventHandlerの実装例
@Component
// (1)
public class SalesPlanDetailValidationEventHandler implements ValidationEventHandler {
    /**
     * Logger.
     */
    private static final Logger logger =
            LoggerFactory.getLogger(SalesPlanDetailValidationEventHandler.class);

    @Override
    public boolean handleEvent(ValidationEvent event) {
        // (2)
        logger.error("[EVENT [SEVERITY:{}] [MESSAGE:{}] [LINKED EXCEPTION:{}]" +
                " [LOCATOR: [LINE NUMBER:{}] [COLUMN NUMBER:{}] [OFFSET:{}]" +
                " [OBJECT:{}] [NODE:{}] [URL:{}] ] ]",
                event.getSeverity(),
                event.getMessage(),
                event.getLinkedException(),
                event.getLocator().getLineNumber(),
                event.getLocator().getColumnNumber(),
                event.getLocator().getOffset(),
                event.getLocator().getObject(),
                event.getLocator().getNode(),
                event.getLocator().getURL());
        return false;  // (3)
    }
}
設定内容の項目一覧
項番 説明

(1)

ValidationEventHandlerクラスを実装し、handleEventメソッドをオーバーライドする。

(2)

引数で受けたevent(ValidationEvent)からイベントの情報を取得し、適宜処理を行う。
例ではイベントの情報をログ出力している。

(3)

検証処理を終了させるためfalseを返す。 検証処理を続行する場合はtrueを返す。
適切なUnmarshalExceptionValidationException、またはMarshalExceptionを生成して現在の操作を終了させる場合はfalseを返す。

依存ライブラリの追加

org.springframework.oxm.jaxb.Jaxb2Marshallerなど、 Spring Frameworkが提供するライブラリであるSpring Object/XML Marshallingを使用する場合は、 ライブラリの依存関係に以下の設定を追加する必要がある。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-oxm</artifactId>
</dependency>
5.3.3.2.2. 出力

XMLファイルの出力にはSpring Batchが提供するorg.springframework.batch.item.xml.StaxEventItemWriterを使用する。
StaxEventItemWriterは指定したMarshallerを使用してBeanをXMLにマッピングすることでXMLファイルを出力することができる。

StaxEventItemWriterは以下の要領で定義する。

  • 変換対象クラスに以下の設定を行う

    • XMLのルートエレメントとなるためクラスに@XmlRootElementを付与する

    • @XmlTypeアノテーションを使用してフィールドを出力する順番を設定する

    • XMLへの変換対象外とするフィールドがある場合、対象フィールドのgetterに@XmlTransientアノテーションを付与する

  • StaxEventItemWriterに以下のプロパティを設定する

    • プロパティresourceに出力対象ファイルを設定する

    • プロパティmarshallerorg.springframework.oxm.jaxb.Jaxb2Marshallerを設定する

  • Jaxb2Marshallerには以下のプロパティを設定する

    • プロパティclassesToBeBoundに変換対象のクラスをリスト形式で設定する

下記の出力ファイルを書き出すための設定例を示す。

出力ファイル例
<?xml version="1.0" encoding="UTF-8"?>
<records>
  <Customer>
    <customerId>001</customerId>
    <customerName>CustomerName001</customerName>
    <customerAddress>CustomerAddress001</customerAddress>
    <customerTel>11111111111</customerTel>
    <chargeBranchId>001</chargeBranchId></Customer>
  <Customer>
    <customerId>002</customerId>
    <customerName>CustomerName002</customerName>
    <customerAddress>CustomerAddress002</customerAddress>
    <customerTel>11111111111</customerTel>
    <chargeBranchId>002</chargeBranchId></Customer>
  <Customer>
    <customerId>003</customerId>
    <customerName>CustomerName003</customerName>
    <customerAddress>CustomerAddress003</customerAddress>
    <customerTel>11111111111</customerTel>
    <chargeBranchId>003</chargeBranchId>
  </Customer>
</records>
XMLファイル出力時のフォーマット処理(改行およびインデント)について

上記の出力ファイル例ではフォーマット処理(改行およびインデント)済みのXMLを例示しているが、実際にはフォーマットされていないファイルが出力される。

Jaxb2MarshallerにはXML出力時にフォーマットを行う機能があるが期待どおり動作しない。
この件に関してはSpring Forumにて議論されているため、今後期待どおり動作するようになる可能性がある