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
相关产品推荐
相关产品推荐

