«1. Введение

По умолчанию любые ошибки, возникающие во время обработки задания Spring Batch, приводят к сбою соответствующего шага. Однако есть много ситуаций, когда мы предпочли бы пропустить обрабатываемый в данный момент элемент для определенных исключений.

В этом руководстве мы рассмотрим два подхода к настройке логики пропуска в среде Spring Batch.

2. Наш вариант использования

В качестве примеров мы будем повторно использовать простое задание, ориентированное на фрагменты, уже представленное во вводной статье о Spring Batch.

Это задание преобразует некоторые финансовые данные из формата CSV в формат XML.

2.1. Входные данные

Во-первых, давайте добавим несколько строк в исходный CSV-файл:

username, user_id, transaction_date, transaction_amount
devendra, 1234, 31/10/2015, 10000
john, 2134, 3/12/2015, 12321
robin, 2134, 2/02/2015, 23411
, 2536, 3/10/2019, 100
mike, 9876, 5/11/2018, -500
, 3425, 10/10/2017, 9999

Как мы видим, последние три строки содержат неверные данные — в строках 5 и 7 отсутствует поле имени пользователя, и сумма транзакции в строке 6 является отрицательной.

В следующих разделах мы настроим наше пакетное задание, чтобы пропустить эти поврежденные записи.

3. Настройка лимита пропуска и исключений с возможностью пропуска

3.1. Использование skip и skipLimit

Давайте теперь обсудим первый из двух способов настройки нашего задания для пропуска элементов в случае сбоя — методы skip и skipLimit:

@Bean
public Step skippingStep(
  ItemProcessor<Transaction, Transaction> processor,
  ItemWriter<Transaction> writer) throws ParseException {
    return stepBuilderFactory
      .get("skippingStep")
      .<Transaction, Transaction>chunk(10)
      .reader(itemReader(invalidInputCsv))
      .processor(processor)
      .writer(writer)
      .faultTolerant()
      .skipLimit(2)
      .skip(MissingUsernameException.class)
      .skip(NegativeAmountException.class)
      .build();
}

Прежде всего, чтобы включить функцию пропуска , нам нужно включить вызов faultTolerant() в процессе построения шага.

В функциях skip() и skipLimit() мы определяем исключения, которые хотим пропустить, и максимальное количество пропускаемых элементов.

В приведенном выше примере, если во время чтения, обработки или записи создается исключение MissingUsernameException или NegativeAmountException, то текущий обрабатываемый элемент будет пропущен и засчитан из общего ограничения, равного двум.

Следовательно, если какое-либо исключение будет вызвано в третий раз, весь шаг завершится ошибкой.

3.1. Использование noSkip

В предыдущем примере любое другое исключение, кроме MissingUsernameException и NegativeAmountException, приводит к сбою нашего шага.

В некоторых ситуациях, однако, может быть более подходящим определить исключения, которые должны привести к сбою нашего шага и пропуску любого другого.

Давайте посмотрим, как мы можем настроить это с помощью skip, skipLimit и noSkip:

@Bean
public Step skippingStep(
  ItemProcessor<Transaction, Transaction> processor,
  ItemWriter<Transaction> writer) throws ParseException {
    return stepBuilderFactory
      .get("skippingStep")
      .<Transaction, Transaction>chunk(10)
      .reader(itemReader(invalidInputCsv))
      .processor(processor)
      .writer(writer)
      .faultTolerant()
      .skipLimit(2)
      .skip(Exception.class)
      .noSkip(SAXException.class)
      .build();
}

С помощью приведенной выше конфигурации мы указываем Spring Batch framework пропускать любое исключение (в пределах настроенного ограничения), кроме SAXException. Это означает, что SAXException всегда вызывает сбой шага.

Порядок вызовов skip() и noSkip() не имеет значения.

4. Использование Custom SkipPolicy

Иногда нам может понадобиться более сложный механизм проверки пропусков. Для этой цели среда Spring Batch предоставляет интерфейс SkipPolicy.

Затем мы можем предоставить собственную реализацию логики пропуска и подключить ее к нашему определению шага.

Помня о предыдущем примере, представьте, что мы все еще хотим определить предел пропуска двух элементов и сделать пропускаемыми только MissingUsernameException и NegativeAmountException.

Однако дополнительным ограничением является то, что мы можем пропустить NegativeAmountException, но только если сумма не превышает установленный предел.

Давайте реализуем нашу пользовательскую политику SkipPolicy:

public class CustomSkipPolicy implements SkipPolicy {

    private static final int MAX_SKIP_COUNT = 2;
    private static final int INVALID_TX_AMOUNT_LIMIT = -1000;

    @Override
    public boolean shouldSkip(Throwable throwable, int skipCount) 
      throws SkipLimitExceededException {

        if (throwable instanceof MissingUsernameException && skipCount < MAX_SKIP_COUNT) {
            return true;
        }

        if (throwable instanceof NegativeAmountException && skipCount < MAX_SKIP_COUNT ) {
            NegativeAmountException ex = (NegativeAmountException) throwable;
            if(ex.getAmount() < INVALID_TX_AMOUNT_LIMIT) {
                return false;
            } else {
                return true;
            }
        }

        return false;
    }
}

Теперь мы можем использовать нашу пользовательскую политику в определении шага:

    @Bean
    public Step skippingStep(
      ItemProcessor<Transaction, Transaction> processor,
      ItemWriter<Transaction> writer) throws ParseException {
        return stepBuilderFactory
          .get("skippingStep")
          .<Transaction, Transaction>chunk(10)
          .reader(itemReader(invalidInputCsv))
          .processor(processor)
          .writer(writer)
          .faultTolerant()
          .skipPolicy(new CustomSkipPolicy())
          .build();
    }

И, как и в предыдущем примере, нам все еще нужно использовать faultTolerant() чтобы включить функцию пропуска.

Однако на этот раз мы не вызываем skip() или noSkip(). Вместо этого мы используем метод skipPolicy(), чтобы предоставить собственную реализацию интерфейса SkipPolicy.

Как мы видим, этот подход дает нам больше гибкости, поэтому может быть хорошим выбором в определенных случаях использования.

5. Заключение

В этом уроке мы представили два способа сделать задание Spring Batch отказоустойчивым.

Несмотря на то, что использование skipLimit() вместе с методами skip() и noSkip() кажется более популярным, в некоторых ситуациях мы можем найти более удобной реализацию пользовательской SkipPolicy.

Как обычно, все примеры кода доступны на GitHub.