このバージョンはまだ開発中であり、まだ安定しているとは見なされていません。最新の安定バージョンについては、Spring Batch ドキュメント 6.0.6 を使用してください!

Step 実行のインターセプト

Job の場合と同様に、Step の実行中には、ユーザーが何らかの機能を実行する必要がある可能性のある多くのイベントがあります。例: フッターを必要とするフラットファイルに書き出すには、フッターを書き込めるように、Step が完了したときに ItemWriter に通知する必要があります。これは、多くの Step スコープのリスナーの 1 つを使用して実現できます。

StepListener の拡張機能の 1 つを実装する任意のクラスを適用できます (ただし、インターフェース自体は空なので、そのインターフェース自体は実装できません) listeners 要素を介してステップに適用できます。listeners 要素は、ステップ、タスクレット、チャンク宣言内で有効です。機能が適用されるレベルでリスナーを宣言するか、多機能 ( StepExecutionListener や ItemReadListener など) の場合は、適用される最も細かいレベルで宣言することをお勧めします。

Step listeners are not designed to throw exceptions: none of the listener methods declares a checked exception, and errors must be handled in the implementation. An exception thrown by a listener is not considered a read, process, or write failure. It is never subject to the skip or retry policies of a fault-tolerant step, and it terminates the step.

  • Java

  • XML

次の例は、Java のチャンクレベルで適用されるリスナーを示しています。

Java 構成
@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(10).transactionManager(transactionManager)
				.reader(reader())
				.writer(writer())
				.listener(chunkListener())
				.build();
}

次の例は、XML のチャンクレベルで適用されるリスナーを示しています。

XML 構成
<step id="step1">
    <tasklet>
        <chunk reader="reader" writer="writer" commit-interval="10"/>
        <listeners>
            <listener ref="chunkListener"/>
        </listeners>
    </tasklet>
</step>

StepListener インターフェースの 1 つを実装する ItemReader、ItemWriter、または ItemProcessor は、名前空間 <step> 要素または *StepFactoryBean ファクトリの 1 つを使用する場合、自動的に Step に登録されます。これは、Step に直接注入されたコンポーネントにのみ適用されます。リスナーが別のコンポーネント内にネストされている場合は、明示的に登録する必要があります (前述の ItemStream を Step に登録するで説明したように)。

StepListener インターフェースに加えて、同じ問題に対処するためのアノテーションが提供されています。プレーンな古い Java オブジェクトは、対応する StepListener 型に変換されるこれらのアノテーションを持つメソッドを持つことができます。ItemReader または ItemWriter または Tasklet などのチャンクコンポーネントのカスタム実装にアノテーションを付けることも一般的です。アノテーションは、<listener/> 要素の XML パーサーによって分析されるだけでなく、ビルダーの listener メソッドに登録されるため、XML 名前空間またはビルダーを使用してリスナーをステップに登録するだけです。

StepExecutionListener

StepExecutionListener は、Step 実行の最も一般的なリスナーを表します。次の例に示すように、Step の開始前と終了後に、正常に終了したか失敗したかを通知できます。

public interface StepExecutionListener extends StepListener {

    void beforeStep(StepExecution stepExecution);

    ExitStatus afterStep(StepExecution stepExecution);

}

afterStep には ExitStatus の戻り型があり、リスナーは Step の完了時に返される終了コードを変更できます。

このインターフェースに対応するアノテーションは次のとおりです。

  • @BeforeStep

  • @AfterStep

ChunkListener

「チャンク」とは、トランザクションのスコープ内で処理される項目として定義されます。コミット間隔ごとにトランザクションをコミットすると、チャンクがコミットされます。次のインターフェース定義に示すように、ChunkListener を使用すると、チャンクの処理開始前、またはチャンクが正常完了または失敗完了した後にロジックを実行できます。

public interface ChunkListener<I, O> extends StepListener {

    void beforeChunk(Chunk<I> chunk);
    void afterChunk(Chunk<O> chunk);
    void onChunkError(Exception exception, Chunk<O> chunk);

}

beforeChunk メソッドは、トランザクションが開始され、アイテムのチャンクが読み込まれた後、処理が開始される前に呼び出されます。一方、afterChunk メソッドは、チャンクが書き込まれた後、トランザクションがコミットまたはロールバックされる前に呼び出されます。

onChunkError は空のチャンクで呼び出されることはありません。読み取りエラーなど、アイテムが処理される前に発生したエラーは、報告すべき処理済みチャンクがないため、代わりに ItemReadListener#onReadError または ItemProcessListener#onProcessError によって通知されます。つまり、実装では空のチャンクに対する対策を講じる必要はありません。

ChunkListener リスナーインターフェースは同時ステップでは呼び出されません

書き込み失敗後にフォールトトレラントなステップがチャンクをスキャンする場合(書き込み失敗とチャンクスキャンをスキップするを参照)、各項目は独自のトランザクションで再試行され、スキャンされた各項目だけでなく、チャンク全体に対してもコールバックが呼び出されます。

  • beforeChunk は、読み取られたチャンクに対して一度呼び出され、その後、スキャンされたアイテムごとに一度呼び出され、そのチャンクには単一の入力アイテムが格納されます。

  • afterChunk は、正常に書き込みが行われたスキャン済みアイテムに対してのみ呼び出されます。

  • onChunkError is called once for the whole chunk that failed to be written, then once per scanned item that turns out to be faulty, whether that item is skipped or fails the step.

For example, for a chunk of three items whose write fails because the second and third items are invalid, a listener is called as follows:

