Пакет Spring для агрегирования значений и записи одного значения
Я использую весеннюю партию и мне нужно добиться следующего
- Прочитайте CSV-файл, который содержит такие данные, как дата и сумма
- Суммируйте сумму всех сумм за ту же дату
- Сохраните одну запись с датой и суммой
Я использовал партию в прошлом, и я подумал о следующем подходе. Создать партию за 2 шага.
Шаг 1:
- Reader: цикл по всему файлу с помощью FlatFileItemReader
- Процессор: заполните карту ключом в качестве даты и значением в виде суммы. Если запись присутствует, тогда получите значение и добавьте его к новому значению
- Писатель: Нет операции писатель, так как я не хочу писать
Шаг 2:
- Читатель: перебрать значения карты
- Писатель: сохраняйте ценности
Я смог достичь шага 1, где я заселил Map
, это Map
был объявлен с @JobScope
Я застрял в том, как создать читателя для шага 2, который должен просто прочитать список значений. Я старался ListItemReader
но я не могу получить доступ к Map
от ListItemReader
,
Пожалуйста, посоветуйте решение или, если у вас есть лучший подход для решения этой проблемы
Спасибо
1 ответ
Вариант 1. Если ваши cvs уже отсортированы по дате, вы можете реализовать программу чтения групп, которая читает строки до изменения значения ключа. После этого вся группа может быть передана как один элемент процессору.
Такой групповой читатель может выглядеть так:
private SingleItemPeekableItemReader<I> reader;
private ItemReader<I> peekReaderDelegate;
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(peekReaderDelegate, "The 'itemReader' may not be null");
this.reader= new SingleItemPeekableItemReader<I>();
this.reader.setDelegate(peekReaderDelegate);
}
@Override
// GroupDTO is just a simple container. It is also possible to use
// List<I> instead of GroupDTO<I>
public GroupDTO<I> read() throws Exception {
State state = State.NEW; // a simple enum with the states NEW, READING, and COMPLETE
GroupDTO<I> group = null;
I item = null;
while (state != State.COMPLETE) {
item = reader.read();
switch (state) {
case NEW: {
if (item == null) {
// end reached
state = State.COMPLETE;
break;
}
group = new GroupDTO<I>();
group.addItem(item);
state = State.READING;
I nextItem = reader.peek();
// isGroupBreak returns true, if 'item' and 'nextItem' do NOT belong to the same group
if (nextItem == null || getGroupBreakStrategy.isGroupBreak(item, nextItem)) {
state = State.COMPLETE;
}
break;
}
case READING: {
group.addItem(item);
// peek and check if there the peeked entry has a new date
I nextItem = peekEntry();
// isGroupBreak returns true, if 'item' and 'nextItem' do NOT belong to the same group
if (nextItem == null || getGroupBreakStrategy.isGroupBreak(item, nextItem)) {
state = State.COMPLETE;
}
break;
}
default: {
throw new org.springframework.expression.ParseException(groupCounter, "ParsingError: Reader is in an invalid state");
}
}
}
return group;
}
Вам нужен SingleItemPeekableItemReader, чтобы предварительно прочитать следующий элемент. Этот обертывает ваш нормальный читатель.
Вариант 2: Шаг первый, как вы предложили, но просто напишите тасклет для шага 2. Нет необходимости использовать подход "читатель-процесс-писатель", вместо этого можно использовать простой тасклет, который записывает содержимое вашей карты в файл.,
Вариант 3: Если вы действительно хотите использовать подход "читатель-процессор-писатель" для шага 2, напишите свой собственный читатель, который перебирает вашу карту.
что-то вроде (я не тестировал этот код):
public class MapReader implements ItemReader {
private MapContainer container;
private Iterator<Map.Entry<Date, Integer> mapIterator;
@PostConstruct
public void afterPropertiesSet() {
Assert.notNull(container);
iterator = container.getMap().entry().iterator;
}
public void setMapContainer(MapContainer container) {
this.container = container;
}
public Map.Entry<Date, Integer> read() {
if (iterator.hasNext()) {
return iterator.next();
}
return null;
}
}
@Component
public class MapContainer {
private Map<Date, Integer> data = new Hashmap<>();
public Map<Date, Integer> getMap() {
return data;
}
// add modifier method as needed for step 1
}
Итак, вы создаете один экземпляр Spring-bean-компонента для Контейнера, вставляете его в свой процессор из шага 2, заполняете его там, а также внедряете его в читатель выше.