Spring批处理汇总值并写入单个值 [英] Spring batch to aggregate values and write single value

查看:131
本文介绍了Spring批处理汇总值并写入单个值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Spring Batch,我需要实现以下目标

  1. 读取csv文件,其中包含日期和金额等详细信息
  2. 汇总同一日期所有金额的总和
  3. 坚持一个带有日期和总和的条目

我过去使用过批处理,因此想到了以下方法.创建一个包含两个步骤的批处理.

第1步:

  1. 阅读器:使用FlatFileItemReader遍历整个文件
  2. 处理器:使用键作为日期和值作为数量填充地图.如果存在条目,则获取值并将其添加到新值
  3. 作家:没有操作作家,因为我不想写作

第2步:

  1. 阅读器:遍历地图的值
  2. 作家:坚持价值观

我能够完成第一步Map.此Map已用@JobScope

声明

我被困在如何为第2步创建读取器的过程中,该读取器仅需要读取值列表.我尝试了ListItemReader,但无法从ListItemReader访问Map.

请提供解决方案或您有更好的方法来解决此问题

谢谢

解决方案

选项1: 如果您的简历已经按日期排序,则可以实现一个组读取器,该读取器读取行直到键值更改为止.之后,整个组可以作为一项传递给处理器.

这样的小组阅读器可能看起来像这样:

  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: 步骤1如您所建议,但是只需为步骤2编写一个tasklet.不需要使用reader-process-writer方法,而是可以使用一个简单的tasklet将地图内容写入文件. >

选项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

}

因此,您为Container创建了一个单个spring-bean实例,将其注入到第2步的处理器中,在其中填充它,还将其注入到上面的阅读器中.

I am using spring batch and I need to achieve the following

  1. Read a csv file which has details like date and amount
  2. Aggregate the sum of all amounts for a same date
  3. Persist one entry with date and the sum

I have used batch in the past and I thought of the following approach. Create a batch with 2 steps.

Step 1:

  1. Reader: Loop through the entire file using FlatFileItemReader
  2. Processor: Populate a map with Key as date and value as amount. If entry is present then get the value and add it to the new value
  3. Writer: No operation writer as I do not wish to write

Step 2:

  1. Reader: Loop through the values of the map
  2. Writer: Persist the values

I was able to acheive step 1 where I populated the Map. This Map has been declared with @JobScope

I am stuck at how do I create the reader for step2 which needs to just read the List of values. I tried ListItemReader but I am not able to access the Map from the ListItemReader.

Please advise a solution or if you have a better approach to tackle this

Thanks

解决方案

Option 1: If your cvs is already sorted by date, you could implement a group reader, which reads lines until a key value changes. After that, the whole group can be passed as one item to the processor.

Such a group reader could look like this:

  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;
  }

You need a SingleItemPeekableItemReader, in order to pre-read the next element. This one wraps your normal reader.

Option 2: Step one is as you have proposed, but simply write a tasklet for step 2. There is no need to use reader-process-writer approach, instead a simple tasklet could be used that writes the content of your map to a file.

Option 3: If you really wanna use a reader-processor-writer approach for step 2, write your own reader that iterates over your map.

something like (I did not test that code):

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

}

so, you create a single spring-bean instance for the Container, inject it in your processor of step 2, fill it there, also inject it in the reader above.

这篇关于Spring批处理汇总值并写入单个值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