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

Spring Batch并行启动Job报ORA-08177错误,求解决方案

并行启动Spring Batch Job引发ORA-08177错误的解决方案

问题现象

并行启动两个Spring Batch Job时触发以下错误:

OriginalSql = INSERT INTO BATCH_JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION)
    VALUES (?, ?, ?, ?)
, Error Msg = ORA-08177: can't serialize access for this transaction

单独或顺序执行Job时正常运行,仅并行启动时出现报错。

技术栈

  • Spring Batch 5.x
  • Spring Boot 3.1.6
  • JDK 17
  • Oracle数据库

相关配置与代码

application.properties

spring.batch.repository.isolationlevelforcreate=ISOLATION_READ_COMMITTED
spring.batch.isolationlevel=READ_COMMITTED
spring.batch.jdbc.table-prefix=BATCH_
spring.batch.job.enabled=false

BatchConfig.java

@Configuration
public class BatchConfig {

    @Bean("simpletaskExecutor")
    public TaskExecutor simpletaskExecutor() {
        SimpleAsyncTaskExecutor asyncTaskExecutor = new SimpleAsyncTaskExecutor("simpletaskExecutor");
        asyncTaskExecutor.setConcurrencyLimit(concurrencyCount);
        return asyncTaskExecutor;
    }


    @Bean("jobLauncherAsync")
    @Scope("prototype")
    public JobLauncher jobLauncherAsync(DataSource dataSource, JobRepository jobRepository) throws Exception {

        TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
        jobLauncher.setJobRepository(jobRepository);
        jobLauncher.setTaskExecutor(simpletaskExecutor());
        jobLauncher.afterPropertiesSet();
        return jobLauncher;
    }
    
    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
    
    @Bean
    public BatchJobExecutionListener batchJobExecutionListener() {
        return new BatchJobExecutionListener();
    } 
    
    @Bean
    public BatchJobStepExecutionListner batchJobStepExecutionListner() {
        return new BatchJobStepExecutionListner();
    } 
}

EmployeeJobConfig.java

@Configuration
@Import(BatchConfig.class)
public class EmployeeJobConfig{
    
        
    @Autowired
    EmployeeStatusWritter employeeStatusWritter;
    
    @Autowired
    EmployeePendingProcessor employeePendingProcessor;
    
    
    
    @Bean("EmployeePendingReader")
    public JpaPagingItemReader<Employee> employeePendingReader(DataSource ds,EntityManagerFactory entityManagerFactory) {
        
        JpaPagingItemReader<Employee> jpaReader = new JpaPagingItemReader<>();
        jpaReader.setEntityManagerFactory(entityManagerFactory);
        jpaReader.setQueryString("SELECT e FROM Employee e");
        jpaReader.setPageSize(5000);
        return jpaReader;

    }
    
    @Bean("EmployeeSPInvokeStep")
    public Step employeeSPInvokeStep(@Qualifier("EmployeePendingReader") ItemReader<Employee> reader,
            @Qualifier("employeeStatusWritter") ItemWriter<Employee> writer,
            @Qualifier("employeePendingProcessor") ItemProcessor<Employee, Employee> processor,JobRepository jobRepository,
            PlatformTransactionManager transactionManager,BatchJobStepExecutionListner batchJobStepExecutionListner,TaskExecutor simpletaskExecutor) {

        return new StepBuilder("EmployeeSPInvokeStep",jobRepository)
                .<Employee, Employee>chunk(50,transactionManager).reader(reader)
                .processor(processor).writer(writer)
                .listener(batchJobStepExecutionListner)
                .taskExecutor(simpletaskExecutor)
                .build();
    }
    
    @Bean("EmployeeSPJob")
    public Job employeeSPJob(@Qualifier("EmployeeSPInvokeStep") Step EmployeeSPInvokeStep,JobRepository jobRepository,BatchJobExecutionListener batchJobExecutionListener) {
        return new JobBuilder("EmployeeSPJob",jobRepository)
                .incrementer(new RunIdIncrementer())
                .listener(batchJobExecutionListener)
                .start(EmployeeSPInvokeStep)
                .build();
    }

}

