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

Spring Batch Item Reader读取以#开头的文件失败,如何解决?

解决Spring Batch读取以#开头行的文件问题

问题原因

你的文件用#作为分隔符,当行以#开头时,DelimitedTokenizer会把第一个令牌解析为空字符串。如果你的Java对象对应的字段不允许为空,或者字段数量与令牌数量不匹配,就会触发读取失败。比如正常行能解析出4个非空令牌,而开头带#的行解析出的第一个令牌为空,后续三个才是有效数据,和对象字段的对应关系就会出错。

解决方案

方案1:配置DelimitedTokenizer忽略空令牌

通过setIgnoreEmptyTokens(true)让Tokenizer跳过空令牌,这样开头#产生的空令牌会被忽略,剩下的有效令牌就能正确映射到对象字段。注意要保证Java对象的字段数量和有效令牌数量一致。

示例代码:

@Bean
public FlatFileItemReader<YourEntity> itemReader() {
    return new FlatFileItemReaderBuilder<YourEntity>()
            .name("yourEntityReader")
            .resource(new ClassPathResource("your-file.txt"))
            .lineMapper(new DefaultLineMapper<YourEntity>() {{
                setLineTokenizer(new DelimitedTokenizer() {{
                    setDelimiter("#");
                    setIgnoreEmptyTokens(true); // 忽略空令牌
                    setNames("field1", "field2", "field3"); // 对应对象的三个字段
                }});
                setFieldSetMapper(new BeanWrapperFieldSetMapper<YourEntity>() {{
                    setTargetType(YourEntity.class);
                }});
            }})
            .build();
}

方案2:自定义LineTokenizer处理行首的#

如果需要保留完整的令牌数量,可以自定义Tokenizer,先移除行首的#再进行解析。

示例代码:

public class CustomDelimitedTokenizer extends DelimitedTokenizer {
    @Override
    public FieldSet tokenize(String line) {
        // 移除行首的#
        if (line.startsWith("#")) {
            line = line.substring(1);
        }
        return super.tokenize(line);
    }
}

在ItemReader中使用自定义Tokenizer:

@Bean
public FlatFileItemReader<YourEntity> itemReader() {
    return new FlatFileItemReaderBuilder<YourEntity>()
            .name("yourEntityReader")
            .resource(new ClassPathResource("your-file.txt"))
            .lineMapper(new DefaultLineMapper<YourEntity>() {{
                setLineTokenizer(new CustomDelimitedTokenizer() {{
                    setDelimiter("#");
                    setNames("field1", "field2", "field3", "field4"); // 对应四个字段
                }});
                setFieldSetMapper(new BeanWrapperFieldSetMapper<YourEntity>() {{
                    setTargetType(YourEntity.class);
                }});
            }})
            .build();
}

方案3:用PatternMatchingCompositeLineMapper兼容两种行格式

如果文件同时存在开头带#和不带#的行,可以用PatternMatchingCompositeLineMapper分别处理不同格式的行。

示例代码:

@Bean
public PatternMatchingCompositeLineMapper<YourEntity> lineMapper() {
    PatternMatchingCompositeLineMapper<YourEntity> lineMapper = new PatternMatchingCompositeLineMapper<>();
    
    // 处理开头带#的行
    DefaultLineMapper<YourEntity> hasHashLineMapper = new DefaultLineMapper<>();
    DelimitedTokenizer hasHashTokenizer = new DelimitedTokenizer();
    hasHashTokenizer.setDelimiter("#");
    hasHashTokenizer.setIgnoreEmptyTokens(true);
    hasHashTokenizer.setNames("field1", "field2", "field3");
    hasHashLineMapper.setLineTokenizer(hasHashTokenizer);
    hasHashLineMapper.setFieldSetMapper(new BeanWrapperFieldSetMapper<YourEntity>() {{
        setTargetType(YourEntity.class);
    }});
    
    // 处理正常行
    DefaultLineMapper<YourEntity> normalLineMapper = new DefaultLineMapper<>();
    DelimitedTokenizer normalTokenizer = new DelimitedTokenizer();
    normalTokenizer.setDelimiter("#");
    normalTokenizer.setNames("field1", "field2", "field3", "field4");
    normalLineMapper.setLineTokenizer(normalTokenizer);
    normalLineMapper.setFieldSetMapper(new BeanWrapperFieldSetMapper<YourEntity>() {{
        setTargetType(YourEntity.class);
    }});
    
    // 配置模式映射
    Map<String, LineMapper<YourEntity>> patternMap = new HashMap<>();
    patternMap.put("#*", hasHashLineMapper); // 匹配开头带#的行
    patternMap.put("*", normalLineMapper); // 匹配其他行
    lineMapper.setLineMappers(patternMap);
    
    return lineMapper;
}

@Bean
public FlatFileItemReader<YourEntity> itemReader() {
    return new FlatFileItemReaderBuilder<YourEntity>()
            .name("yourEntityReader")
            .resource(new ClassPathResource("your-file.txt"))
            .lineMapper(lineMapper())
            .build();
}

注意事项

  • 确保Java对象的字段数量与解析后的令牌数量一致,字段类型匹配。
  • 如果文件末尾有多余的#,可以设置DelimitedTokenizer的setStrict(false)忽略末尾空令牌,避免因令牌数量不匹配报错。

内容的提问来源于stack exchange,提问作者Srikaran

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 01:47:32