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

Spring Batch线程安全实现咨询:同表并发读写报错问题

Spring Batch 多Job并发操作同表的线程安全解决方案

核心问题分析

你遇到的报错本质是两个独立Job并发操作同一张数据库表导致的资源冲突,StepScope仅用于控制Step级别的bean作用域,无法解决跨Job的并发执行冲突。以下是针对性的解决方案:


方案1:强制控制Job执行顺序(最直接有效)

既然Job2依赖Job1的插入结果,完全不需要让它们并发执行。可以通过以下两种方式确保Job2在Job1执行完成后再启动:

方式1:同步调用JobLauncher

在触发Job的入口代码中,同步执行Job1,完成后再启动Job2:

// 注入JobLauncher、job1、job2
@Autowired
private JobLauncher jobLauncher;
@Autowired
@Qualifier("xxxProcess")
private Job job1;
@Autowired
private Job job2;

public void triggerJobs() throws JobExecutionException {
    // 同步执行Job1,等待其完成
    JobExecution job1Execution = jobLauncher.run(job1, new JobParametersBuilder()
            .addLong("job1Time", System.currentTimeMillis())
            .toJobParameters());
    
    // 仅当Job1成功完成时,启动Job2
    if (BatchStatus.COMPLETED.equals(job1Execution.getStatus())) {
        jobLauncher.run(job2, new JobParametersBuilder()
                .addLong("job2Time", System.currentTimeMillis())
                .toJobParameters());
    }
}

方式2:通过Job监听器触发Job2

修改JobCompletionListener,在Job1执行完成后自动启动Job2:

public class JobCompletionListener implements JobExecutionListener {
    private final JobLauncher jobLauncher;
    private final Job job2;

    // 构造注入依赖
    public JobCompletionListener(JobLauncher jobLauncher, Job job2) {
        this.jobLauncher = jobLauncher;
        this.job2 = job2;
    }

    @Override
    public void afterJob(JobExecution jobExecution) {
        if (BatchStatus.COMPLETED.equals(jobExecution.getStatus())) {
            try {
                JobParameters jobParams = new JobParametersBuilder()
                        .addLong("triggerTime", System.currentTimeMillis())
                        .toJobParameters();
                jobLauncher.run(job2, jobParams);
            } catch (JobExecutionException e) {
                // 处理Job2启动异常
                e.printStackTrace();
            }
        }
    }
}

然后在Job1的配置中注入该监听器的依赖:

@Bean(name = "xxx")
public Job xxxProcess(JobBuilderFactory jobBuilderFactory, Step xxxReadProcessWrite, 
                      JobLauncher jobLauncher, Job job2) {
    return jobBuilderFactory
            .get("xxxProcess")
            .flow(xxxReadProcessWrite)
            .end()
            .listener(new JobCompletionListener(jobLauncher, job2))
            .build();
}

方案2:数据库层面加锁(必须并发时使用)

如果业务上必须让两个Job并发执行,需要从数据库层面控制资源访问:

方式1:行级锁(悲观锁)

在Job2的更新操作中,使用SELECT ... FOR UPDATE锁定要更新的行,确保Job1的插入完成后再执行更新:

-- Job2更新前先锁定目标行
SELECT * FROM employee WHERE id = ? FOR UPDATE;
-- 执行更新操作
UPDATE employee SET ... WHERE id = ?;

方式2:乐观锁(无锁机制)

在employee表中添加version字段,每次更新时校验版本号,避免冲突:

-- 创建表时添加version字段
ALTER TABLE employee ADD COLUMN version INT DEFAULT 1;

-- Job2更新语句
UPDATE employee SET ..., version = version + 1 WHERE id = ? AND version = ?;

在代码中捕获乐观锁异常(如OptimisticLockingFailureException),可选择重试或抛出错误提示。


方案3:确保ItemWriter线程安全

检查你的xxxItemWriter是否为线程安全实现:

  • 如果Writer是单例且包含状态变量,会导致并发问题,可将其标记为@Scope("step"),确保每个Step实例拥有独立的Writer实例;
  • 或者用SynchronizedItemWriter包装原Writer,强制同步执行:
@Bean
public ItemWriter<xxxDto> xxxItemWriter() {
    ItemWriter<xxxDto> delegateWriter = items -> {
        // 原有的插入/更新逻辑
    };
    return new SynchronizedItemWriter<>(delegateWriter);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 11:47:25