You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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解析不同格式的行,再通过自定义逻辑把多行数据聚合到一个业务实体中。

具体配置要点:

  1. 给每种行类型定义专属Tokenizer:比如START行、中间数据行、END行各一个。
  2. 配置PatternMatchingCompositeLineTokenizer,给每个行模式绑定对应的Tokenizer(比如"START*"对应headerTokenizer,"END*"对应endTokenizer)。
  3. 给MultiLineItemReader设置recordSeparatorPolicy为SuffixRecordSeparatorPolicy,指定suffix为"END"——这样Spring Batch就会把从START开始到END结束的所有行当作一个完整记录。
  4. 自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 19:35:22