Spring Batch: запись данных в несколько файлов с динамическим именем файла

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

Вот как данные определяются в базе данных:

Columns --> FILE_NAME, REC_ID, NAME
 Data --> file_1.csv, 1, ABC
 Data --> file_1.csv, 2, BCD
 Data --> file_1.csv, 3, DEF
 Data --> file_2.csv, 4, FGH
 Data --> file_2.csv, 5, DEF
 Data --> file_3.csv, 6, FGH
 Data --> file_3.csv, 7, DEF
 Data --> file_4.csv, 8, FGH

Как видите, в основном имена файлов вместе с данными определены в базе данных, поэтому SpringBatch должен получить эти данные и записать их в соответствующий файл, указанный в базе данных (т.е. file_1.csv должен содержать только 3 записи (1,2 , 3), file_2.csv должны содержать только записи 4 и 5 и т. Д.)

Можно ли использовать MultiResourceItemWriter для этого требования (обратите внимание, что полное имя файла является динамическим и должно быть получено из базы данных).


person forumuser1    schedule 12.04.2013    source источник


Ответы (2)


Я не уверен, но не думаю, что есть простой способ получить это. Вы можете попробовать создать свой собственный ItemWriter следующим образом:

public class DynamicItemWriter  implements ItemStream, ItemWriter<YourEntry> {

    private Map<String, FlatFileItemWriter<YourEntry>> writers = new HashMap<>();

    private LineAggregator<YourEntry> lineAggregator;

    private ExecutionContext executionContext;

    @Override
    public void open(ExecutionContext executionContext) throws ItemStreamException {
        this.executionContext = executionContext;
    }

    @Override
    public void update(ExecutionContext executionContext) throws ItemStreamException {
    }

    @Override
    public void close() throws ItemStreamException {
        for(FlatFileItemWriter f:writers.values()){
            f.close();
        }
    }

    @Override
    public void write(List<? extends YourEntry> items) throws Exception {
        for (YourEntry item : items) {
            FlatFileItemWriter<YourEntry> ffiw = getFlatFileItemWriter(item);
            ffiw.write(Arrays.asList(item));
        }
    }

    public LineAggregator<YourEntry> getLineAggregator() {
        return lineAggregator;
    }

    public void setLineAggregator(LineAggregator<YourEntry> lineAggregator) {
        this.lineAggregator = lineAggregator;
    }


