バッチのドメイン言語

経験豊富なバッチアーキテクトにとって、Spring Batch で使用されるバッチ処理の全体的な概念は、親しみやすく快適なものです。「ジョブ」と「ステップ」、および ItemReader と ItemWriter と呼ばれる開発者提供の処理ユニットがあります。ただし、Spring のパターン、操作、テンプレート、コールバック、イディオムがあるため、次の可能性があります。

  • 関心事の明確な分離への遵守の大幅な改善。

  • インターフェースとして提供される明確に描かれたアーキテクチャ層とサービス。

  • 迅速な採用とすぐに使える使いやすさを可能にするシンプルでデフォルトの実装。

  • 拡張性が大幅に向上しました。

次の図は、何十年も使用されてきたバッチリファレンスアーキテクチャの簡易版です。バッチ処理のドメイン言語を構成するコンポーネントの概要を提供します。このアーキテクチャフレームワークは、過去数世代のプラットフォーム (メインフレームでは COBOL、Unix では C、現在はどこでも Java) で数十年にわたって実装されてきた設計図です。JCL および COBOL の開発者は、C、C#、Java の開発者と同じように概念に慣れている可能性があります。Spring Batch は、単純なバッチアプリケーションから複雑なバッチアプリケーションの作成に対応するために使用される堅牢で保守可能なシステムに一般的に見られるレイヤー、コンポーネント、技術サービスの物理的な実装を提供し、非常に複雑な処理のニーズに対応するインフラストラクチャと拡張機能を備えています。

Figure 2.1: Batch Stereotypes
図 1: バッチステレオタイプ

上の図は、Spring Batch のドメイン言語を構成する主要な概念を強調しています。Job には 1 つ以上のステップがあり、各ステップには正確に 1 つの ItemReader、1 つの ItemProcessor、1 つの ItemWriter があります。ジョブを ( JobLauncher で) 起動する必要があり、現在実行中のプロセスに関するメタデータを ( JobRepository で) 保存する必要があります。

ジョブ

このセクションでは、バッチジョブの概念に関連するステレオタイプについて説明します。Job は、バッチプロセス全体をカプセル化するエンティティです。他の Spring プロジェクトと同様に、Job は XML 構成ファイルまたは Java ベースの構成と一緒に接続されます。この構成は、「ジョブ構成」と呼ばれることがあります。ただし、次の図に示すように、Job は階層全体の最上位にすぎません。

Job Hierarchy
図 2: ジョブ階層

Spring Batch では、Job は単に Step インスタンスのコンテナーです。これは、フロー内で論理的に一緒に属する複数のステップを結合し、再開可能性など、すべてのステップにグローバルなプロパティの構成を可能にします。ジョブ構成には次が含まれます。

  • ジョブの名前。

  • Step インスタンスの定義と順序。

  • ジョブが再開可能かどうか。

  • Java

  • XML

Java 構成を使用するユーザー向けに、Spring Batch は SimpleJob クラスの形式で Job インターフェースのデフォルト実装を提供し、Job の上にいくつかの標準機能を作成します。Java ベースの構成を使用する場合、次の例に示すように、ビルダーのコレクションを Job のインスタンス化に使用できるようになります。

@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
                     .start(playerLoad())
                     .next(gameLoad())
                     .next(playerSummarization())
                     .build();
}

XML 構成を使用するユーザー向けに、Spring Batch は SimpleJob クラスの形式で Job インターフェースのデフォルト実装を提供し、Job の上にいくつかの標準機能を作成します。ただし、バッチ名前空間により、直接インスタンス化する必要がなくなりました。代わりに、次の例に示すように、<job> 要素を使用できます。

<job id="footballJob">
    <step id="playerload" next="gameLoad"/>
    <step id="gameLoad" next="playerSummarization"/>
    <step id="playerSummarization"/>
</job>

JobInstance

JobInstance は、論理ジョブ実行の概念を指します。前の図の EndOfDayJob など、1 日の終わりに 1 回実行する必要があるバッチジョブを考えてみましょう。EndOfDay ジョブは 1 つありますが、Job の個々の実行は個別に追跡する必要があります。このジョブの場合、1 日あたり 1 つの論理 JobInstance があります。例: 1 月 1 日の実行、1 月 2 日の実行などがあります。1 月 1 日の実行が最初に失敗し、次の日に再度実行された場合でも、1 月 1 日の実行のままです。(通常、これは処理中のデータにも対応します。つまり、1 月 1 日の実行で 1 月 1 日のデータが処理されます)。各 JobInstance は複数の実行を持つことができ (JobExecution については、この章の後半で詳しく説明します)、特定の時間に実行できる JobInstance (特定の Job に対応し、JobParameters を識別する) は 1 つだけです。

