バッチのドメイン言語
経験豊富なバッチアーキテクトにとって、Spring Batch で使用されるバッチ処理の全体的な概念は、馴染みがあり快適です。「ジョブ」と「ステップ」、および ItemReader
と ItemWriter
と呼ばれる開発者提供の処理ユニットがあります。ただし、Spring のパターン、操作、テンプレート、コールバック、イディオムのため、次の機会があります。
関心事の明確な分離への遵守の大幅な改善。
インターフェースとして提供される明確に描かれたアーキテクチャ層とサービス。
すぐに使用でき、すぐに使用できる簡単でデフォルトの実装。
拡張性が大幅に向上しました。
次の図は、何十年も使用されてきたバッチ参照アーキテクチャの簡略版です。バッチ処理のドメイン言語を構成するコンポーネントの概要を提供します。このアーキテクチャフレームワークは、過去数世代のプラットフォーム(COBOL/ メインフレーム、C/Unix、現在は Java/ 任意の場所)での数十年の実装を通じて実証された設計図です。JCL および COBOL の開発者は、C、C#、Java の開発者と同じくらい概念に慣れていると思われます。Spring Batch は、シンプルで複雑なバッチアプリケーションの作成に対応するために使用される、堅牢で保守可能なシステムに一般的に見られるレイヤー、コンポーネント、技術サービスの物理的な実装を提供します。
上記の図は、Spring Batch のドメイン言語を構成する重要な概念を強調しています。ジョブには 1 つから多数のステップがあり、各ステップには正確に 1 つの ItemReader
、1 つの ItemProcessor
、1 つの ItemWriter
があります。ジョブを立ち上げる必要があり(JobLauncher
を使用)、現在実行中のプロセスに関するメタデータを(JobRepository
に)格納する必要があります。
ジョブ
このセクションでは、バッチジョブの概念に関連するステレオタイプについて説明します。Job
は、バッチプロセス全体をカプセル化するエンティティです。他の Spring プロジェクトで一般的であるように、Job
は XML 構成ファイルまたは Java ベースの構成のいずれかと一緒に接続されます。この構成は、「ジョブ構成」と呼ばれる場合があります。ただし、次の図に示すように、Job
は階層全体の最上位にすぎません。
Spring Batch では、Job
は Step
インスタンスの単なるコンテナーです。フロー内で論理的に一緒に属する複数のステップを組み合わせて、再起動性など、すべてのステップに対してグローバルなプロパティの構成を可能にします。ジョブ構成には次が含まれます。
ジョブの単純な名前。
Step
インスタンスの定義と順序。ジョブが再開可能かどうか。
Java 構成を使用する場合、Spring Batch は、Job
の上にいくつかの標準機能を作成する SimpleJob
クラスの形式で Job インターフェースのデフォルトの実装を提供します。java ベースの構成を使用する場合、次の例に示すように、Job
のインスタンス化にビルダーのコレクションが使用可能になります。
@Bean
public Job footballJob() {
return this.jobBuilderFactory.get("footballJob")
.start(playerLoad())
.next(gameLoad())
.next(playerSummarization())
.build();
}
XML 構成を使用する場合、Spring Batch は、Job
の上にいくつかの標準機能を作成する SimpleJob
クラスの形式で Job
インターフェースのデフォルトの実装を提供します。ただし、バッチ名前空間は、直接インスタンス化する必要性を抽象化します。代わりに、次の例に示すように、<job>
要素を使用できます。
<job id="footballJob">
<step id="playerload" next="gameLoad"/>
<step id="gameLoad" next="playerSummarization"/>
<step id="playerSummarization"/>
</job>
JobInstance
JobInstance
は、論理ジョブ実行の概念を指します。前の図の 'EndOfDay' Job
など、一日の終わりに 1 回実行するバッチジョブを検討します。1 つの "EndOfDay" ジョブがありますが、Job
の個々の実行は個別に追跡する必要があります。このジョブの場合、1 日に 1 つの論理 JobInstance
があります。例: 1 月 1 日の実行、1 月 2 日の実行などがあります。1 月 1 日の実行が初めて失敗し、翌日に再度実行された場合、1 月 1 日の実行のままです。(通常、これは処理中のデータにも対応します。つまり、1 月 1 日の実行は 1 月 1 日のデータを処理します)。各 JobInstance
は複数回実行できます(JobExecution
については、この章で後ほど詳しく説明します)。特定の Job
に対応し、JobParameters
を識別する JobInstance
は、一度に 1 つしか実行できません。
JobInstance
の定義は、ロードされるデータにはまったく関係ありません。データのロード方法を決定するのは、ItemReader
実装次第です。例: EndOfDay シナリオでは、データが属する「発効日」または「スケジュール日」を示す列がデータにある場合があります。1 月 1 日の実行では 1 日からのデータのみがロードされ、1 月 2 日の実行では 2 日からのデータのみが使用されます。この決定はビジネス上の決定である可能性が高いため、決定するのは ItemReader
に任されています。ただし、同じ JobInstance
を使用すると、以前の実行からの「状態」(つまり、この章で後述する ExecutionContext
)を使用するかどうかが決まります。新しい JobInstance
を使用することは「最初から開始する」ことを意味し、既存のインスタンスを使用することは通常「中断したところから開始する」ことを意味します。
JobParameters
JobInstance
とそれがジョブとどのように異なるかについて議論したため、確認する自然な質問は、「ある JobInstance
を別の JobInstance
とどのように区別するか」です。答えは JobParameters
です。JobParameters
オブジェクトは、バッチジョブの開始に使用される一連のパラメーターを保持します。次の図に示すように、これらは識別に使用することも、実行中の参照データとして使用することもできます。
1 月 1 日と 1 月 2 日の 2 つのインスタンスがある上記の例では、実際には Job
が 1 つだけですが、JobParameter
オブジェクトが 2 つあります。1 つは 01-01-2017 のジョブパラメーターで開始され、もう 1 つは 01-02-2017 のパラメーターで開始。契約は JobInstance
= Job
+ JobParameters
を識別するように定義できます。これにより、開発者は、渡されるパラメーターを制御するため、JobInstance
の定義方法を効果的に制御できます。
すべてのジョブパラメーターが JobInstance の識別にコントリビュートする必要はありません。デフォルトでは、そうします。ただし、このフレームワークでは、JobInstance の ID にコントリビュートしないパラメーターを使用して Job を送信することもできます。 |
JobExecution
JobExecution
は、ジョブを実行する 1 回の試行の技術的概念を指します。実行は失敗または成功で終了する場合がありますが、実行が正常に完了するまで、特定の実行に対応する JobInstance
は完了したとは見なされません。例として前述した EndOfDay Job
を使用して、01-01-2017 の JobInstance
が最初に実行されたときに失敗したと考えてください。最初の実行(01-01-2017)と同じ識別ジョブパラメーターで再度実行すると、新しい JobExecution
が作成されます。ただし、まだ JobInstance
は 1 つしかありません。
Job
はジョブの内容と実行方法を定義し、JobInstance
は主に正しい再起動セマンティクスを有効にするために、実行をグループ化する純粋な組織オブジェクトです。ただし、JobExecution
は、実行中に実際に発生したことの主なストレージメカニズムであり、次の表に示すように、制御および永続化する必要のある多くのプロパティが含まれています。
プロパティ | 定義 |
ステータス | 実行のステータスを示す |
startTime | 実行が開始された現在のシステム時刻を表す |
endTime | 成功したかどうかに関係なく、実行が終了した現在のシステム時刻を表す |
exitStatus | 実行結果を示す |
createTime |
|
lastUpdated |
|
executionContext | 実行間で保持する必要があるユーザーデータを含む「プロパティバッグ」。 |
failureExceptions |
|
これらのプロパティは永続的であり、実行のステータスを完全に決定するために使用できるため、これらのプロパティは重要です。例: 01-01 の EndOfDay ジョブが 9:00 PM で実行され、9:30 で失敗すると、バッチメタデータテーブルに次のエントリが作成されます。
JOB_INST_ID | JOB_NAME |
1 | EndOfDayJob |
JOB_EXECUTION_ID | TYPE_CD | KEY_NAME | DATE_VAL | IDENTIFYING |
1 | DATE | schedule.Date | 2017-01-01 | TRUE |
JOB_EXEC_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
1 | 1 | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
列名は、明確さとフォーマットのために省略または削除されている場合があります。 |
ジョブが失敗したため、「バッチウィンドウ」が閉じられるように、課題を特定するのに一晩かかったと仮定します。さらに、ウィンドウが 9:00 PM から開始すると仮定すると、01-01 のジョブは再び開始され、中断したところから開始して 9:30 で正常に完了します。翌日であるため、01-02 ジョブも実行する必要があり、その後すぐに 9:31 で開始され、10:30 で通常の 1 時間で完了します。2 つのジョブが同じデータにアクセスしようとしてデータベースレベルでのロックの課題を引き起こす可能性がない限り、JobInstance
を次々に開始する必要はありません。Job
をいつ実行するかを決定するのは、完全にスケジューラー次第です。それらは別個の JobInstances
であるため、Spring Batch はそれらの同時実行を停止しようとしません。(別の JobInstance
をすでに実行中に同じ JobInstance
を実行しようとすると、JobExecutionAlreadyRunningException
がスローされます)。次の表に示すように、JobInstance
テーブルと JobParameters
テーブルの両方に追加のエントリがあり、JobExecution
テーブルに 2 つの追加のエントリがあります。
JOB_INST_ID | JOB_NAME |
1 | EndOfDayJob |
2 | EndOfDayJob |
JOB_EXECUTION_ID | TYPE_CD | KEY_NAME | DATE_VAL | IDENTIFYING |
1 | DATE | schedule.Date | 2017-01-01 00:00:00 | TRUE |
2 | DATE | schedule.Date | 2017-01-01 00:00:00 | TRUE |
3 | DATE | schedule.Date | 2017-01-02 00:00:00 | TRUE |
JOB_EXEC_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
1 | 1 | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
2 | 1 | 2017-01-02 21:00 | 2017-01-02 21:30 | COMPLETED |
3 | 2 | 2017-01-02 21:31 | 2017-01-02 22:29 | COMPLETED |
列名は、明確さとフォーマットのために省略または削除されている場合があります。 |
ステップ
Step
は、バッチジョブの独立したシーケンシャルフェーズをカプセル化するドメインオブジェクトです。すべてのジョブは 1 つ以上のステップで完全に構成されます。Step
には、実際のバッチ処理を定義および制御するために必要なすべての情報が含まれています。Step
の内容は Job
を作成する開発者の裁量にあるため、これは必然的に曖昧な記述です。Step
は、開発者が望むほど単純でも複雑でもかまいません。単純な Step
は、ファイルからデータベースにデータをロードし、コードをほとんどまたはまったく必要としない場合があります(使用する実装によって異なります)。より複雑な Step
には、処理の一部として適用される複雑なビジネスルールがある場合があります。Job
と同様に、Step
には、次のイメージに示すように、一意の JobExecution
と相関する個々の StepExecution
があります。
StepExecution
StepExecution
は、Step
を実行する 1 回の試行を表します。JobExecution
と同様に、Step
が実行されるたびに新しい StepExecution
が作成されます。ただし、失敗する前のステップが原因でステップの実行が失敗した場合、その実行は持続しません。StepExecution
は、その Step
が実際に開始されたときにのみ作成されます。
Step
の実行は、StepExecution
クラスのオブジェクトによって表されます。各実行には、対応するステップへの参照、JobExecution
、コミットとロールバックのカウント、開始時間と終了時間などのトランザクション関連データが含まれます。さらに、各ステップの実行には ExecutionContext
が含まれています。これには、統計情報や再起動に必要な状態情報など、開発者がバッチ実行で保持する必要があるデータが含まれています。次の表に、StepExecution
のプロパティを示します。
プロパティ | 定義 |
ステータス | 実行のステータスを示す |
startTime | 実行が開始された現在のシステム時刻を表す |
endTime | 成功したかどうかに関係なく、実行が終了した現在のシステム時刻を表す |
exitStatus | 実行結果を示す |
executionContext | 実行間で保持する必要があるユーザーデータを含む「プロパティバッグ」。 |
readCount | 正常に読み取られたアイテムの数。 |
writeCount | 正常に書き込まれたアイテムの数。 |
commitCount | この実行のためにコミットされたトランザクションの数。 |
rollbackCount |
|
readSkipCount |
|
processSkipCount |
|
filterCount |
|
writeSkipCount |
|
ExecutionContext
ExecutionContext
は、開発者が StepExecution
オブジェクトまたは JobExecution
オブジェクトをスコープとする永続的な状態を保存する場所を可能にするために、フレームワークによって永続化および制御されるキー / 値ペアのコレクションを表します。Quartz に精通している人にとっては、JobDataMap と非常によく似ています。最適な使用例は、再起動を容易にすることです。例としてフラットファイル入力を使用して、個々の行を処理している間、フレームワークはコミットポイントで ExecutionContext
を定期的に永続化します。そうすることで、実行中に致命的なエラーが発生した場合、電源が切れた場合でも、ItemReader
はその状態を保存できます。必要なのは、次の例に示すように、読み取られた現在の行数をコンテキストに入れることだけで、残りはフレームワークが行います。
executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition());
例として Job
ステレオタイプセクションの EndOfDay の例を使用して、データベースにファイルをロードする "loadData" という 1 つのステップがあると仮定します。最初の失敗した実行後、メタデータテーブルは次の例のようになります。
JOB_INST_ID | JOB_NAME |
1 | EndOfDayJob |
JOB_INST_ID | TYPE_CD | KEY_NAME | DATE_VAL |
1 | DATE | schedule.Date | 2017-01-01 |
JOB_EXEC_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
1 | 1 | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
STEP_EXEC_ID | JOB_EXEC_ID | STEP_NAME | START_TIME | END_TIME | STATUS |
1 | 1 | データを読み込む | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
STEP_EXEC_ID | SHORT_CONTEXT |
1 | {piece.count=40321} |
上記の場合、Step
は 30 分間実行され、40,321 の「ピース」を処理しました。これは、このシナリオのファイル内の行を表します。この値は、フレームワークによって各コミットの直前に更新され、ExecutionContext
内のエントリに対応する複数の行を含むことができます。コミットの前に通知を受けるには、さまざまな StepListener
実装の 1 つ(または ItemStream
)が必要です。これについては、このガイドで後ほど詳しく説明します。前の例と同様に、Job
は翌日に再起動されると想定されています。再起動すると、最後の実行の ExecutionContext
の値がデータベースから再構成されます。ItemReader
を開くと、次の例に示すように、コンテキストに保存された状態があるかどうかを確認し、そこから自身を初期化できます。
if (executionContext.containsKey(getKey(LINES_READ_COUNT))) {
log.debug("Initializing for restart. Restart data is: " + executionContext);
long lineCount = executionContext.getLong(getKey(LINES_READ_COUNT));
LineReader reader = getReader();
Object record = "";
while (reader.getPosition() < lineCount && record != null) {
record = readLine();
}
}
この場合、上記のコードを実行すると、現在の行は 40,322 になり、Step
は中断した場所から再開できます。ExecutionContext
は、実行自体について永続化する必要がある統計にも使用できます。例: フラットファイルに複数の明細にわたって存在する処理のための受注が含まれている場合は、処理された受注の数 (読み取り行数とは大きく異なる) を格納して、本文で処理された受注の合計数とともに Step
の最後にメールを送信できるようにする必要があります。フレームワークは、個々の JobInstance
で正しくスコープを設定するために、開発者のためにこれを保存します。既存の ExecutionContext
を使用すべきかどうかを判断することは非常に困難です。例: 上記の "EndOfDay" の例を使用すると、01-01 の実行が再度開始されると、フレームワークは、それが同じ JobInstance
であり、個別の Step
ベースであることを認識し、ExecutionContext
をデータベースから取り出し、Step
自体に (StepExecution
の一部として) 渡します。逆に、01-02 を実行すると、フレームワークはそれが別のインスタンスであることを認識するため、空のコンテキストを Step
に渡す必要があります。フレームワークが開発者に対して行う決定には、適切なタイミングで状態が与えられることを保証するために、このような種類が数多くあります。また、StepExecution
ごとに常に 1 つの ExecutionContext
が存在することにも注意してください。ExecutionContext
のクライアントは、共有鍵空間を作成するため注意が必要です。そのため、値を入力するときは、データが上書きされないように注意する必要があります。ただし、Step
はコンテキスト内にデータをまったく格納しないため、フレームワークに悪影響を与えることはありません。
JobExecution
ごとに少なくとも 1 つの ExecutionContext
があり、各 StepExecution
に 1 つあることに注意することも重要です。例: 次のコードスニペットを検討します。
ExecutionContext ecStep = stepExecution.getExecutionContext();
ExecutionContext ecJob = jobExecution.getExecutionContext();
//ecStep does not equal ecJob
コメントで記述されていたように、ecStep
は ecJob
と等しくありません。それらは 2 つの異なる ExecutionContexts
です。Step
を対象とするものは Step
のすべてのコミットポイントで保存されますが、ジョブを対象とするものは Step
の実行のたびに保存されます。
JobRepository
JobRepository
は、上記のすべてのステレオタイプの永続化メカニズムです。JobLauncher
、Job
、Step
実装の CRUD 操作を提供します。Job
が最初に起動されると、JobExecution
がリポジトリから取得され、実行中に StepExecution
と JobExecution
の実装がリポジトリに渡されて永続化されます。
Spring Batch XML 名前空間は、次の例に示すように、<job-repository>
タグを使用して JobRepository
インスタンスを構成するためのサポートを提供します。
<job-repository id="jobRepository"/>
Java 構成を使用する場合、@EnableBatchProcessing
アノテーションは、すぐに利用できる自動的に構成されるコンポーネントの 1 つとして JobRepository
を提供します。
JobLauncher
JobLauncher
は、次の例に示すように、指定された JobParameters
のセットで Job
を起動するための単純なインターフェースを表します。
public interface JobLauncher {
public JobExecution run(Job job, JobParameters jobParameters)
throws JobExecutionAlreadyRunningException, JobRestartException,
JobInstanceAlreadyCompleteException, JobParametersInvalidException;
}
実装が JobRepository
から有効な JobExecution
を取得し、Job
を実行することが期待されます。
アイテムリーダー
ItemReader
は、一度に 1 項目ずつ Step
の入力の取得を表す抽象概念です。ItemReader
が提供できるアイテムを使い果たすと、null
を返すことでこれを示します。ItemReader
インターフェースとそのさまざまな実装に関する詳細は、リーダーとライターにあります。
アイテムライター
ItemWriter
は、一度に 1 つのバッチまたはアイテムのチャンクである Step
の出力を表す抽象化です。一般的に、ItemWriter
は次に受信する入力を認識せず、現在の呼び出しで渡されたアイテムのみを認識します。ItemWriter
インターフェースとそのさまざまな実装に関する詳細は、リーダーとライターにあります。
アイテムプロセッサー
ItemProcessor
は、アイテムのビジネス処理を表す抽象概念です。ItemReader
は 1 つのアイテムを読み取り、ItemWriter
はそれらを書き込みますが、ItemProcessor
は他のビジネス処理を変換または適用するためのアクセスポイントを提供します。アイテムの処理中に、アイテムが無効であると判断された場合、null
を返すことは、アイテムを書き出すべきではないことを示します。ItemProcessor
インターフェースの詳細については、リーダーとライターを参照してください。
バッチ名前空間
前述のドメイン概念の多くは、Spring ApplicationContext
で構成する必要があります。標準の Bean 定義で使用できる上記のインターフェースの実装がありますが、次の例に示すように、構成を容易にするために名前空間が提供されています。
<beans:beans xmlns="http://www.springframework.org/schema/batch"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/batch
https://www.springframework.org/schema/batch/spring-batch.xsd">
<job id="ioSampleJob">
<step id="step1">
<tasklet>
<chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
</tasklet>
</step>
</job>
</beans:beans>