    public FlatFileItemWriter<YourEntry> getFlatFileItemWriter(YourEntry item) {
        String key = item.FileName();
        FlatFileItemWriter<YourEntry> rr = writers.get(key);
        if(rr == null){
            rr = new FlatFileItemWriter<>();
            rr.setLineAggregator(lineAggregator);
            try {
                UrlResource resource = new UrlResource("file:"+key);
                rr.setResource(resource);
                rr.open(executionContext);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            writers.put(key, rr);
            //rr.afterPropertiesSet();
        }
        return rr;
    }
}

и настройте его как писатель:

<bean id="csvWriter" class="com....DynamicItemWriter">
        <property name="lineAggregator">
        <bean
         class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
            <property name="delimiter" value=","/>
            <property name="fieldExtractor" ref="csvFieldExtractor"/>
        </bean>
    </property>
person user1121883    schedule 19.04.2013
comment
Спасибо, Жиль, за ваш ответ и время. Из-за этого сложного по своей природе требования я отказался от настройки фреймворка под свои нужды, поэтому вместо этого я обработал часть записи файла с помощью простого тасклета (конечно, перезапустить пакетное задание сложно) - person forumuser1; 30.04.2013
comment
@ forumuser1 Как вы это сделали, кстати? У меня такая же проблема, и динамическое создание ItemWriters дает много ошибок ... - person Adelin; 09.05.2019
comment
Спасибо за этот полезный ответ. В моем случае у меня была проблема с файлом, недоступным для записи на втором writer.open, поэтому мне пришлось установить @StepScope для писателя. Проблема, связанная с этим, потому что «перезапуск» был истинным после первого раза: stackoverflow.com/a/26729596/2641426 - person DependencyHell; 19.06.2020

В spring -batch это можно сделать с помощью ClassifierCompositeItemWriter.

Поскольку ClassifierCompositeItemWriter дает вам доступ к вашему объекту во время записи, вы можете написать собственную логику, чтобы инструктировать Spring выполнять запись в разные файлы.

Взгляните на образец ниже. ClassifierCompositeItemWriter требуется реализация интерфейса Classifier. Ниже вы можете увидеть, что я создал лямбду, в которой я реализую метод classify() интерфейса Classifier. Метод classify() - это то место, где вы создадите свой ItemWriter. В нашем примере ниже мы создали FlatFileItemWriter, который получает имя файла из самого item, а затем создает для него ресурс.

@Bean
public ClassifierCompositeItemWriter<YourDataObject> yourDataObjectItemWriter(
    Classifier<YourDataObject, ItemWriter<? super YourDataObject>> itemWriterClassifier
) {
  ClassifierCompositeItemWriter<YourDataObject> compositeItemWriter = new ClassifierCompositeItemWriter<>();
  compositeItemWriter.setClassifier(itemWriterClassifier);
  return compositeItemWriter;
}

@Bean
public Classifier<YourDataObject, ItemWriter<? super YourDataObject>> itemWriterClassifier() {
  return yourDataObject -> {
    String fileName = yourDataObject.getFileName();

    BeanWrapperFieldExtractor<YourDataObject> fieldExtractor = new BeanWrapperFieldExtractor<>();
    fieldExtractor.setNames(new String[]{"recId", "name"});
    DelimitedLineAggregator<YourDataObject> lineAggregator = new DelimitedLineAggregator<>();
    lineAggregator.setFieldExtractor(fieldExtractor);

    FlatFileItemWriter<YourDataObject> itemWriter = new FlatFileItemWriter<>();
    itemWriter.setResource(new FileSystemResource(fileName));
    itemWriter.setAppendAllowed(true);
    itemWriter.setLineAggregator(lineAggregator);
    itemWriter.setHeaderCallback(writer -> writer.write("REC_ID,NAME"));

    itemWriter.open(new ExecutionContext());
    return itemWriter;
  };
}

Наконец, вы можете прикрепить свой ClassifierCompositeItemWriter к этапу пакетной обработки, как вы обычно прикрепляете свой ItemWriter.

@Bean
public Step myCustomStep(
    StepBuilderFactory stepBuilderFactory
) {
  return stepBuilderFactory.get("myCustomStep")
      .<?, ?>chunk(1000)
      .reader(myCustomReader())
      .writer(yourDataObjectItemWriter(itemWriterClassifier(null)))
      .build();
}

ПРИМЕЧАНИЕ. Как указано в комментариях @Ping, для каждого фрагмента будет создан новый писатель, что обычно является плохой практикой и не оптимальным решением. Лучшим решением было бы сохранить хэш-карту с именем файла и писателем, чтобы вы могли повторно использовать писателя.

person Rash    schedule 09.11.2018
comment
В приведенном выше примере будет ли создаваться новый писатель для каждого фрагмента? Если нет, как ClassifierCompositeItemWriter узнает, что ему нужно повторно использовать ItemWriter на основе некоторого атрибута в YourObject? - person Ping; 16.11.2019
comment
В приведенном выше примере новый модуль записи будет создан для каждого фрагмента, потому что itemWriterClassifier вызывается для каждого объекта, который он хочет записать, и создает новый модуль записи на месте. Это явно плохо и было написано только в демонстрационных целях. Но я думаю, вы легко можете сохранить ссылку на автора и использовать ее повторно. stackoverflow.com/questions/53501152/ - person Rash; 16.11.2019
comment
Пример, на который вы ссылаетесь, не учитывает динамические имена файлов. Существует фиксированный набор компонентов записи, из которых можно выбрать, что известно во время компиляции, а не во время выполнения. В текущем примере из вопроса, если OP в будущем получит file5 в столбце FILE_NAME базы данных, нет доступного модуля записи для его обработки. Писатели должны быть созданы во время выполнения и передать имя файла для записи. Писатели также должны быть кэшированы, чтобы их можно было повторно использовать при повторной записи в тот же файл. - person Ping; 16.11.2019
comment
Вы можете просто создать хэш-карту или некоторую пару «ключ-значение» для динамического создания модуля записи, если он не существует, а затем использовать тот же самый, если он существует. - person Rash; 16.11.2019
comment
Точно. Я считаю, что это нужно добавить к ответу? - person Ping; 17.11.2019