11.5 多行记录

虽然通常的flat文件,每个记录限制是一行,但是一个文件可能与多种格式多行记录,这是很常见的.以下摘录文件说明了这一点:

  1. HEA;0013100345;2007-02-15
  2. NCU;Smith;Peter;;T;20014539;F
  3. BAD;;Oak Street 31/A;;Small Town;00235;IL;US
  4. FOT;2;2;267.34

之间的所有行从’HEA’开始和从’FOT’开始被认为是一个记录.有一些注意事项,必须正确地处理这种情况:
不是一次读取一条记录,而是ItemReader必须读取多行的每一行记录作为一个分组.这样它就可以被完整的传递到ItemWriter.

  1. 每一行可能需要标记不同的类型.

因为单行记录跨越多行,我们可能不知道有多少行,所以ItemReader必须总是小心的读一个完整的记录.为了做到这一点,一个自定义ItemReader,应该实现一个FlatFileItemReader的包装器

  1. <bean id="itemReader" class="org.spr...MultiLineTradeItemReader">
  2. <property name="delegate">
  3. <bean class="org.springframework.batch.item.file.FlatFileItemReader">
  4. <property name="resource" value="data/iosample/input/multiLine.txt" />
  5. <property name="lineMapper">
  6. <bean class="org.spr...DefaultLineMapper">
  7. <property name="lineTokenizer" ref="orderFileTokenizer"/>
  8. <property name="fieldSetMapper">
  9. <bean class="org.spr...PassThroughFieldSetMapper" />
  10. </property>
  11. </bean>
  12. </property>
  13. </bean>
  14. </property>
  15. </bean>

确保正确标记的每一行,特别是重要的固定长度的输入,可以将PatternMatchingCompositeLineTokenizer委托于FlatFileItemReader.见章节 “Multiple Record Types within a Single File”中的更多的细节.这个委托reader,会将FieldSet传递到PassThroughFieldSetMapper再返回每一行到ItemReader 包装器中.

  1. <bean id="orderFileTokenizer" class="org.spr...PatternMatchingCompositeLineTokenizer">
  2. <property name="tokenizers">
  3. <map>
  4. <entry key="HEA*" value-ref="headerRecordTokenizer" />
  5. <entry key="FOT*" value-ref="footerRecordTokenizer" />
  6. <entry key="NCU*" value-ref="customerLineTokenizer" />
  7. <entry key="BAD*" value-ref="billingAddressLineTokenizer" />
  8. </map>
  9. </property>
  10. </bean>

这个包装器必须能够识别结束的记录,所以它可以不断调用delegate的read()方法直到结束.
对于读取的每一行,该wrapper应该绑定返回的item,一旦读到footer结束,item应该传递给ItemProcessor和ItemWriter返回.
private FlatFileItemReader

delegate;

  1. public Trade read() throws Exception {
  2. Trade t = null;
  3. for (FieldSet line = null; (line = this.delegate.read()) != null;) {
  4. String prefix = line.readString(0);
  5. if (prefix.equals("HEA")) {
  6. t = new Trade(); // Record must start with header
  7. }
  8. else if (prefix.equals("NCU")) {
  9. Assert.notNull(t, "No header was found.");
  10. t.setLast(line.readString(1));
  11. t.setFirst(line.readString(2));
  12. ...
  13. }
  14. else if (prefix.equals("BAD")) {
  15. Assert.notNull(t, "No header was found.");
  16. t.setCity(line.readString(4));
  17. t.setState(line.readString(6));
  18. ...
  19. }
  20. else if (prefix.equals("FOT")) {
  21. return t; // Record must end with footer
  22. }
  23. }
  24. Assert.isNull(t, "No 'END' was found.");
  25. return null;
  26. }