Spring Batch的LineMapper如何配置可选字段并判断CSV列是否存在
Spring Batch 动态可选列CSV解析、分表写入实现方案
一、可选字段适配的LineMapper核心配置
原有固定列配置仅支持列顺序固定、列数完全匹配的场景,要兼容部分列缺失、列顺序不固定的CSV文件,需要做3处核心调整:
- 关闭
DelimitedLineTokenizer的严格校验,允许实际列数和预设列数不一致 - 关闭
BeanWrapperFieldSetMapper的严格校验,缺失的字段自动映射为null,不抛出绑定异常 - 读取CSV首行表头动态设置列名,避免列顺序变化导致的映射错误
完整的Reader配置示例:
@Bean public FlatFileItemReader<CSVFileRequest> csvReader() { FlatFileItemReader<CSVFileRequest> reader = new FlatFileItemReader<>(); // 替换为实际的CSV文件路径 reader.setResource(new ClassPathResource("target.csv")); // 跳过第一行表头,后续通过回调解析表头 reader.setLinesToSkip(1); // 初始化LineMapper核心组件 DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(); tokenizer.setDelimiter(","); tokenizer.setStrict(false); BeanWrapperFieldSetMapper<CSVFileRequest> fieldSetMapper = new BeanWrapperFieldSetMapper<>(); fieldSetMapper.setTargetType(CSVFileRequest.class); // 关键配置:字段缺失时不抛错,自动赋值为null fieldSetMapper.setStrict(false); DefaultLineMapper<CSVFileRequest> lineMapper = new DefaultLineMapper<>(); lineMapper.setLineTokenizer(tokenizer); lineMapper.setFieldSetMapper(fieldSetMapper); reader.setLineMapper(lineMapper); // 解析表头动态绑定列名,同时做文件级别的字段存在判断 Set<String> table2Columns = Set.of("order_id", "shipment", "amount", "acc_num", "due_date"); reader.setSkippedLinesCallback(skippedLines -> { String header = skippedLines.get(0).trim(); String[] actualColumns = Arrays.stream(header.split(",")) .map(String::trim) .toArray(String[]::new); // 动态设置实际存在的列名,适配任意列顺序 tokenizer.setNames(actualColumns); // 校验是否包含第二张表所需全量字段,结果存入Step上下文 boolean hasTable2Fields = new HashSet<>(Arrays.asList(actualColumns)).containsAll(table2Columns); StepSynchronizationManager.getContext() .getStepExecution() .getExecutionContext() .put("hasTable2Fields", hasTable2Fields); }); return reader; }
对应的CSVFileRequest实体类需要定义所有可能出现的字段,名称和CSV列名保持一致,类型匹配业务字段类型即可,缺失的列映射后对应字段值为null。
二、字段存在判断与分表写入逻辑
字段存在性判断分两个维度处理:
- 文件维度判断:在上述表头解析的回调中已经完成,判断结果
hasTable2Fields存入Step执行上下文,整个Step执行阶段都可以读取。如果该值为false,说明当前CSV文件完全没有第二张表需要的列,所有行都不需要写入第二张表。 - 行维度判断:如果
hasTable2Fields为true,在写入逻辑中逐行判断第二张表对应字段是否符合写入规则即可:如果业务要求所有字段非空才写入,就校验所有第二张表字段不为null;如果允许部分字段为空,直接赋值写入即可。
分表写入可以通过自定义ItemWriter实现,逻辑参考:
public class SplitTableWriter implements ItemWriter<CSVFileRequest> { // 提前注入两张表的对应Mapper/Repository private final Table1Mapper table1Mapper; private final Table2Mapper table2Mapper; @Override public void write(Chunk<? extends CSVFileRequest> chunk) { // 从上下文读取文件级别的字段标记 Boolean hasTable2Fields = (Boolean) StepSynchronizationManager.getContext() .getStepExecution() .getExecutionContext() .get("hasTable2Fields"); for (CSVFileRequest item : chunk) { // 第一张表写入逻辑:caller_id非空即可写入 if (item.getCallerId() != null) { Table1Entity t1 = new Table1Entity(); t1.setCallerId(item.getCallerId()); table1Mapper.insert(t1); } // 第二张表写入逻辑:先判断文件是否有对应列,再校验行数据有效性 if (Boolean.TRUE.equals(hasTable2Fields)) { // 按业务规则校验字段有效性,示例为全字段非空才写入 if (item.getOrderId() != null && item.getShipment() != null && item.getAmount() != null && item.getAccNum() != null && item.getDueDate() != null) { Table2Entity t2 = new Table2Entity(); BeanUtils.copyProperties(item, t2); table2Mapper.insert(t2); } } } } }
注意:如果CSV文件没有表头、列顺序固定只是可能缺尾部列,可以去掉表头解析的逻辑,保留两个
setStrict(false)配置即可,缺失的字段会自动映射为null,行级判断逻辑不变。
内容的提问来源于stack exchange,提问作者Satish Kumar
相关产品推荐
相关产品推荐

