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

如何在Spring Batch中通过多步骤处理同一对象?

解决同一对象多步骤处理的Spring Batch方案

看起来你想要的是让从数据库读取的每个合同对象,依次走完调研、利息计算、滞纳金计算、FDI计算这些流程,而不是每个步骤都单独查询一次数据库重新处理对吧?下面给你两种适配不同场景的解决方案:

方案一:用CompositeItemProcessor串联所有处理逻辑(推荐,高效简洁)

如果你的各个处理步骤不需要独立事务,也不需要在每个步骤后持久化中间状态,这种方式最适合——只需要读取一次数据,就让每个对象依次经过所有处理逻辑。

步骤1:定义单个处理逻辑的Processor

先把每个步骤的处理逻辑拆成独立的ItemProcessor:

@Bean
public ItemProcessor<Contract, Contract> researchContractProcessor() {
    return contract -> {
        // 在这里实现调研合同的业务逻辑
        contract.setResearchCompleted(true);
        // 必须返回对象,否则会被过滤掉
        return contract;
    };
}

@Bean
public ItemProcessor<Contract, Contract> calculateInterestProcessor() {
    return contract -> {
        // 实现利息计算逻辑
        contract.setInterest(contract.getPrincipalAmount() * 0.06);
        return contract;
    };
}

@Bean
public ItemProcessor<Contract, Contract> calculateArrearsProcessor() {
    return contract -> {
        // 实现滞纳金计算逻辑
        contract.setArrears(contract.getOverdueDays() * 5.0);
        return contract;
    };
}

@Bean
public ItemProcessor<Contract, Contract> calculateFDIProcessor() {
    return contract -> {
        // 实现FDI计算逻辑
        contract.setFdiAmount(contract.getTotalAmount() * 0.02);
        return contract;
    };
}

步骤2:组合成CompositeItemProcessor

把上面的处理器按执行顺序组合成一个复合处理器:

@Bean
public CompositeItemProcessor<Contract, Contract> compositeContractProcessor() {
    CompositeItemProcessor<Contract, Contract> compositeProcessor = new CompositeItemProcessor<>();
    // 注意顺序:必须按业务依赖的先后排列
    compositeProcessor.setDelegates(Arrays.asList(
        researchContractProcessor(),
        calculateInterestProcessor(),
        calculateArrearsProcessor(),
        calculateFDIProcessor()
        // 后续如果有其他处理逻辑,继续添加在这里
    ));
    return compositeProcessor;
}

步骤3:定义单个Step完成所有处理

用你的JdbcPagingItemReader、复合处理器和最终的Writer构建一个Step:

@Bean
public Step contractProcessingStep() {
    return stepBuilderFactory.get("contractProcessingStep")
        .<Contract, Contract>chunk(10) // 按10条为一批处理,可根据性能调整
        .reader(yourJdbcPagingItemReader()) // 你原来的JdbcPagingItemReader
        .processor(compositeContractProcessor())
        .writer(contractFinalWriter()) // 最终写入正式表的Writer
        .build();
}

步骤4:简化Job配置

现在你的Job只需要启动这一个Step即可:

@Bean
public Job jobValorizacao() {
    return jobBuilderFactory.get("jobValorizacao")
        .incrementer(new RunIdIncrementer())
        .start(contractProcessingStep())
        .build();
}

方案二:多Step+临时存储(适合需要独立事务/中间持久化的场景)

如果每个处理步骤需要单独的事务控制,或者必须在每个步骤后把中间状态持久化到数据库,你可以用临时存储(比如临时表)来传递数据:

核心思路

  1. 第一个Step读取原始数据,执行调研逻辑后写入临时表
  2. 后续每个Step从临时表读取数据,执行对应处理逻辑后更新临时表
  3. 最后一个Step把临时表的数据写入正式表

示例代码片段

// 第一个Step:调研合同并写入临时表
@Bean
public Step researchContractStep() {
    return stepBuilderFactory.get("researchContractStep")
        .<Contract, Contract>chunk(10)
        .reader(yourJdbcPagingItemReader())
        .processor(researchContractProcessor())
        .writer(tempContractWriter()) // 写入临时表的Writer
        .build();
}

// 第二个Step:从临时表读取,计算利息并更新临时表
@Bean
public JdbcPagingItemReader<Contract> tempContractReader() {
    // 配置读取临时表的JdbcPagingItemReader
    JdbcPagingItemReader<Contract> reader = new JdbcPagingItemReader<>();
    reader.setDataSource(dataSource);
    reader.setRowMapper(new BeanPropertyRowMapper<>(Contract.class));
    // 设置临时表的查询语句和分页参数
    // ... 省略具体配置
    return reader;
}

@Bean
public Step calculateInterestStep() {
    return stepBuilderFactory.get("calculateInterestStep")
        .<Contract, Contract>chunk(10)
        .reader(tempContractReader())
        .processor(calculateInterestProcessor())
        .writer(tempContractUpdateWriter()) // 更新临时表的Writer
        .build();
}

// 后续步骤以此类推,最后一个Step写入正式表
@Bean
public Step finalWriteStep() {
    return stepBuilderFactory.get("finalWriteStep")
        .<Contract, Contract>chunk(10)
        .reader(tempContractReader())
        .writer(contractFinalWriter())
        .build();
}

// 最终的Job配置
@Bean
public Job jobValorizacao() {
    return jobBuilderFactory.get("jobValorizacao")
        .incrementer(new RunIdIncrementer())
        .start(researchContractStep())
        .next(calculateInterestStep())
        .next(calculateArrearsStep())
        .next(calculateFDIStep())
        .next(finalWriteStep())
        .build();
}

注意事项

  • 方案一注意点:如果任何一个Processor返回null,这个Item会被过滤掉,不会进入后续处理。如果不需要过滤,确保每个Processor都返回非null的对象。
  • 方案二注意点:临时表需要注意数据隔离(比如用Job的RunId作为区分标识),避免不同Job实例的数据互相干扰;处理完成后记得清理临时表数据。
  • 性能考量:方案一减少了多次数据库查询的开销,适合大数据量场景;方案二因为多了多次读写临时表的操作,性能稍逊,但胜在事务和状态管理更灵活。

内容的提问来源于stack exchange,提问作者Gustavo Barbosa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:12:25