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执行都创建新实例:
- 修改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; }
- 确保你的Step定义中,
reader(excelStudentReader())会每次获取新的实例,这样既解决了状态复用的问题,又能正确读取用户上传的文件。
额外建议
- 作业完成后,可以在Controller中或者Job的Listener里删除临时上传的文件,避免磁盘空间被占用。
- 可以在Reader中显式指定要读取的sheet(比如
reader.setSheetNames(new String[]{"Sheet1"})),避免默认遍历所有sheet带来的意外问题。
内容的提问来源于stack exchange,提问作者Explain Down Vote
相关产品推荐
相关产品推荐