ManagerConfig.java

@Configuration
@Import(BatchConfig.class)
public class ManagerConfig {
    
        

    @Autowired
    ManagerStatusWritter managerStatusWritter;
    
    
    @Autowired
    ManagerPendingProcessor managerPendingProcessor;
    
    @Bean("ManagerPendingReader")
    public JpaPagingItemReader<Manager> managerPendingReader(DataSource ds,EntityManagerFactory entityManagerFactory) {
        
        JpaPagingItemReader<Manager> jpaReader = new JpaPagingItemReader<>();
        jpaReader.setEntityManagerFactory(entityManagerFactory);
        jpaReader.setQueryString("SELECT m FROM Manager m");
        jpaReader.setPageSize(5000);
        return jpaReader;

    }
    
    @Bean("ManagerSPInvokeStep")
    public Step indvInvoiceConsCtlSPInvokeStep(@Qualifier("ManagerPendingReader") ItemReader<Manager> reader,
            @Qualifier("ManagerStatusWritter") ItemWriter<Manager> writer,
            @Qualifier("managerPendingProcessor") ItemProcessor<Manager, Manager> processor,JobRepository jobRepository,
            PlatformTransactionManager transactionManager,BatchJobStepExecutionListner batchJobStepExecutionListner,TaskExecutor simpletaskExecutor) {

        return new StepBuilder("ManagerSPInvokeStep",jobRepository)
                .<Manager, Manager>chunk(5000,transactionManager).reader(reader)
                .processor(processor).writer(writer)
                .listener(batchJobStepExecutionListner)
                .taskExecutor(simpletaskExecutor)
                .build();
    }
    
    @Bean("ManagerSPJob")
    public Job ManagerSPJob(@Qualifier("ManagerSPInvokeStep") Step indvInvoiceConsCtlSPInvokeStep,JobRepository jobRepository,BatchJobExecutionListener batchJobExecutionListener) {
        return new JobBuilder("ManagerSPJob",jobRepository)
                .incrementer(new RunIdIncrementer())
                .listener(batchJobExecutionListener)
                .start(indvInvoiceConsCtlSPInvokeStep)
                .build();
    }
    
}

BatchJobManager.java

@Service
public class BatchJobManager {

    
    
      @Autowired ApplicationContext context;
     
      @Autowired BatchExecutorService batchExecutorService;
      
      @Autowired
      BatchJobRunner batchJobRunner;
      
        
    
    public void startJob() {
        
         try {
               System.out.println("BatchJobManager called .. "+new Date());
                
               String[] invoiceNames={"EmployeeSPJob","ManagerSPJob"};
               
               List<String> invoiceNamesList = Arrays.asList(invoiceNames);
               launchAsyn(getBatchJobList(invoiceNamesList));
                
                 
         } catch (Exception e) {
             System.out.println("While loading job..");
             e.printStackTrace();
         }
        
    }
       

    public List<BatchJob>  getBatchJobList(List<String> jobNames) throws Exception{
        List<BatchJob> batchJobList=new ArrayList<BatchJob>();
        for(String job:jobNames) {
             BatchJob batchJob= BatchJob.builder().jobName(invoiceJob).build();
             batchJobList.add(batchJob);
        }
        return batchJobList;
    }
      
    
       
    public void launchAsyn( List<BatchJob> batchJobList) throws Exception{
         List<CompletableFuture<BatchJob>> batchJobFutureList = new ArrayList<CompletableFuture<BatchJob>>();
          
          for(BatchJob batchJob:batchJobList) {
          CompletableFuture<BatchJob> jobFuture = batchExecutorService.execute(batchJob, asyncTaskExecutor);
          batchJobFutureList.add(jobFuture);
          }
          
          CompletableFuture<Void> jobFutureResult = CompletableFuture
                    .allOf(batchJobFutureList.toArray(new CompletableFuture[batchJobFutureList.size()]));
          
          
          CompletableFuture<List<CanBatchJob>> allCompletableFuture = jobFutureResult.thenApply(future -> {
                return batchJobFutureList.stream().map(completableFuture -> completableFuture.join())
                        .collect(Collectors.toList());
            });
        
          List<BatchJob> resultFutureList=allCompletableFuture.get();
          
          for(BatchJob batch:resultFutureList) {
              System.out.println("Status "+batch.getIsCompleted());
          }
    }
   
    
}