JobInstance の定義は、ロードされるデータにはまったく関係ありません。データのロード方法を決定するのは、ItemReader の実装次第です。例: EndOfDay シナリオでは、データが属する effective date または schedule date を示す列がデータにある場合があります。1 月 1 日の実行では 1 日目のデータのみがロードされ、1 月 2 日の実行では 2 日目のデータのみが使用されます。この決定はビジネス上の決定である可能性が高いため、決定は ItemReader に任されています。ただし、同じ JobInstance を使用すると、以前の実行からの「状態」(つまり、この章で後述する ExecutionContext) が使用されるかどうかが決まります。新しい JobInstance を使用することは「最初から開始する」ことを意味し、既存のインスタンスを使用することは一般的に「中断したところから開始する」ことを意味します。

JobParameters

JobInstance とそれが Job とどのように異なるかについて説明したため、「ある JobInstance と他の JobInstance はどのように区別されるのですか ? 」という質問をするのは自然なことです。答えは: JobParameters です。JobParameters オブジェクトは、バッチジョブの開始に使用される一連のパラメーターを保持します。次の図に示すように、これらは識別のために、または実行中の参照データとしても使用できます。

Job Parameters
図 3: ジョブパラメーター

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 は完了したとは見なされません。例として前に説明した EndOfDayJob を使用して、初めて実行したときに失敗した 01-01-2017 の JobInstance を考えてみましょう。最初の実行 (01-01-2017) と同じ識別ジョブパラメーターを使用して再度実行すると、新しい JobExecution が作成されます。ただし、JobInstance はまだ 1 つしかありません。

Job は、ジョブとは何か、どのように実行するかを定義します。JobInstance は、主に正しい再起動セマンティクスを有効にするために、実行をグループ化するための純粋に組織的なオブジェクトです。ただし、JobExecution は、実行中に実際に発生した内容の主要なストレージメカニズムであり、次の表に示すように、制御および永続化する必要があるさらに多くのプロパティが含まれています。

表 1: JobExecution のプロパティ

プロパティ

定義

Status

実行のステータスを示す BatchStatus オブジェクト。実行中は、BatchStatus#STARTED です。失敗した場合、BatchStatus#FAILED です。正常に終了した場合は、BatchStatus#COMPLETED です

startTime

実行が開始された現在のシステム時刻を表す java.time.LocalDateTime。ジョブがまだ開始されていない場合、このフィールドは空です。

endTime

成功したかどうかに関係なく、実行が終了した現在のシステム時刻を表す java.time.LocalDateTime。ジョブがまだ終了していない場合、フィールドは空です。

exitStatus

実行結果を示す ExitStatus。最も重要なのは、呼び出し元に返される終了コードが含まれているためです。詳細については、第 5 章を参照してください。ジョブがまだ終了していない場合、フィールドは空です。

createTime

JobExecution が最初に永続化されたときの現在のシステム時刻を表す java.time.LocalDateTime。ジョブはまだ開始されていない可能性があります (したがって、開始時刻はありません) が、ジョブレベルの ExecutionContexts を管理するためのフレームワークで必要な createTime が常に含まれています。

lastUpdated

JobExecution が最後に保存された時間を表す java.time.LocalDateTime。ジョブがまだ開始されていない場合、このフィールドは空です。

executionContext

実行間で永続化する必要があるユーザーデータを含む「プロパティバッグ」。

failureExceptions

Job の実行中に発生した例外のリスト。これらは、Job の障害中に複数の例外が発生した場合に役立ちます。

これらのプロパティは永続化され、実行のステータスを完全に判断するために使用できるため、重要です。例: 01-01 の EndOfDay ジョブが 9:00 PM で実行され、9:30 で失敗した場合、次のエントリがバッチメタデータテーブルに作成されます。

表 2: BATCH_JOB_INSTANCE

JOB_INST_ID

JOB_NAME

1

EndOfDayJob

表 3: BATCH_JOB_EXECUTION_PARAMS

JOB_EXECUTION_ID

TYPE_CD

KEY_NAME

DATE_VAL

IDENTIFYING

1

DATE

schedule.Date

2017-01-01

TRUE

表 4: BATCH_JOB_EXECUTION

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 つの追加エントリがあるはずです。

表 5: BATCH_JOB_INSTANCE

JOB_INST_ID

JOB_NAME

1

EndOfDayJob

2

EndOfDayJob

表 6: BATCH_JOB_EXECUTION_PARAMS

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

