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

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

核心疑问:

  1. Spring Batch是否支持多任务同时执行?
  2. 上述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=always
    
    注意:生产环境使用always会清空现有元数据,需谨慎操作。

(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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 15:54:56