Spring Batch多文件写入疑问:如何基于CommonDTO生成3个.dat文件?
解决方案:Spring Batch 多文件拆分写入设计
针对你的需求,核心问题在于直接用FlatFileItemWriter<List<Child1DTO>>不符合Spring Batch的Writer设计规范——FlatFileItemWriter的泛型参数是单个待写入的Item类型(比如Child1DTO),而非集合。如果强行传入List,它会把整个集合当作单个Item处理,默认的行聚合器无法解析集合结构,导致文件生成异常。
下面给出两种可行的实现方案,你可以根据场景选择:
方案一:自定义ItemWriter(推荐,灵活可控)
直接编写一个ItemWriter<CommonDTO>,内部封装三个独立的FlatFileItemWriter,分别处理Parent和两个Child数据集的写入逻辑,同时自动维护Parent与Child的ID关联。
1. 定义基础FlatFileItemWriter
先为每个数据类型创建对应的文件Writer,负责单行数据的格式化写入:
// ParentDTO 对应的Writer @Bean public FlatFileItemWriter<ParentDTO> parentFileWriter() { return new FlatFileItemWriterBuilder<ParentDTO>() .name("parent-dat-writer") .resource(new FileSystemResource("parent.dat")) .lineAggregator(new DelimitedLineAggregator<ParentDTO>() {{ setDelimiter("|"); // 按业务需求设置分隔符 setFieldExtractor(new BeanWrapperFieldExtractor<ParentDTO>() {{ setNames(new String[]{"id", "name", "createTime"}); // 替换为ParentDTO的实际字段 }}); }}) .append(true) // 批量处理时追加写入,避免覆盖 .build(); } // Child1DTO 对应的Writer @Bean public FlatFileItemWriter<Child1DTO> child1FileWriter() { return new FlatFileItemWriterBuilder<Child1DTO>() .name("child1-dat-writer") .resource(new FileSystemResource("child1.dat")) .lineAggregator(new DelimitedLineAggregator<Child1DTO>() {{ setDelimiter("|"); setFieldExtractor(new BeanWrapperFieldExtractor<Child1DTO>() {{ setNames(new String[]{"parentId", "childId", "childData"}); // 必须包含parentId关联字段 }}); }}) .append(true) .build(); } // Child2DTO 对应的Writer(与Child1逻辑一致,替换字段即可) @Bean public FlatFileItemWriter<Child2DTO> child2FileWriter() { // ... 省略重复代码,参考child1FileWriter实现 }
2. 编写CommonDTO处理的自定义Writer
注入三个基础Writer,遍历每个CommonDTO,拆分数据并调用对应Writer写入:
@Component public class CommonDTOItemWriter implements ItemWriter<CommonDTO> { private final FlatFileItemWriter<ParentDTO> parentWriter; private final FlatFileItemWriter<Child1DTO> child1Writer; private final FlatFileItemWriter<Child2DTO> child2Writer; // 构造注入三个基础Writer public CommonDTOItemWriter(FlatFileItemWriter<ParentDTO> parentWriter, FlatFileItemWriter<Child1DTO> child1Writer, FlatFileItemWriter<Child2DTO> child2Writer) { this.parentWriter = parentWriter; this.child1Writer = child1Writer; this.child2Writer = child2Writer; } @Override public void write(List<? extends CommonDTO> items) throws Exception { for (CommonDTO dto : items) { ParentDTO parent = dto.getParentDTO(); // 写入Parent数据 parentWriter.write(Collections.singletonList(parent)); // 写入Child1数据,自动设置parentId关联 List<Child1DTO> child1List = dto.getChild1DTOList(); if (child1List != null && !child1List.isEmpty()) { child1List.forEach(child -> child.setParentId(parent.getId())); child1Writer.write(child1List); } // 写入Child2数据,同理 List<Child2DTO> child2List = dto.getChild2DTOList(); if (child2List != null && !child2List.isEmpty()) { child2List.forEach(child -> child.setParentId(parent.getId())); child2Writer.write(child2List); } } } }
3. 配置Step
将自定义Writer绑定到Step中即可:
@Bean public Step dataProcessStep(ItemReader<Parent> parentReader, ItemProcessor<Parent, CommonDTO> commonDTOProcessor, CommonDTOItemWriter commonDTOWriter, JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("data-process-step", jobRepository) .<Parent, CommonDTO>chunk(100, transactionManager) // 按业务设置Chunk大小 .reader(parentReader) .processor(commonDTOProcessor) .writer(commonDTOWriter) .build(); }
方案二:使用CompositeItemWriter+数据提取Processor
如果希望复用Spring Batch的组件组合能力,可以用CompositeItemWriter配合三个ItemProcessor,每个Processor从CommonDTO中提取对应的数据,再交给各自的Writer处理。
核心要点:
- 每个Processor负责从
CommonDTO中提取单个类型的数据(比如ParentDTO或单个Child1DTO) - 注意Child数据需要先设置
parentId再传递给Writer CompositeItemWriter会将同一个CommonDTO传给所有子Writer,通过Processor过滤出对应数据
示例代码(仅展示关键部分):
// Parent数据提取Processor public class ParentExtractProcessor implements ItemProcessor<CommonDTO, ParentDTO> { @Override public ParentDTO process(CommonDTO item) { return item.getParentDTO(); } } // Child1数据提取Processor(返回Iterable类型,适配Writer的批量写入) public class Child1ExtractProcessor implements ItemProcessor<CommonDTO, Iterable<Child1DTO>> { @Override public Iterable<Child1DTO> process(CommonDTO item) { item.getChild1DTOList().forEach(child -> child.setParentId(item.getParentDTO().getId())); return item.getChild1DTOList(); } } // 自定义委托类,绑定Processor和Writer public class ItemWriterDelegate<I, O> implements ItemWriter<I> { private final FlatFileItemWriter<O> delegate; private final ItemProcessor<I, O> processor; public ItemWriterDelegate(FlatFileItemWriter<O> delegate, ItemProcessor<I, O> processor) { this.delegate = delegate; this.processor = processor; } @Override public void write(List<? extends I> items) throws Exception { List<O> processedItems = new ArrayList<>(); for (I item : items) { O processed = processor.process(item); if (processed != null) { if (processed instanceof Iterable) { ((Iterable<O>) processed).forEach(processedItems::add); } else { processedItems.add(processed); } } } delegate.write(processedItems); } } // 配置CompositeItemWriter @Bean public CompositeItemWriter<CommonDTO> compositeWriter() { List<ItemWriter<? super CommonDTO>> writers = new ArrayList<>(); writers.add(new ItemWriterDelegate<>(parentFileWriter(), new ParentExtractProcessor())); writers.add(new ItemWriterDelegate<>(child1FileWriter(), new Child1ExtractProcessor())); writers.add(new ItemWriterDelegate<>(child2FileWriter(), new Child2ExtractProcessor())); CompositeItemWriter<CommonDTO> compositeWriter = new CompositeItemWriter<>(); compositeWriter.setDelegates(writers); return compositeWriter; }
关键注意事项
- 事务一致性:如果需要保证三个文件的写入原子性(要么都成功,要么都失败),需确保Writer的事务配置正确,Spring Batch的Chunk事务会覆盖整个Writer的写入操作。
- 文件覆盖/追加:根据业务场景设置
FlatFileItemWriter的append属性,避免批量处理时覆盖已有数据。 - 字段映射正确性:确保
BeanWrapperFieldExtractor的字段名称与DTO类的属性完全一致,否则会导致数据缺失。
内容的提问来源于stack exchange,提问作者angel princess
相关产品推荐
相关产品推荐

