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

Spring Batch单Reader多Writer:拆分文件数据至对应数据库表

刚好之前做过类似的需求,用Spring Batch的ClassifierCompositeItemWriter就能完美解决这种单Reader读取混合类型数据,多Writer分别写入不同表的场景,我给你一步步拆解实现过程:

核心思路

你的文件是混合结构:首行是班级元数据(HeaderDataDAO),其余行是学生数据(StudentDataDAO)。我们的实现逻辑是:

  1. 用一个通用的Reader读取所有行,先把每行解析成字符串数组;
  2. 通过ItemProcessor判断当前行是班级数据还是学生数据,转换成对应的DAO对象;
  3. 用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:20:57