Spring Batch:修改RegexLineTokenizer与ItemReader处理合并行数据
解决方案:在ItemReader层面拆分合并行,复用原有RegexLineTokenizer
针对你遇到的合并行问题,核心思路是在ItemReader读取行之后、交给Tokenizer解析之前,先拆分同一行中的多个带引号姓名条目,再将拆分后的单个条目逐个传递给原有的RegexLineTokenizer处理。这种方式不需要修改输入文件,也能复用你已有的正则和映射逻辑,同时适配大数据量场景(逐行处理,无内存溢出风险)。
实现步骤
1. 自定义包装类ItemReader
实现ItemStreamReader接口,内部包装原有的FlatFileItemReader,通过队列缓存拆分后的单行数据,每次read()调用返回一个拆分后的条目对应的Item。
import org.springframework.batch.item.ItemStreamReader; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.file.FlatFileItemReader; import java.util.LinkedList; import java.util.Queue; import java.util.regex.Matcher; import java.util.regex.Pattern; public class MultiLineSplittingItemReader<T> implements ItemStreamReader<T> { private final FlatFileItemReader<T> delegateReader; private final Queue<String> splitLinesCache = new LinkedList<>(); // 匹配所有双引号包裹的姓名条目 private final Pattern nameEntryPattern = Pattern.compile("\"([^\"]+)\""); public MultiLineSplittingItemReader(FlatFileItemReader<T> delegateReader) { this.delegateReader = delegateReader; } @Override public T read() throws Exception { // 优先从缓存队列取拆分后的单行 if (!splitLinesCache.isEmpty()) { String singleLine = splitLinesCache.poll(); return delegateReader.getLineMapper().mapLine(singleLine, delegateReader.getCurrentItemCount()); } // 读取原始行 String originalLine = delegateReader.readLine(); if (originalLine == null) { return null; } // 拆分原始行中的多个姓名条目 Matcher matcher = nameEntryPattern.matcher(originalLine); while (matcher.find()) { // 重新包装成带引号的单行格式,适配原有Tokenizer splitLinesCache.add("\"" + matcher.group(1) + "\""); } // 递归调用,返回第一个拆分后的条目 return read(); } @Override public void open(ExecutionContext executionContext) throws ItemStreamException { delegateReader.open(executionContext); } @Override public void update(ExecutionContext executionContext) throws ItemStreamException { delegateReader.update(executionContext); } @Override public void close() throws ItemStreamException { delegateReader.close(); splitLinesCache.clear(); } }
2. 配置Spring Batch组件
复用你原有的RegexLineTokenizer和FieldSetMapper,仅替换ItemReader为自定义的包装类:
import org.springframework.batch.item.file.FlatFileItemReader; import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder; import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper; import org.springframework.batch.item.file.transform.RegexLineTokenizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; @Configuration public class BatchConfig { // 原有Tokenizer配置,无需修改 @Bean public RegexLineTokenizer nameTokenizer() { RegexLineTokenizer tokenizer = new RegexLineTokenizer(); tokenizer.setNames("firstName", "lastName"); tokenizer.setRegex("(.*?)(?: .*)* (.*)"); return tokenizer; } // 原有基础Reader配置,无需修改 @Bean public FlatFileItemReader<Person> basePersonReader() { return new FlatFileItemReaderBuilder<Person>() .name("basePersonReader") .resource(new ClassPathResource("names.txt")) .lineTokenizer(nameTokenizer()) .fieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {{ setTargetType(Person.class); }}) .build(); } // 替换为自定义拆分Reader @Bean public ItemStreamReader<Person> splittingPersonReader() { return new MultiLineSplittingItemReader<>(basePersonReader()); } }
方案说明
- 内存友好:逐行读取原始文件,拆分后的条目仅缓存少量在队列中,不会加载全量数据到内存,适配大数据量场景。
- 复用原有逻辑:完全保留你已验证的正则表达式和字段映射规则,无需重新调试Tokenizer。
- 无文件修改:直接读取原始输入文件,不需要提前预处理生成新文件。
内容的提问来源于stack exchange,提问作者Vamsi
相关产品推荐
相关产品推荐

