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
相关产品推荐
相关产品推荐