BatchExecutorService.java

@Service
public class BatchExecutorService {
    
        
    @Autowired 
    BatchJobRunner batchJobRunner;
        
    public CompletableFuture<CanBatchJob> execute(CanBatchJob canBatchJob,TaskExecutor threadPoolTaskExecutor){
        return CompletableFuture.supplyAsync(() -> batchJobRunner.execute(canBatchJob),threadPoolTaskExecutor);
    }
    
    
}

BatchJobRunner.java

@Service
public class BatchJobRunner {

    @Autowired ApplicationContext context;
      
    
     @Autowired 
     @Qualifier("jobLauncherAsync")
     JobLauncher jobLauncherAsync;
    
    
    /*
     * @Autowired JobLauncher jobLauncherAsync;
     */
    
    public BatchJob execute(BatchJob batchJob) {
        try {
            System.out.println(" batchJob"+batchJob.getJobName()+" called ...");
            jobLauncherAsync.run(getJob(batchJob.getJobName()), getJobParameters(batchJob.getJobName()));
            Thread.sleep(15000);
            batchJob.setIsCompleted(true);
            System.out.println(" batchJob"+batchJob.getJobName()+" completed ...");
        }
        catch(Exception e) {
            System.out.println("Exception "+e.getMessage());
            batchJob.setErrorDesc(e.getMessage().toString());
            e.printStackTrace();
        }
        
        return canBatchJob;
    }
    
    public Job getJob(String jobName) {
        
         return  (Job) context.getBean(jobName); 
    }
    
    public JobParameters getJobParameters(String jobName) {
    
    JobParameters  jobParameters = new JobParametersBuilder() .addString("UNIQUE_ID",
              UUID.randomUUID().toString(), true) .addString("JOB_NAME", jobName,
              true) .addDate("EXECUTION_START_DATE", Date.from(Instant.now()),
              true).toJobParameters();
    return jobParameters;
    }
    
}

BatchJob.java

public class BatchJob {

    private String jobName;
    private Boolean isCompleted;
    private String errorDesc;
    
    
}

问题分析

1. 并行启动Spring Batch Job是否可行?

并行启动多个独立的Spring Batch Job是合法且常见的使用场景,适用于处理无共享资源冲突的不同业务数据,能有效提升整体处理效率。

2. ORA-08177错误原因

该错误是Oracle数据库的序列化访问失败异常,触发根源是Spring Batch写入BATCH_JOB_INSTANCE表时的并发冲突:

  • Spring Batch创建Job实例时,会先执行"查询是否存在相同Job Key的实例"操作,再执行插入;
  • 并发场景下,两个事务的查询都返回"不存在",随后同时执行插入操作,因VERSION字段初始值冲突,触发序列化校验失败。

解决方案

方案1:调整Job Repository的隔离级别

将创建Job实例时的事务隔离级别改为ISOLATION_SERIALIZABLE,确保同一时间只有一个事务能完成"查询-插入"的原子操作,避免并发冲突:

spring.batch.repository.isolationlevelforcreate=ISOLATION_SERIALIZABLE

方案2:控制Job启动的并发度

替换SimpleAsyncTaskExecutor为ThreadPoolTaskExecutor,限制同时启动的Job数量,避免瞬间并发写入冲突:

// 修改BatchConfig中的simpletaskExecutor Bean
@Bean("simpletaskExecutor")
public TaskExecutor simpletaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(2);
    executor.setThreadNamePrefix("batch-task-");
    executor.initialize();
    return executor;
}

方案3:优化Job启动逻辑

避免直接用CompletableFuture无限制并行启动,改用Spring Batch的JobOperator配合消息队列实现异步启动,更好地控制并发和冲突。

结论

并行启动Spring Batch Job是完全可行的做法,你的问题核心是并发写入Job Repository时的事务冲突,通过调整隔离级别或控制并发度即可解决。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 04:45:54