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

