Spring Batch多行CSV记录读取咨询:自定义格式处理
处理Spring Batch中START/END包裹的多行CSV记录
先直接解答你的问题,再给实际配置示例,帮你理清和常规FlatFileItemReader的差异:
问题1:headerRecordTokenizer的作用与HashMap<>(4)的含义
- headerRecordTokenizer这类分词器的作用:
PatternMatchingCompositeLineTokenizer是一个多模式匹配分词器,它会根据每行的前缀(比如你的START)匹配对应的Tokenizer。比如headerRecordTokenizer就是专门解析以START开头的行——这类行的字段格式大概率和中间数据行、END行不一样,需要单独的Tokenizer拆分字段。同理,你还需要其他Tokenizer处理中间行、END行。 - HashMap<>(4)的含义:这只是初始化HashMap时设置的初始容量为4,和每次读取的行数完全无关。这个参数是HashMap的性能优化项,用来减少扩容次数,和Spring Batch的行读取逻辑没有关系。
问题2:配置识别END作为记录结束标记
要让Spring Batch把START到END之间的多行当作一个完整记录,核心是用MultiLineItemReader——它专门处理多行组成一个Item的场景。配合PatternMatchingCompositeLineTokenizer解析不同格式的行,再通过自定义逻辑把多行数据聚合到一个业务实体中。
具体配置要点:
- 给每种行类型定义专属Tokenizer:比如
START行、中间数据行、END行各一个。 - 配置
PatternMatchingCompositeLineTokenizer,给每个行模式绑定对应的Tokenizer(比如"START*"对应headerTokenizer,"END*"对应endTokenizer)。 - 给
MultiLineItemReader设置recordSeparatorPolicy为SuffixRecordSeparatorPolicy,指定suffix为"END"——这样Spring Batch就会把从START开始到END结束的所有行当作一个完整记录。 - 自定义
LineMapper或ItemProcessor,把解析后的多行数据合并成一个业务对象。
常规FlatFileItemReader与当前场景的差异
- 常规
FlatFileItemReader:每行对应一个Item,一行就是一条完整记录,用单一Tokenizer解析所有行。 - 当前场景:多行组成一个Item,不同行有不同格式(START/中间/END),需要用
MultiLineItemReader聚合多行,再用PatternMatchingCompositeLineTokenizer针对不同行格式做解析,最后把多行数据合并成一个业务对象。
示例配置(Java Config)
假设你的业务实体是OrderRecord,包含START行的订单号、中间行的商品信息、END行的总金额:
@Configuration public class BatchConfig { // 解析START行的Tokenizer @Bean public DelimitedLineTokenizer headerTokenizer() { DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(); tokenizer.setNames("recordType", "orderId", "customerId"); tokenizer.setDelimiter(","); return tokenizer; } // 解析中间数据行的Tokenizer @Bean public DelimitedLineTokenizer dataTokenizer() { DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(); tokenizer.setNames("recordType", "productId", "quantity", "price"); tokenizer.setDelimiter(","); return tokenizer; } // 解析END行的Tokenizer @Bean public DelimitedLineTokenizer endTokenizer() { DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(); tokenizer.setNames("recordType", "totalAmount"); tokenizer.setDelimiter(","); return tokenizer; } // 配置多模式分词器 @Bean public PatternMatchingCompositeLineTokenizer compositeTokenizer() { PatternMatchingCompositeLineTokenizer tokenizer = new PatternMatchingCompositeLineTokenizer(); Map<String, LineTokenizer> tokenizers = new HashMap<>(4); tokenizers.put("START*", headerTokenizer()); tokenizers.put("DATA*", dataTokenizer()); // 假设中间行以DATA开头 tokenizers.put("END*", endTokenizer()); tokenizer.setTokenizers(tokenizers); return tokenizer; } // 自定义LineMapper,聚合多行到OrderRecord @Bean public LineMapper<OrderRecord> orderLineMapper() { DefaultLineMapper<OrderRecord> lineMapper = new DefaultLineMapper<>(); lineMapper.setLineTokenizer(compositeTokenizer()); lineMapper.setFieldSetMapper(fieldSet -> { String recordType = fieldSet.readString("recordType"); OrderRecord orderRecord = new OrderRecord(); switch (recordType) { case "START": orderRecord.setOrderId(fieldSet.readString("orderId")); orderRecord.setCustomerId(fieldSet.readString("customerId")); break; case "DATA": // 实际场景如果有多条数据行,可结合ItemProcessor累加商品信息 orderRecord.addProduct(new Product(fieldSet.readString("productId"), fieldSet.readInt("quantity"), fieldSet.readDouble("price"))); break; case "END": orderRecord.setTotalAmount(fieldSet.readDouble("totalAmount")); break; } return orderRecord; }); return lineMapper; } // 配置MultiLineItemReader,识别END作为记录结束 @Bean public MultiLineItemReader<OrderRecord> multiLineItemReader(Resource inputResource) { MultiLineItemReader<OrderRecord> reader = new MultiLineItemReader<>(); reader.setLineMapper(orderLineMapper()); // 设置记录分隔策略:以END结尾的行作为记录结束 SuffixRecordSeparatorPolicy separatorPolicy = new SuffixRecordSeparatorPolicy(); separatorPolicy.setSuffix("END"); reader.setRecordSeparatorPolicy(separatorPolicy); reader.setResource(inputResource); return reader; } }
注:如果中间行没有固定前缀,可以调整PatternMatchingCompositeLineTokenizer的匹配规则,比如用"*"匹配中间行,但要确保START和END的匹配优先级更高。
内容的提问来源于stack exchange,提问作者tan
相关产品推荐
相关产品推荐