beforeChunk([1, 2, 3])   onChunkError(ex, [1, 2, 3])   (1)
beforeChunk([1])         afterChunk([1])               (2)
beforeChunk([2])         onChunkError(ex, [2])         (3)
beforeChunk([3])         onChunkError(ex, [3])
1The initial chunk transaction, which is rolled back.
2The first scan transaction, which commits.
3A scan transaction for a faulty item, which is rolled back after the item is skipped.

As stated above, a ChunkListener must handle its own errors. An exception thrown from a listener callback terminates the step, including during a chunk scan: at that point the item being scanned can no longer be written or skipped, so the step is failed rather than completed with the item unaccounted for. The job can then be restarted.

このインターフェースに対応するアノテーションは次のとおりです。

  • @BeforeChunk

  • @AfterChunk

  • @OnChunkError

ChunkListener は、チェックされた例外をスローするようには設計されていません。実装でエラーを処理する必要があります。そうしないと、ステップが終了します。

ItemReadListener

前にスキップロジックについて説明したとき、後で処理できるように、スキップされたレコードをログに記録することが有益である可能性があると述べました。読み取りエラーの場合、次のインターフェース定義が示すように、これは ItemReaderListener で実行できます。

public interface ItemReadListener<T> extends StepListener {

    void beforeRead();
    void afterRead(T item);
    void onReadError(Exception ex);

}

beforeRead メソッドは、各呼び出しの前に ItemReader を読み取るために呼び出されます。afterRead メソッドは、読み取りの呼び出しが成功するたびに呼び出され、読み取られたアイテムを渡します。読み取り中にエラーが発生した場合、onReadError メソッドが呼び出されます。発生した例外はログに記録できるように提供されます。

このインターフェースに対応するアノテーションは次のとおりです。

  • @BeforeRead

  • @AfterRead

  • @OnReadError

ItemProcessListener

ItemReadListener と同様に、次のインターフェース定義が示すように、アイテムの処理を「リッスン」できます。

public interface ItemProcessListener<T, S> extends StepListener {

    void beforeProcess(T item);
    void afterProcess(T item, S result);
    void onProcessError(T item, Exception e);

}

beforeProcess メソッドは、ItemProcessor の process の前に呼び出され、処理されるアイテムに渡されます。afterProcess メソッドは、アイテムが正常に処理された後に呼び出されます。処理中にエラーが発生した場合、onProcessError メソッドが呼び出されます。発生した例外と処理が試行されたアイテムが提供されるため、ログに記録できます。

このインターフェースに対応するアノテーションは次のとおりです。

  • @BeforeProcess

  • @AfterProcess

  • @OnProcessError

ItemWriteListener

次のインターフェース定義が示すように、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);

}

beforeWrite メソッドは、ItemWriter の write の前に呼び出され、書き込まれた項目のリストが渡されます。afterWrite メソッドは、項目が正常に書き込まれた後、チャンクの処理に関連付けられたトランザクションをコミットする前に呼び出されます。書き込み中にエラーが発生した場合は、onWriteError メソッドが呼び出されます。発生した例外と書き込みが試行された項目が提供され、ログに記録されます。

このインターフェースに対応するアノテーションは次のとおりです。

  • @BeforeWrite

  • @AfterWrite

  • @OnWriteError

SkipListener

ItemReadListener、ItemProcessListener、ItemWriteListener はすべて、エラーを通知するメカニズムを提供しますが、レコードが実際にスキップされたことを通知するものはありません。たとえば、onWriteError は、アイテムが再試行されて成功した場合でも呼び出されます。このため、次のインターフェース定義に示すように、スキップされたアイテムを追跡するための別のインターフェースがあります。

public interface SkipListener<T,S> extends StepListener {

    void onSkipInRead(Throwable t);
    void onSkipInProcess(T item, Throwable t);
    void onSkipInWrite(S item, Throwable t);

}

onSkipInRead は、読み取り中にアイテムがスキップされるたびに呼び出されます。ロールバックにより、同じアイテムが複数回スキップされて登録される可能性があることに注意してください。onSkipInWrite は、書き込み中にアイテムがスキップされると呼び出されます。項目は正常に読み取られた(スキップされなかった)ため、引数として項目自体も提供されます。

このインターフェースに対応するアノテーションは次のとおりです。

  • @OnSkipInRead

  • @OnSkipInWrite

  • @OnSkipInProcess

SkipListeners とトランザクション

One of the most common use cases for a SkipListener is to log out a skipped item, so that another batch process or even human process can be used to evaluate and fix the issue that leads to the skip.

onSkipInWrite is called during a chunk scan, inside the single-item transaction used to re-attempt the faulty item. That transaction is always rolled back right after the callback returns, since the item is being skipped rather than committed. onSkipInRead and onSkipInProcess, on the other hand, are called inside the chunk’s own ongoing transaction, before the rest of the chunk has been read, processed, and written. Whether that transaction is ultimately committed or rolled back depends on the outcome of the remaining items in the chunk.

Because a SkipListener callback can run in a transaction that ends up rolled back, any transactional resource used by the listener (for example, to log the skipped item to a database) is not guaranteed to be committed. As a result, you should use PROPAGATION_REQUIRES_NEW for any transactional operation called from a SkipListener method, the same guidance given for ChunkListener#onChunkError and ItemWriteListener#onWriteError.

Regardless of the fate of the transaction, Spring Batch guarantees that the appropriate skip method (depending on when the error happened) is called only once per item.