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

Spring Batch读取Excel二次调用失败:Sheet索引越界问题

排查Spring Batch二次调用时Excel读取的Sheet索引越界异常

我来帮你分析这个问题:你首次调用Spring Batch作业正常,但第二次调用抛出Sheet index (3) is out of range (0..2)的异常,核心原因和解决方案如下:

异常信息

Caused by: java.lang.IllegalArgumentException: Sheet index (3) is out of range (0..2).

相关代码片段

Config类

@Configuration 
@EnableBatchProcessing 
@PropertySource("classpath:batch.properties") 
public class ExcelFileToDatabaseJobConfig { 
    @Autowired public JobBuilderFactory jobBuilderFactory; 
    @Autowired public StepBuilderFactory stepBuilderFactory; 

    @Bean 
    ItemReader<StudentDTO> excelStudentReader() throws Exception { 
        System.out.println("inside excelStudentReader"); 
        PoiItemReader<StudentDTO> reader = new PoiItemReader<>(); 
        reader.setLinesToSkip(1); 
        reader.setResource(new UrlResource("file:///C:/Users/lenovo/IdeaProjects/test/out/production/classes/inserttest.xlsx")); 
        reader.setRowMapper(excelRowMapper()); 
        return reader; 
    } 

    private RowMapper<StudentDTO> excelRowMapper() { 
        System.out.println("inside excelRowMapper"); 
        BeanWrapperRowMapper<StudentDTO> rowMapper = new BeanWrapperRowMapper<>(); 
        rowMapper.setTargetType(StudentDTO.class); 
        return rowMapper; 
    } 

    @Bean 
    public JdbcBatchItemWriter<StudentDTO> writer(DataSource dataSource) { 
        System.out.println("inside writer"); 
        return new JdbcBatchItemWriterBuilder<StudentDTO>() 
            .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) 
            .sql("INSERT INTO tbl_item_master (mrp_rate,price,item_code) VALUES (:mrp_rate,:rate,:u_item_code)") 
            .dataSource(dataSource) 
            .build(); 
    } 

    @Bean 
    public Job importUserJob(JobCompletionNotificationListener listener, Step step1) { 
        System.out.println("inside importUserJob"); 
        return jobBuilderFactory.get("importUserJob") 
            .incrementer(new RunIdIncrementer()) 
            .listener(listener) 
            .flow(step1) 
            .end() 
            .build(); 
    } 

    @Bean 
    public Step step1(JdbcBatchItemWriter<StudentDTO> writer,CustomChunkListener customChunkListener, CustomItemReaderListener customItemReaderListener,CustomItemWriterListener customItemWriterListener, CustomStepListener customStepListener,CustomSkipListener customSkipListener) throws Exception { 
        System.out.println("inside step1"); 
        return stepBuilderFactory.get("step1") 
            .<StudentDTO, StudentDTO>chunk(3000) 
            .reader(excelStudentReader()) 
            .writer(writer) 
            .faultTolerant() 
            .skip(Exception.class) 
            .noRetry(Exception.class) 
            .noRollback(Exception.class) 
            .skipLimit(100) 
            .listener(customItemWriterListener) 
            .listener(customSkipListener) 
            .build(); 
    } 

    @Bean public CustomStepListener customStepListener() { return new CustomStepListener(); } 
    @Bean public CustomItemWriterListener itemWriterListener() { return new CustomItemWriterListener(); } 
    @Bean public CustomItemReaderListener itemReaderListener() { return new CustomItemReaderListener(); } 
    @Bean public CustomSkipListener customSkipListener() { return new CustomSkipListener(); } 
}

Controller类

@Controller 
public class FileController1 { 
    @Autowired private JobLauncher jobLauncher; 
    @Autowired private Job importUserJob; 

    @RequestMapping(value = "/echofile", method = RequestMethod.POST, produces = {"application/json"}) 
    public @ResponseBody String echoFile(MultipartHttpServletRequest request, HttpServletResponse response) throws Exception { 
        MultipartFile multipartFile = request.getFile("file"); 
        String path = new ClassPathResource("/").getURL().getPath(); 
        File fileToImport = new File(path + multipartFile.getOriginalFilename()); 
        OutputStream outputStream = new FileOutputStream(fileToImport); 
        IOUtils.copy(multipartFile.getInputStream(), outputStream); 
        outputStream.flush(); 
        outputStream.close(); 

        JobExecution jobExecution = jobLauncher.run(importUserJob,new JobParametersBuilder() 
            .addString("fullPathFileName", fileToImport.getAbsolutePath())
            .addLong("time",System.currentTimeMillis())
            .toJobParameters()); 
        return "something"; 
    } 
}

batch.properties

spring.batch.job.enabled=false 

问题原因

PoiItemReader是有状态的读取器,它会记录当前读取到的sheet索引、行号等状态信息。而你在@Bean方法中定义它时,Spring默认会将其作为单例Bean管理——也就是说,整个应用中只有这一个reader实例。

第一次作业运行时,reader已经遍历完了Excel的所有sheet(索引0到2),状态停留在了“下一个要读取的sheet是索引3”的位置。第二次调用作业时,复用了这个已经处于“读完状态”的reader,自然就会抛出索引越界的异常。

另外还有一个小问题:你的Controller中通过Job参数传递了上传文件的路径,但Reader代码里硬编码了固定的文件路径,根本没用到这个参数!这意味着你每次运行作业都在读取同一个固定文件,而不是用户上传的文件,这显然不符合你的需求。

解决方案

方案1:让Reader每次作业都生成新实例(解决状态复用问题)

给excelStudentReader方法添加@Scope("prototype")注解,让Spring每次请求这个Bean时都创建新实例,避免状态残留:

@Bean
@Scope("prototype") // 关键:每次调用都创建新的Reader实例
ItemReader<StudentDTO> excelStudentReader() throws Exception {
    // 原有代码不变
}

方案2:动态读取上传的文件(同时解决状态和文件路径问题)

使用@Scope("step")让Reader能获取Job参数中的文件路径,并且每次Step执行都创建新实例:

  1. 修改Reader的Bean定义:
@Bean
@Scope("step") // 步骤级作用域,允许访问Job参数
ItemReader<StudentDTO> excelStudentReader(@Value("#{jobParameters['fullPathFileName']}") String filePath) throws Exception {
    System.out.println("inside excelStudentReader, reading file: " + filePath);
    PoiItemReader<StudentDTO> reader = new PoiItemReader<>();
    reader.setLinesToSkip(1);
    reader.setResource(new FileSystemResource(filePath)); // 使用上传的文件路径
    reader.setRowMapper(excelRowMapper());
    return reader;
}
  1. 确保你的Step定义中,reader(excelStudentReader())会每次获取新的实例,这样既解决了状态复用的问题,又能正确读取用户上传的文件。

额外建议

  • 作业完成后,可以在Controller中或者Job的Listener里删除临时上传的文件,避免磁盘空间被占用。
  • 可以在Reader中显式指定要读取的sheet(比如reader.setSheetNames(new String[]{"Sheet1"})),避免默认遍历所有sheet带来的意外问题。

内容的提问来源于stack exchange,提问作者Explain Down Vote

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:45:25