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

再試行ロジックの構成

ほとんどの場合、スキップまたは Step 障害のいずれかを引き起こす例外が必要です。ただし、すべての例外が決定論的というわけではありません。読み取り中に FlatFileParseException が検出されると、そのレコードに対して常にスローされます。ItemReader をリセットしても解決しません。ただし、他の例外 (別のプロセスがロックを保持しているレコードを現在のプロセスが更新しようとしたことを示す DeadlockLoserDataAccessException など) の場合は、待機して再試行すると成功する可能性があります。

  • Java

  • XML

Java では、再試行は次のように構成する必要があります。

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
    // retry policy configuration
    int retryLimit = 3;
    var retrybaleExceptions = Set.of(DeadlockLoserDataAccessException.class);
    RetryPolicy retryPolicy = RetryPolicy.builder()
        .maxRetries(retryLimit)
        .includes(retrybaleExceptions)
        .build();

	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(2).transactionManager(transactionManager)
				.reader(itemReader())
				.writer(itemWriter())
				.faultTolerant()
				.retryPolicy(retryPolicy)
				.build();
}

XML では、再試行は次のように構成する必要があります。

<step id="step1">
   <tasklet>
      <chunk reader="itemReader" writer="itemWriter"
             commit-interval="2" retry-limit="3">
         <retryable-exception-classes>
            <include class="org.springframework.dao.DeadlockLoserDataAccessException"/>
         </retryable-exception-classes>
      </chunk>
   </tasklet>
</step>

Step では、個々の項目を再試行できる回数の制限と、「再試行可能」な例外のリストが許可されます。

再試行して ItemReader

Retrying a failed ItemProcessor or ItemWriter call is always safe with respect to the forward-only ItemReader contract: the item being retried is already held by the step, so a retry simply re-invokes process/write with the exact same item.

ItemReader#read() 呼び出しの失敗を再試行するのは、アイテムがまだ存在しないため、状況が異なります。アイテムを生成するのは read() です。再試行で同じ論理アイテムを再試行するか、次のアイテムに静かに進むかは、フレームワークではなく、リーダーの実装に完全に依存します。読み取りが失敗した場合、ChunkOrientedStep は同じリーダーインスタンスで read() を再度呼び出すだけで、それ以上の調整は行いません。リーダーがアイテムを正常に返す直前まで内部位置を進めない限り、これは安全です。組み込みのページングスタイルのリーダー (JdbcPagingItemReader など) はすでにこのルールに従っています。各ページは、新しく自己完結型のクエリによって取得され、現在の位置はクエリが成功した場合にのみ進みます。カーソルベースのリーダー (JdbcCursorItemReader など) は、呼び出し間でアクティブな ResultSet/Connection を保持するため、再試行はカーソルを実際に無効にしない、より狭い一時的な問題にのみ役立ちます。

基となるソースを消費してから例外をスローするかどうかを決定するカスタムリーダーは、リトライセーフではありません。

public class UnsafeItemReader extends ListItemReader<String> {

	@Override
	public String read() {
		String item = super.read(); (1)
		validate(item); (2)
		return item;
	}

}
1 代表者はここで、次に何が起ころうとも、自らの立場を有利にするために尽力します。
2If this throws and the read is retried, the next call to read() invokes super.read() again and returns a different item. The item that failed validation is never retried, never skipped, and never seen again — it is silently dropped.

Contrast this with a reader whose position only advances after a successful read:

public class SafeItemReader implements ItemReader<String> {

	private long lastId = 0;

	@Override
	public String read() {
		Row row = fetchNext(lastId); // does not mutate any reader state
		if (row == null) {
			return null;
		}
		lastId = row.id(); // only advances on success
		return row.value();
	}

}

Here, a read() call that throws before returning has not moved lastId, so a retry re-issues the exact same fetch and returns the exact same next item.

This is also why retryable exceptions on read should be reserved for genuinely transient conditions (a dropped connection, a timeout, a deadlock), as described above. A deterministic exception, such as one raised while parsing invalid input, is thrown for the same record every time: retrying it only spends the configured retry attempts before eventually failing (or being skipped, if a skip policy also applies) anyway. Route deterministic read failures to a SkipPolicy instead of a RetryPolicy.