#spring-batch #spring-batch-tasklet
Вопрос:
Я пытаюсь записать элементы в файл, в комплекте с «идентификатором типа». В файле будет более одного такого набора элементов. Каждый набор элементов с одинаковым идентификатором типа должен иметь соответствующий верхний и нижний колонтитулы.
Я настроил Spring Batch для отдельного Step
чтения записей базы данных по идентификатору типа (используя a RepositoryItemReader
, передавая ему идентификатор типа в качестве аргумента метода) и записываю их в файл вместе с их верхним и нижним колонтитулами, используя FlatFileHeaderCallback
и FlatFileFooterCallback
.
Для первого шага FlatFileItemWriter
append
установлено значение false
, в то время как для авторов последующих шагов append
установлено true
значение, чтобы они не перезаписывали то, что было ранее записано в файл.
Мой файл должен выглядеть так:
Header TYPE ID 001 ::Other Header Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Footer TYPE ID 001 ::Other Footer Info:: <---Written by Step 1, append false
Header TYPE ID 002 ::Other Header Info:: <---Written by Step 2, append true
Item TYPE ID 002 ::Other Item Info:: <---Written by Step 2, append true
Item TYPE ID 002 ::Other Item Info:: <---Written by Step 2, append true
Footer TYPE ID 002 ::Other Footer Info:: <---Written by Step 2, append true
Header TYPE ID 003 ::Other Header Info:: <---Written by Step 3, append true
Item TYPE ID 003 ::Other Item Info:: <---Written by Step 3, append true
Item TYPE ID 003 ::Other Item Info:: <---Written by Step 3, append true
Item TYPE ID 003 ::Other Item Info:: <---Written by Step 3, append true
Item TYPE ID 003 ::Other Item Info:: <---Written by Step 3, append true
Footer TYPE ID 003 ::Other Footer Info:: <---Written by Step 3, append true
Проблема 1: Добавление
Тем не менее, AbstractFileItemWriter.doOpen()
не будет вызывать мой FlatFileHeaderCallback
, когда append
верно, поэтому заголовок записывается только для первого шага, в строке 1 файла, и в файле отсутствуют «промежуточные» заголовки:
Header TYPE ID 001 ::Other Header Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Item TYPE ID 001 ::Other Item Info:: <---Written by Step 1, append false
Footer TYPE ID 001 ::Other Footer Info:: <---Written by Step 1, append false
Item TYPE ID 002 ::Other Item Info:: <---Written by Step 2, append true
Item TYPE ID 002 ::Other Item Info:: <---Written by Step 2, append true
Footer TYPE ID 002 ::Other Footer Info:: <---Written by Step 2, append true
Item TYPE ID 003 ::Other Item Info:: <---Written by Step 3, append true
Item TYPE ID 003 ::Other Item Info:: <---Written by Step 3, append true
Item TYPE ID 003 ::Other Item Info:: <---Written by Step 3, append true
Item TYPE ID 003 ::Other Item Info:: <---Written by Step 3, append true
Footer TYPE ID 003 ::Other Footer Info:: <---Written by Step 3, append true
Решение проблемы 1
Чтобы решить эту проблему, я отказался от FlatFileFooterCallback
и написал свой собственный FlatFileItemWriter
, который записывает заголовок независимо от append
того, но только если количество написанных строк равно нулю. Пока все идет хорошо. Он создает файл в нужном мне формате вместе с «промежуточными» заголовками.
public class MyFlatFileItemWriter extends FlatFileItemWriter<MyItem> {
@Override
public String doWrite(List<? extends MyItem> items) {
StringBuilder lines = new StringBuilder();
if (getOutputState().getLinesWritten() == 0) {
lines.append( <<MY HEADER CONTENT>> ))
.append(lineSeparator);
}
Iterator<? extends MyItem> iterator = items.iterator();
while (iterator.hasNext()) {
MyItem item = iterator.next();
lines.append(this.lineAggregator.aggregate(item)).append(this.lineSeparator);
}
return lines.toString();
}
}
Problem 2: Writing Zero Items
A further requirement is that even if the database has no items for a given type ID, the file must have a header and footer corresponding to the type ID, (and no item lines in between). It should look like this:
Header TYPE ID 001 ::Other Header Info:: <---Written by Step 1, append false
Footer TYPE ID 001 ::Other Footer Info:: <---Written by Step 1, append false
Header TYPE ID 002 ::Other Header Info:: <---Written by Step 2, append true
Footer TYPE ID 002 ::Other Footer Info:: <---Written by Step 2, append true
Header TYPE ID 003 ::Other Header Info:: <---Written by Step 3, append true
Footer TYPE ID 003 ::Other Footer Info:: <---Written by Step 3, append true
But instead, only the footers are written. No sign of the headers:
Footer TYPE ID 001 ::Other Footer Info:: <---Written by Step 1, append false
Footer TYPE ID 002 ::Other Footer Info:: <---Written by Step 2, append true
Footer TYPE ID 003 ::Other Footer Info:: <---Written by Step 3, append true
I tracked this down to the way SimpleChunkProcessor
works. If it receives an empty list of items, it won’t call through to MyItemWriter.write()
, so there’s no chance my header is written out when I have zero items.
From SimpleChunkProcessor
:
public final void process(StepContribution contribution, Chunk<I> inputs) throws Exception {
this.initializeUserData(inputs);
if (!this.isComplete(inputs)) {
Chunk<O> outputs = this.transform(contribution, inputs);
contribution.incrementFilterCount(this.getFilterCount(inputs, outputs));
this.write(contribution, inputs, this.getAdjustedOutputs(inputs, outputs));
}
}
And also from SimpleChunkProcessor
:
protected boolean isComplete(Chunk<I> inputs) {
return inputs.isEmpty();
}
Possible Solution to Problem 2
However, SimpleChunkProcessor.isComplete()
is protected, so I subclassed it, overrode isComplete()
to return false
:
public class HeaderWritingChunkProcessor extends SimpleChunkProcessor<MyItem, MyItem> {
public HeaderWritingChunkProcessor(ItemWriter<MyItem> itemWriter) {
super(itemWriter);
}
@Override
protected boolean isComplete(Chunk<MyItem> inputs) {
return false;
}
}
But how do I get my HeaderWritingChunkProcessor
into my application? Where do I set it in the configuration of my step, or elsewhere?
Or have I missed a simpler solution to achieve my two core requirements?
- Write a header even in appending mode, not just on line 1 of the file.
- Write a header even if there are zero items to write.
I should add, the footers are always written as I need them in them in the file, so no issue with those.
Thank you.