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

Jakarta EE Batch任务失败时如何回滚数据库变更避免无效Delivery实体留存

解决方案

核心问题根因

  • 你当前在doOpen()阶段就独立提交事务持久化Delivery实体,此时后续文件写入、批处理逻辑还未执行,一旦后续环节失败,提前提交的Delivery数据自然无法回滚
  • 数据库序列的设计天生不支持回滚:所有主流关系型数据库(Oracle/PostgreSQL/MySQL等)的序列为了保证高并发下无重复主键,取值后即使事务回滚也不会回收序号,完全不消耗序列值的需求无法通过原生序列实现

标准非侵入式修复方案

方案1:调整持久化时机+JTA事务绑定(推荐,无业务侵入)

  1. 移除persistNewLieferung()方法内部的独立事务提交逻辑,不要在doOpen()阶段执行entityManager.persist()操作,仅在内存中初始化newDelivery实体的非主键字段
  2. 新增JobListener,在作业执行成功的回调中再执行Delivery实体的持久化并提交事务:
@Named
public class DeliveryJobListener implements JobListener {
    @PersistenceContext(unitName = MyPersistenceUnit.NAME)
    private EntityManager entityManager;

    @Inject
    private MyItemWriter myItemWriter;

    @Override
    public void beforeJob(JobExecution jobExecution) {
        // 不需要提前操作
    }

    @Override
    public void afterJob(JobExecution jobExecution) {
        // 只有作业执行成功才持久化Delivery
        if (jobExecution.getExitStatus().equals(ExitStatus.COMPLETED)) {
            Delivery newDelivery = myItemWriter.getNewDelivery();
            // 补全所有字段后持久化
            entityManager.persist(newDelivery);
            // 若此处是容器管理事务则不需要手动提交,否则手动提交事务
        } else {
            // 作业失败,删除已生成的不完整文件
            File incompleteFile = new File(myItemWriter.getDeliveryFilePath());
            if (incompleteFile.exists()) {
                incompleteFile.delete();
            }
        }
    }
}
  1. 调整close()方法逻辑,增加执行成功状态判断,只有作业成功时才写入特殊记录:
@Override
public void close() throws IOException {
    // 从批处理上下文中获取当前作业状态
    JobContext jobContext = BatchRuntime.getJobOperator().getJobContext(jobExecutionId);
    if (ExitStatus.COMPLETED.equals(jobContext.getExitStatus())) {
        Delivery previousDelivery = getPreviousDelivery();
        writeToFile(createRecord(newDelivery.getDeliveryNo().toString(),
                                 writtenRecords,
                                 previousDelivery.getDeliveryDate(),
                                 previousDelivery.getDeliveryNo().toString()));
    }
    fileWriter.close();
}

方案2:状态标记法(适合要求配送号连续的场景)

如果必须保证配送编号完全连续无缺口,放弃数据库序列主键,改为自行维护序号表:

  1. 新增序号维护表DELIVERY_SEQ,仅存一个字段current_max_no,记录当前已成功使用的最大配送编号
  2. Delivery实体去掉@GeneratedValue注解,主键自行赋值
  3. 作业执行成功后,先对序号表加行锁读取最新编号+1作为本次配送号,再持久化Delivery实体、更新序号表,两个操作在同一个事务中提交,保证原子性

方案3:失败反向清理(适合存量系统小改动场景)

如果不想调整现有持久化逻辑,新增批处理异常监听器,作业执行失败时:

  • 手动删除doOpen()阶段插入的未完成Delivery记录
  • 删除不完整的配送文件
  • 过滤查询时仅查关联文件存在、且写入完成的配送记录即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 02:54:03