Spring Batch单Reader多Writer:拆分文件数据至对应数据库表
刚好之前做过类似的需求,用Spring Batch的ClassifierCompositeItemWriter就能完美解决这种单Reader读取混合类型数据,多Writer分别写入不同表的场景,我给你一步步拆解实现过程:
核心思路
你的文件是混合结构:首行是班级元数据(HeaderDataDAO),其余行是学生数据(StudentDataDAO)。我们的实现逻辑是:
- 用一个通用的Reader读取所有行,先把每行解析成字符串数组;
- 通过ItemProcessor判断当前行是班级数据还是学生数据,转换成对应的DAO对象;
- 用
ClassifierCompositeItemWriter作为统一Writer,根据数据类型自动路由到对应的班级表Writer或学生表Writer。
具体实现步骤
1. 配置通用FlatFileItemReader读取所有行
我们用FlatFileItemReader读取星号分隔的文件,先把每行解析成String数组,这样后续可以灵活转换为不同的DAO类型:
@Bean public FlatFileItemReader<String[]> classStudentReader() { FlatFileItemReader<String[]> reader = new FlatFileItemReader<>(); // 替换成你的文件路径,比如ClassPathResource或FileSystemResource reader.setResource(new ClassPathResource("student-class-data.txt")); // 配置星号分隔符,允许行尾有多余的分隔符(避免报错) DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(); tokenizer.setDelimiter("*"); tokenizer.setStrict(false); // 把FieldSet转换为String数组 DefaultLineMapper<String[]> lineMapper = new DefaultLineMapper<>(); lineMapper.setLineTokenizer(tokenizer); lineMapper.setFieldSetMapper(fieldSet -> fieldSet.getValues()); reader.setLineMapper(lineMapper); return reader; }
2. 实现ItemProcessor区分并转换数据类型
这个Processor负责接收String数组,判断数据类型并转换成对应的DAO对象:
@Component public class ClassStudentProcessor implements ItemProcessor<String[], Object> { @Override public Object process(String[] rowData) throws Exception { // 通过首元素判断:班级行以"Class-"开头,学生行首元素是数字ID if (rowData[0].startsWith("Class-")) { HeaderDataDAO classHeader = new HeaderDataDAO(); classHeader.setClassName(rowData[0]); classHeader.setNumberOfStudent(rowData[1]); classHeader.setBatch(rowData[2]); return classHeader; } else { StudentDataDAO student = new StudentDataDAO(); student.setId(Long.parseLong(rowData[0])); student.setFirstName(rowData[1]); student.setLastName(rowData[2]); // 调用StudentDataDAO自带的日期解析方法,自动处理空字符串 student.setBirthDate(rowData[3]); return student; } } }
3. 配置两个独立的JdbcWriter分别写入数据库
分别创建写入班级表和学生表的JdbcBatchItemWriter:
// 班级表Writer @Bean public JdbcBatchItemWriter<HeaderDataDAO> classWriter(DataSource dataSource) { JdbcBatchItemWriter<HeaderDataDAO> writer = new JdbcBatchItemWriter<>(); writer.setDataSource(dataSource); // 替换成你的班级表插入SQL writer.setSql("INSERT INTO class_info (class_name, student_count, batch) VALUES (:className, :numberOfStudent, :batch)"); // 自动映射DAO属性到SQL参数 writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()); return writer; } // 学生表Writer @Bean public JdbcBatchItemWriter<StudentDataDAO> studentWriter(DataSource dataSource) { JdbcBatchItemWriter<StudentDataDAO> writer = new JdbcBatchItemWriter<>(); writer.setDataSource(dataSource); // 替换成你的学生表插入SQL writer.setSql("INSERT INTO student (id, first_name, last_name, birth_date) VALUES (:id, :firstName, :lastName, :birthDate)"); writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()); return writer; }
4. 配置ClassifierCompositeItemWriter实现数据路由
这是核心组件,它会根据数据类型自动选择对应的Writer:
@Bean public ClassifierCompositeItemWriter<Object> compositeWriter(JdbcBatchItemWriter<HeaderDataDAO> classWriter, JdbcBatchItemWriter<StudentDataDAO> studentWriter) { ClassifierCompositeItemWriter<Object> compositeWriter = new ClassifierCompositeItemWriter<>(); // 定义分类逻辑:根据对象类型路由到对应Writer compositeWriter.setClassifier((Classifier<Object, ItemWriter<? super Object>>) item -> { if (item instanceof HeaderDataDAO) { return classWriter; } else if (item instanceof StudentDataDAO) { return studentWriter; } throw new IllegalArgumentException("未知的数据类型:" + item.getClass().getName()); }); return compositeWriter; }
5. 组装Step和Job
最后把Reader、Processor、CompositeWriter组装成Step,再构建Job:
@Bean public Step classStudentStep(ItemReader<String[]> classStudentReader, ClassStudentProcessor processor, ClassifierCompositeItemWriter<Object> compositeWriter, StepBuilderFactory stepBuilderFactory) { return stepBuilderFactory.get("classStudentProcessingStep") .<String[], Object>chunk(10) // 按10条数据提交一次事务,可根据需求调整 .reader(classStudentReader) .processor(processor) .writer(compositeWriter) .build(); } @Bean public Job classStudentJob(JobBuilderFactory jobBuilderFactory, Step classStudentStep) { return jobBuilderFactory.get("classStudentImportJob") .incrementer(new RunIdIncrementer()) // 每次运行生成唯一ID .flow(classStudentStep) .end() .build(); }
注意事项
- 处理多余分隔符:设置
DelimitedLineTokenizer.setStrict(false),避免行尾的多余星号导致解析报错; - 日期异常处理:如果文件中有无效的日期格式,可以在
StudentDataDAO.setBirthDate里添加异常捕获,或者在Processor里提前校验; - 多文件场景:如果要处理多个类似结构的文件,只需把
FlatFileItemReader换成MultiResourceItemReader,批量读取多个文件即可; - 事务管理:Spring Batch默认会按chunk大小提交事务,确保数据一致性。
内容的提问来源于stack exchange,提问作者Vishal
相关产品推荐
相关产品推荐

