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

Spring Batch处理含数组的复杂DTO:如何拆分列表字段

Spring Batch 解析含数组字段的DTO方案

问题说明

你有如下DTO定义:

public class Dto {
       
        private String somethingObject;
        private List<String> arrayOfObjects;
}

需要通过Spring Batch做批处理,输入文件每行格式为:

object | arrayItem1 arrayItem2 arrayItem3

使用|作为分隔符的LineTokenizer只能把arrayOfObjects识别成一个包含长字符串的列表,无法拆分成3个独立元素,需要解决这个解析问题。

解决办法

方法一:自定义FieldSetMapper(推荐,灵活适配)

在字段映射阶段手动拆分数组字段,是适用性最广的方案:

  1. 先配置LineTokenizer按|拆分每行,得到两个基础字段:
@Bean
public LineTokenizer lineTokenizer() {
    DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
    tokenizer.setDelimiter("|");
    tokenizer.setNames("somethingObject", "arrayField");
    tokenizer.setStrict(false); // 防止字段数量不匹配时报错
    return tokenizer;
}
  1. 实现自定义FieldSetMapper,将第二个字段按空格拆分成列表:
public class DtoFieldSetMapper implements FieldSetMapper<Dto> {
    @Override
    public Dto mapFieldSet(FieldSet fieldSet) throws BindException {
        Dto dto = new Dto();
        // 去除首尾空格,避免空字符干扰
        dto.setSomethingObject(fieldSet.readString("somethingObject").trim());
        // 按任意数量空格拆分,自动过滤空字符串
        String[] arrayItems = fieldSet.readString("arrayField").trim().split("\\s+");
        dto.setArrayOfObjects(Arrays.asList(arrayItems));
        return dto;
    }
}
  1. 将自定义Mapper配置到FlatFileItemReader中:
@Bean
public FlatFileItemReader<Dto> itemReader() {
    return new FlatFileItemReaderBuilder<Dto>()
            .resource(new ClassPathResource("input.txt"))
            .lineTokenizer(lineTokenizer())
            .fieldSetMapper(new DtoFieldSetMapper())
            .build();
}

方法二:自定义LineTokenizer(适合固定长度数组)

如果想在拆分每行的阶段就完成数组项的拆分,可以扩展DelimitedLineTokenizer:

public class CustomLineTokenizer extends DelimitedLineTokenizer {
    @Override
    protected List<String> doTokenize(String line) {
        List<String> tokens = super.doTokenize(line);
        // 处理第二个字段,按空格拆分后替换原字段
        if (tokens.size() >= 2) {
            String arrayStr = tokens.get(1).trim();
            List<String> arrayTokens = Arrays.asList(arrayStr.split("\\s+"));
            tokens.remove(1);
            tokens.addAll(1, arrayTokens);
        }
        return tokens;
    }
}

接着配置字段映射,把拆分后的每个数组项绑定到arrayOfObjects的对应位置:

@Bean
public LineTokenizer customLineTokenizer() {
    CustomLineTokenizer tokenizer = new CustomLineTokenizer();
    tokenizer.setDelimiter("|");
    tokenizer.setNames("somethingObject", "arrayItem1", "arrayItem2", "arrayItem3");
    return tokenizer;
}

@Bean
public FieldSetMapper<Dto> fieldSetMapper() {
    BeanWrapperFieldSetMapper<Dto> mapper = new BeanWrapperFieldSetMapper<>();
    mapper.setTargetType(Dto.class);
    mapper.setFieldMap(Map.of(
            "arrayItem1", "arrayOfObjects[0]",
            "arrayItem2", "arrayOfObjects[1]",
            "arrayItem3", "arrayOfObjects[2]"
    ));
    return mapper;
}

这种方法仅适合数组元素数量固定的场景,元素数量不固定时优先选方法一。

方法三:调整输入文件格式(最简单,但需修改文件)

如果允许调整输入文件格式,把数组项用逗号(或其他无冲突分隔符)分开:

object | arrayItem1,arrayItem2,arrayItem3

然后编写字符串转List的转换器,注册到映射器中:

public class StringToListConverter implements Converter<String, List<String>> {
    @Override
    public List<String> convert(String source) {
        if (source == null || source.isBlank()) {
            return Collections.emptyList();
        }
        return Arrays.asList(source.trim().split(","));
    }
}

配置映射器时添加该转换器:

@Bean
public FieldSetMapper<Dto> fieldSetMapper() {
    BeanWrapperFieldSetMapper<Dto> mapper = new BeanWrapperFieldSetMapper<>();
    mapper.setTargetType(Dto.class);
    DefaultConversionService conversionService = new DefaultConversionService();
    conversionService.addConverter(new StringToListConverter());
    mapper.setConversionService(conversionService);
    return mapper;
}

这种方法最省事,但前提是你能修改输入文件的格式。

内容的提问来源于stack exchange,提问作者Bence Ágocsi Kiss

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 19:54:25