Spring Batch 5多任务并行执行遇ORA-02289序列不存在错误
问题描述
配置了四个定时任务在同一时间执行Spring Batch作业,运行时出现如下错误:
Caused by: java.sql.SQLSyntaxErrorException: ORA-02289: sequence does not exist at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:629) at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:563) at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:1150) at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:770) at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:298) at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:497) at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:151) at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:936) at oracle.jdbc.driver.OracleStatement.prepareDefineBufferAndExecute(OracleStatement.java:1171) at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1100) at oracle.jdbc.driver.OracleStatement.executeSQLSelect(OracleStatement.java:1425) at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1308) at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3745) at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3854) at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1097) at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57) ... 89 common frames omitted Caused by: oracle.jdbc.OracleDatabaseException: ORA-02289: sequence does not exist at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:636) ... 106 common frames omitted
相关代码实现:
1. 定时调度类
@Slf4j @Configuration @RequiredArgsConstructor public class TransactionScheduler { @Scheduled(cron = "0 */1 * * * ?") public void job1(){ try { JobMaker jobMaker = jobFactory.getInstance("TRANSACTION-CBL"); JobExecution execution = jobLauncher.run(jobMaker.create(), new JobParameters()); } catch (JobExecutionAlreadyRunningException | JobRestartException | JobInstanceAlreadyCompleteException | JobParametersInvalidException e) { log.info("{}", e.getMessage()); } } @Scheduled(cron = "0 */1 * * * ?") public void job2(){ try { JobMaker jobMaker = jobFactory.getInstance("TRANSACTION-CBL"); JobExecution execution = jobLauncher.run(jobMaker.create(), new JobParameters()); } catch (JobExecutionAlreadyRunningException | JobRestartException | JobInstanceAlreadyCompleteException | JobParametersInvalidException e) { log.info("{}", e.getMessage()); } } @Scheduled(cron = "0 */1 * * * ?") public void JOB3(){ try { JobMaker jobMaker = jobFactory.getInstance("EFT"); JobExecution execution = jobLauncher.run(jobMaker.create(), new JobParameters()); } catch (JobExecutionAlreadyRunningException | JobRestartException | JobInstanceAlreadyCompleteException | JobParametersInvalidException e) { log.info("{}", e.getMessage()); } } @Scheduled(cron = "0 */1 * * * ?") public void job5(){ try { JobMaker jobMaker = jobFactory.getInstance("CBL"); JobExecution execution = jobLauncher.run(jobMaker.create(), new JobParameters()); } catch (JobExecutionAlreadyRunningException | JobRestartException | JobInstanceAlreadyCompleteException | JobParametersInvalidException e) { //log.info("{}", e.getMessage()); } } }
2. JobFactory类
public class JobFactory { private JobEFT jobEFT; private JobCBL jobCBL; private JobTransactionCBL jobTransactionCBL; private JobTransactionEFTN jobTransactionEFTN; public JobMaker getInstance(String type) { log.info(Constants.batchTag + "{} getInstance {} type: "+ type); return switch (type) { case "EFT" -> jobEFT; case "CBL" -> jobCBL; case "TRANSACTION-EFTN" -> jobTransactionEFTN; case "TRANSACTION-CBL" -> jobTransactionCBL; default -> throw new IllegalStateException("Unexpected value: " + type); }; } }
3. 作业步骤类
@Component @RequiredArgsConstructor @Slf4j public class JobEFT implements JobMaker { private final TransactionSPcall transactionSPcall; public Job create(){ String id = UUID.randomUUID().toString(); return new JobBuilder("EFT-"+id, jobRepository) .incrementer(new RunIdIncrementer()) .flow(eftStep()) .end() .build(); } private Step eftStep(){ String id = UUID.randomUUID().toString(); id = "eftStep"+id; return new StepBuilder(id, jobRepository) .<TransactionProjection, List<DDSITransactionDetails>> chunk(10, transactionManager) .reader(new EFTReader(repository, repository, transactionSPcall)) .processor(batchProcessor) .writer(batchWriter) .build(); } } @Component @RequiredArgsConstructor @Slf4j public class JobCBL implements JobMaker { public Job create(){ String id = UUID.randomUUID().toString(); return new JobBuilder("CBL-"+id, jobRepository) .incrementer(new RunIdIncrementer()) .flow(cblStep()) .end() .build(); } private Step cblStep(){ String id = UUID.randomUUID().toString(); id = "cblStep"+id; return new StepBuilder(id, jobRepository) .<TransactionProjection, List<DDSITransactionDetails>> chunk(10, transactionManager) .reader(new CBLReader(repository, repository, transactionSPcall)) .processor(batchProcessor) .writer(batchWriter) .build(); } } @Component @RequiredArgsConstructor @Slf4j public class JobTransactionCBL implements JobMaker { public Job create(){ String id = UUID.randomUUID().toString(); return new JobBuilder("TRANSACTION-"+id, jobRepository) .incrementer(new RunIdIncrementer()) .flow(transactionStep()) .end() .build(); } private Step transactionStep(){ String id = UUID.randomUUID().toString(); id = "transactionStep"+id; return new StepBuilder(id, jobRepository) .<DDSITransactionDetails, DebitRequest> chunk(10, transactionManager) .reader(new CBLTransactionReader(repository, transactionSPcall)) .processor(transactionProcessor) .writer(transactionWriter) .build(); } } @Component @RequiredArgsConstructor @Slf4j public class JobTransactionEFTN implements JobMaker { public Job create(){ String id = UUID.randomUUID().toString(); return new JobBuilder("TRANSACTION-"+id, jobRepository) .incrementer(new RunIdIncrementer()) .flow(transactionStep()) .end() .build(); } private Step transactionStep(){ log.info(Constants.batchTag + "{} transactionStep "); return new StepBuilder("transactionStep", jobRepository) .<DDSITransactionDetails, DebitRequest> chunk(10, transactionManager) .reader(new EFTNTransactionReader(repository, transactionSPcall)) .processor(transactionProcessor) .writer(transactionWriter) .build(); } }
application.properties配置
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver spring.datasource.url=jdbc:oracle:thin:@localhost:1633/testdb spring.datasource.username=test spring.datasource.password=test spring.batch.job.enabled=true spring.batch.job.chunk.size=10
核心疑问:
- Spring Batch是否支持多任务同时执行?
- 上述ORA-02289错误如何解决?
解答
1. Spring Batch是否支持多任务同时执行?
Spring Batch支持多任务并行执行,但默认的SimpleJobLauncher是同步阻塞执行的,每次只能运行一个作业。要实现真正的并行执行,需要配置异步的JobLauncher,通过引入TaskExecutor来实现多线程调度:
@Configuration public class BatchConfig { @Bean public JobLauncher asyncJobLauncher(JobRepository jobRepository) throws Exception { SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); jobLauncher.setJobRepository(jobRepository); // 使用线程池任务执行器 jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor()); jobLauncher.afterPropertiesSet(); return jobLauncher; } }
配置后,多个定时任务触发时,JobLauncher会用不同线程同时执行多个作业。
2. ORA-02289错误解决
这个错误是Oracle数据库提示指定的序列不存在,根源和Spring Batch的元数据管理有关,解决步骤如下:</think_never_used_51bce0c785ca2f68081bfa7d91973934>
(1) 初始化Spring Batch元数据表及序列
Spring Batch依赖一套元数据表来管理作业实例、执行记录等,其中包含多个序列(比如BATCH_JOB_SEQ、BATCH_STEP_SEQ)用于生成唯一ID。如果这些序列不存在,就会触发ORA-02289错误。
解决方式:
- 手动执行脚本:从Spring Batch官方文档获取对应Oracle版本的DDL脚本,在数据库中执行创建元数据表和序列。
- 自动初始化:在
application.properties中添加配置,让Spring Boot自动初始化元数据:
注意:生产环境使用# 开发环境可以用always,生产环境建议用embedded或never spring.batch.initialize-schema=alwaysalways会清空现有元数据,需谨慎操作。
(2) 修复代码中的潜在问题
- JobFactory的依赖注入问题:JobFactory类中的
jobEFT、jobCBL等字段没有注入,会导致getInstance返回null,引发空指针异常。需添加构造器注入或@Autowired注解:@Component // 先把JobFactory注册为Spring组件 @RequiredArgsConstructor public class JobFactory { private final JobEFT jobEFT; private final JobCBL jobCBL; private final JobTransactionCBL jobTransactionCBL; private final JobTransactionEFTN jobTransactionEFTN; // ... 原有getInstance方法 } - Step名称冲突问题:
JobTransactionEFTN的step固定使用"transactionStep"作为名称,多个并行作业会导致元数据冲突,建议和其他Job一样用UUID生成唯一名称:private Step transactionStep(){ log.info(Constants.batchTag + "{} transactionStep "); String id = UUID.randomUUID().toString(); return new StepBuilder("transactionStep"+id, jobRepository) .<DDSITransactionDetails, DebitRequest> chunk(10, transactionManager) .reader(new EFTNTransactionReader(repository, transactionSPcall)) .processor(transactionProcessor) .writer(transactionWriter) .build(); }
内容的提问来源于stack exchange,提问作者Enamul Haque
相关产品推荐
相关产品推荐