表 7: BATCH_JOB_EXECUTION

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 は、バッチジョブの独立した順次フェーズをカプセル化するドメインオブジェクトです。すべての Job は完全に 1 つまたは複数のステップで構成されます。Step には、実際のバッチ処理を定義および制御するために必要なすべての情報が含まれています。任意の Step の内容は Job を作成する開発者の裁量に任されているため、これは必然的に曖昧な説明です。Step は、開発者の希望に応じて単純にも複雑にもできます。単純な Step は、ファイルからデータベースにデータをロードする可能性があり、コードはほとんどまたはまったく必要ありません (使用される実装によって異なります)。より複雑な Step には、処理の一部として適用される複雑なビジネスルールがある場合があります。Job と同様に、Step には、次の図に示すように、一意の JobExecution と相関する個別の StepExecution があります。

Figure 2.1: Job Hierarchy With Steps
図 4: ステップを含むジョブ階層

StepExecution

StepExecution は、Step を実行する 1 回の試行を表します。JobExecution と同様に、Step が実行されるたびに新しい StepExecution が作成されます。ただし、失敗する前のステップが原因でステップの実行が失敗した場合、その実行は持続しません。StepExecution は、その Step が実際に開始されたときにのみ作成されます。

Step 実行は、StepExecution クラスのオブジェクトによって表されます。各実行には、対応するステップと JobExecution への参照、およびコミットとロールバックの数、開始時間と終了時間などのトランザクション関連データが含まれます。さらに、各ステップの実行には ExecutionContext が含まれます。これには、再起動に必要な統計情報や状態情報など、開発者がバッチ実行全体で保持する必要があるデータが含まれます。次の表に、StepExecution のプロパティを示します。

表 8: StepExecution のプロパティ

プロパティ

定義

Status

実行のステータスを示す BatchStatus オブジェクト。実行中のステータスは BatchStatus.STARTED です。失敗した場合、ステータスは BatchStatus.FAILED です。正常に終了した場合、ステータスは BatchStatus.COMPLETED です。

startTime

実行が開始された現在のシステム時刻を表す java.time.LocalDateTime。ステップがまだ開始されていない場合、このフィールドは空です。

endTime

成功したかどうかに関係なく、実行が終了した現在のシステム時刻を表す java.time.LocalDateTime。ステップがまだ終了していない場合、このフィールドは空です。

exitStatus

実行結果を示す ExitStatus。最も重要なのは、呼び出し元に返される終了コードが含まれているためです。詳細については、第 5 章を参照してください。ジョブがまだ終了していない場合、このフィールドは空です。

executionContext

実行間で永続化する必要があるユーザーデータを含む「プロパティバッグ」。

readCount

正常に読み取られたアイテムの数。

writeCount

正常に書き込まれたアイテムの数。

commitCount

この実行のためにコミットされたトランザクションの数。

rollbackCount

Step によって制御されるビジネストランザクションがロールバックされた回数。

readSkipCount

read が失敗し、結果としてアイテムがスキップされた回数。

processSkipCount

process が失敗し、結果としてアイテムがスキップされた回数。

filterCount

ItemProcessor によって「フィルタリング」されたアイテムの数。

writeSkipCount

write が失敗し、結果としてアイテムがスキップされた回数。

ExecutionContext

ExecutionContext は、StepExecution オブジェクトまたは JobExecution オブジェクトにスコープされた永続状態を格納する場所を開発者に提供するために、フレームワークによって永続化および制御されるキーと値のペアのコレクションを表します。( Quartz に精通している方にとっては、JobDataMap と非常によく似ています) 最適な使用例は、再起動を容易にすることです。例としてフラットファイル入力を使用すると、個々の行を処理している間、フレームワークは定期的にコミットポイントで ExecutionContext を永続化します。そうすることで、実行中に致命的なエラーが発生した場合や電源が切れた場合でも、ItemReader はその状態を保存できます。次の例に示すように、必要なのは現在の行数をコンテキストに入れることだけで、あとはフレームワークが行います。

executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition());

Job ステレオタイプセクションの EndOfDay の例を例として使用し、ファイルをデータベースにロードする loadData という 1 つのステップがあるとします。最初の失敗した実行の後、メタデータテーブルは次の例のようになります。

表 9: BATCH_JOB_INSTANCE

JOB_INST_ID

JOB_NAME

1

EndOfDayJob

表 10: BATCH_JOB_EXECUTION_PARAMS

JOB_INST_ID

TYPE_CD

KEY_NAME

DATE_VAL

1

DATE

schedule.Date

2017-01-01

表 11: BATCH_JOB_EXECUTION

JOB_EXEC_ID

JOB_INST_ID

START_TIME

END_TIME

STATUS

1

1

2017-01-01 21:00

2017-01-01 21:30

FAILED

表 12: BATCH_STEP_EXECUTION

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

