Spring Batch定时任务仅首次执行有效,后续仅显示完成无读写
Spring Batch定时任务仅首次执行读写的问题分析与解决
你遇到的问题很典型——定时调度的Spring Batch任务每次都能触发并标记为COMPLETED,但只有第一次有实际的REST读取、处理和写入操作,后续任务直接跳过了核心步骤。结合你的代码和batch_job_execution表数据,我们来一步步排查:
从数据库数据看异常
先看你的batch_job_execution记录:
- 第一条任务(ID338):开始时间
2018-05-18 14:36:36,结束时间2018-05-18 14:37:47,有明显的执行耗时,说明确实跑了读写逻辑。 - 后续任务(ID339及以后):开始和结束时间完全一致,几乎瞬间完成,说明任务没有执行任何实际的步骤处理,直接标记为完成。
你的代码细节
Spring Boot应用入口
@EnableScheduling @SpringBootApplication public class BatchApplication { public static void main(String[] args) { SpringApplication.run(BatchApplication.class,args); } }
任务调度器
@Component public class BatchScheduler { Logger log = Logger.getLogger(BatchScheduler.class); @Autowired private JobLauncher jobLauncher; @Autowired private JobCompletionNotificationListener listener; @Autowired private Step step1; @Autowired public JobBuilderFactory jobBuilderFactory; @Autowired private CreateRegistrationsBatchConfiguration job; @Scheduled(fixedRate = 85000) public void runJob() { try{ JobExecution execution = jobLauncher.run( job.importRegistrationJob(jobBuilderFactory, listener, step1), new JobParametersBuilder().addLong("uniqueness", System.nanoTime()).toJobParameters() ); log.info("Job finished with status :" + execution.getStatus()); }catch(Exception exception) { log.error(exception.getMessage()); } } }
Spring Batch配置类
@Configuration @EnableBatchProcessing public class CreateRegistrationsBatchConfiguration { Logger log = Logger.getLogger(CreateRegistrationsBatchConfiguration.class); @Autowired public JobBuilderFactory jobBuilderFactory; @Autowired public StepBuilderFactory stepBuilderFactory; @Autowired private Environment environment; @Bean RestTemplate restTemplate() { return new RestTemplate(); } @Bean ItemReader<EmployeeEmploymentDTO> restEmployeeReader(Environment environment, RestTemplate restTemplate) { return new RESTEmployeeReader( environment.getRequiredProperty("rest.api.url"), restTemplate ); } @Bean public RegistrationItemProcessor processor() { return new RegistrationItemProcessor(); } @Bean public ItemWriter<List<Registration>> writer() { return new MultiOutputItemWriter(); } @Bean public Job importRegistrationJob(JobBuilderFactory jobs, JobCompletionNotificationListener listener, Step step1) { return jobBuilderFactory.get("importRegistrationJob") .incrementer(new RunIdIncrementer()) .listener(listener) .flow(step1) .end() .build(); } @Bean public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader<EmployeeEmploymentDTO> reader, ItemWriter<List<Registration>> writer, ItemProcessor<EmployeeEmploymentDTO, List<Registration>> processor) { return stepBuilderFactory.get("step1").allowStartIfComplete(true) .<EmployeeEmploymentDTO, List<Registration>>chunk(10) .reader(restEmployeeReader(environment,restTemplate())) .processor(processor()) .writer(writer) .build(); } }
问题根源
最可能的原因是你的RESTEmployeeReader是单例Bean,且没有重置读取状态:
- Spring默认所有Bean都是单例模式,所以第一次Job执行时,Reader读取完数据后,内部的状态(比如是否已经读取完毕的标记、游标位置等)会保持在“已读完”的状态。
- 后续Job执行时,复用同一个Reader实例,
hasNext()直接返回false,导致整个Step没有任何数据处理,直接结束。
另外,你在Step配置中加了allowStartIfComplete(true),这个配置是允许Step在已经完成后再次启动,但它不会自动重置Reader的状态。
解决方案
方案1:将Reader设置为原型Scope
让Spring每次创建Job实例时都生成新的Reader,避免状态复用:
@Bean @Scope("prototype") // 关键:每次请求都创建新实例 ItemReader<EmployeeEmploymentDTO> restEmployeeReader(Environment environment, RestTemplate restTemplate) { return new RESTEmployeeReader( environment.getRequiredProperty("rest.api.url"), restTemplate ); }
方案2:让Reader实现ItemStream接口重置状态
如果不想改变Bean的Scope,可以让RESTEmployeeReader实现ItemStream接口,在每次Job启动时重置读取状态:
public class RESTEmployeeReader implements ItemReader<EmployeeEmploymentDTO>, ItemStream { private String apiUrl; private RestTemplate restTemplate; private List<EmployeeEmploymentDTO> dataList; private int currentIndex = 0; // 构造方法省略 @Override public EmployeeEmploymentDTO read() throws Exception { if (dataList == null) { dataList = restTemplate.getForObject(apiUrl, new ParameterizedTypeReference<List<EmployeeEmploymentDTO>>() {}); } if (currentIndex < dataList.size()) { return dataList.get(currentIndex++); } else { return null; // 表示读取完毕 } } @Override public void open(ExecutionContext executionContext) throws ItemStreamException { // 每次Job启动时重置状态 currentIndex = 0; dataList = null; } @Override public void update(ExecutionContext executionContext) throws ItemStreamException { // 可选:保存当前状态到ExecutionContext,用于重启 executionContext.putInt("currentIndex", currentIndex); } @Override public void close() throws ItemStreamException { // 清理资源 } }
额外检查点
- 确认你的REST接口在后续调用时确实能返回数据(可以单独测试接口),排除接口本身返回空的情况。
- 检查JobParameters是否正确生成:你用了
System.nanoTime()作为唯一参数,这部分是对的,确保每次Job都是新的实例,不会被Spring Batch认为是重复任务。
内容的提问来源于stack exchange,提问作者Nandhini
相关产品推荐
相关产品推荐