表 13: BATCH_STEP_EXECUTION_CONTEXT

STEP_EXEC_ID

SHORT_CONTEXT

1

{piece.count=40321}

前のケースでは、Step は 30 分間実行され、このシナリオではファイル内の行を表す 40,321 の「断片」を処理しました。この値は、フレームワークによる各コミットの直前に更新され、ExecutionContext 内のエントリに対応する複数の行を含めることができます。コミット前に通知を受けるには、さまざまな StepListener 実装 (または 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 の実行が 2 回目に再び開始されると、フレームワークはそれが同じ JobInstance であり、個々の Step ベースであることを認識し、データベースから ExecutionContext を引き出し、それを (一部として) 渡します。StepExecution の) を Step 自体に接続します。逆に、01-02 の実行では、フレームワークはそれが別のインスタンスであることを認識するため、空のコンテキストを Step に渡す必要があります。適切なタイミングで状態が与えられるようにするために、フレームワークが開発者のために行うこれらの種類の決定の多くがあります。また、常に StepExecution ごとに 1 つの ExecutionContext が存在することに注意することも重要です。これにより共有キースペースが作成されるため、ExecutionContext のクライアントは注意が必要です。そのため、データが上書きされないように、値を入れるときは注意が必要です。ただし、Step はコンテキストにデータをまったく保存しないため、フレームワークに悪影響を与える方法はありません。

JobExecution ごとに少なくとも 1 つの ExecutionContext と StepExecution ごとに 1 つの ExecutionContext があることに注意してください。例: 次のコードスニペットを検討してください。

ExecutionContext ecStep = stepExecution.getExecutionContext();
ExecutionContext ecJob = jobExecution.getExecutionContext();
//ecStep does not equal ecJob

コメントで記述されていたように、ecStep は ecJob と等しくありません。それらは 2 つの異なる ExecutionContexts です。Step を対象とするものは Step のすべてのコミットポイントで保存されますが、ジョブを対象とするものは Step の実行のたびに保存されます。

ExecutionContext では、すべての非一時エントリは Serializable である必要があります。実行コンテキストの適切な直列化は、ステップとジョブの再起動機能を支えます。ネイティブに直列化できないキーまたは値を使用する場合は、カスタマイズされた直列化アプローチを採用する必要があります。実行コンテキストの直列化に失敗すると、状態の永続化プロセスが危険にさらされ、失敗したジョブを適切に回復できなくなる可能性があります。

JobRepository

JobRepository は、前述のすべてのステレオタイプの持続メカニズムです。JobLauncherJobStep 実装に CRUD 操作を提供します。Job が最初に起動されると、リポジトリから JobExecution が取得されます。また、実行の過程で、StepExecution および JobExecution の実装は、リポジトリに渡すことによって永続化されます。

  • Java

  • XML

Java 構成を使用する場合、@EnableBatchProcessing アノテーションは、自動的に構成されるコンポーネントの 1 つとして JobRepository を提供します。

次の例に示すように、Spring Batch XML 名前空間は、<job-repository> タグを使用して JobRepository インスタンスを構成するためのサポートを提供します。

<job-repository id="jobRepository"/>

JobLauncher

JobLauncher は、次の例に示すように、特定のセットの JobParameters で Job を起動するための単純なインターフェースを表します。

public interface JobLauncher {

public JobExecution run(Job job, JobParameters jobParameters)
            throws JobExecutionAlreadyRunningException, JobRestartException,
                   JobInstanceAlreadyCompleteException, JobParametersInvalidException;
}

実装が JobRepository から有効な JobExecution を取得し、Job を実行することが期待されます。

ItemReader

ItemReader は、Step の入力を一度に 1 つずつ取得することを表す抽象化です。ItemReader が提供できるアイテムを使い果たすと、null を返すことでこれを示します。ItemReader インターフェースとそのさまざまな実装の詳細については、リーダーとライターを参照してください。

ItemWriter

ItemWriter は、Step の出力、一度に 1 つのバッチまたはアイテムのチャンクを表す抽象化です。一般に、ItemWriter は、次に受信する必要がある入力を認識しておらず、現在の呼び出しで渡されたアイテムのみを認識しています。ItemWriter インターフェースとそのさまざまな実装の詳細については、リーダーとライターを参照してください。

ItemProcessor

ItemProcessor は、アイテムのビジネス処理を表す抽象化です。ItemReader は 1 つの項目を読み取り、ItemWriter は 1 つの項目を書き込みますが、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>

バッチ名前空間が宣言されている限り、その要素はどれでも使用できます。ジョブの構成と実行でジョブの構成に関する詳細情報を見つけることができます。ステップの構成で Step の構成に関する詳細情報を見つけることができます。