环境:Spring Boot 3.5.0
1. 简介
在实际项目中,Excel 导入一直是一个绕不开的需求。用户上传文件后,开发者往往需要处理文件解析、数据校验、异常记录、批量写入、失败重试等一系列问题。数据量一大,简单的循环读取不仅效率低,还容易导致内存占用过高。Spring Batch 提供了一套完整的批处理机制,将读取、处理、写入这些步骤进行标准化封装,让 Excel 导入不再依赖大量重复代码。通过合理设计 Job 和 Step,就能轻松应对大批量数据处理场景。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-batch</artifactId></dependency><dependency><groupId>org.springframework.batch.extensions</groupId><artifactId>spring-batch-excel</artifactId><version>0.2.0</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.5.1</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.5.1</version></dependency>
@Entity@Table(name = "t_person")public class Person {@Idprivate Long id ;private String name;private Integer age ;private String email ;private String address ;private String phone ;}
public interface PersonRepository extends JpaRepository<Person, Long> {}
Spring Batch 扩展模块通过提供 PoiItemReader 类来简化了Excel的读取,该类实现了 ItemReader 接口,并利用 Apache POI 读取 Excel 文件。它将按工作表逐个、按行逐行地读取文件。需要注意的是,该类并非线程安全的。
@BeanItemReader<Person> personReader(ApplicationContext context) {PoiItemReader<Person> reader = new PoiItemReader<>();reader.setLinesToSkip(1);reader.setResource(context.getResource(context.getEnvironment().getProperty("pack.resource.file")));reader.setRowMapper(new PersonRowMapper()) ;return reader;}
public class PersonRowMapper implements RowMapper<Person> {@Overridepublic Person mapRow(RowSet rs) throws Exception {Person person = new Person();person.setId(Long.valueOf(rs.getProperties().getProperty("编号"))) ;person.setName(rs.getProperties().getProperty("姓名")) ;person.setAge(Integer.valueOf(rs.getProperties().getProperty("年龄"))) ;person.setEmail(rs.getProperties().getProperty("邮箱")) ;person.setAddress(rs.getProperties().getProperty("住址")) ;person.setPhone(rs.getProperties().getProperty("电话")) ;return person;}}
创建 ItemWriter,它将 Person 实体写入数据库。在写入实现中,我们可以使用事务控制提交和回滚过程,或者直接使用 JpaRepository,它会在处理每条记录时自动保存并提交该记录。
public class DatabasePersonWriter implements ItemWriter<Person> {private static final Logger logger = LoggerFactory.getLogger(DatabasePersonWriter.class);private final PersonRepository personRepository;public DatabasePersonWriter(PersonRepository personRepository) {this.personRepository = personRepository;}@Overridepublic void write(Chunk<? extends Person> personChunk) throws Exception {logger.info("写 {} 条数据", personChunk.size());this.personRepository.saveAllAndFlush(personChunk.getItems()) ;}}
创建 ItemProcessor,并在其中定义数据验证和转换逻辑。该逻辑必须针对具体项目进行编写。我们仅对值进行日志记录,以验证该方法是否已被调用。这个步骤是可选的。
public class LoggingPersonProcessor implements ItemProcessor<Person, Person> {private static final Logger logger = LoggerFactory.getLogger(LoggingPersonProcessor.class);@Overridepublic Person process(Person item) throws Exception {logger.info("Processing person: {}", item);return item;}}
"Step" 用于明确表示要执行的步骤配置,这些步骤主要包括读取、写入和/或处理记录。
@BeanStep excel2dbStep(ItemReader<Person> personReader, PlatformTransactionManager transactionManager,DatabasePersonWriter databasePersonWriter, JobRepository jobRepository) {return new StepBuilder("excel2db-step", jobRepository).<Person, Person>chunk(10, transactionManager).reader(personReader).processor(loggingPersonProcessor()).writer(databasePersonWriter).build();}
一个Job可能包含多个步骤。每个Step可能涉及不同的读写操作。通过创建一个 Job我们可以定义哪些步骤属于该作业。
@RestController@RequestMapping("/excel2db")public class Excel2DbController {private final JobLauncher jobLauncher;private final Step excel2dbStep ;private final JobRepository jobRepository ;public Excel2DbController(JobLauncher jobLauncher,Step excel2dbStep, JobRepository jobRepository) {this.jobLauncher = jobLauncher;this.excel2dbStep = excel2dbStep ;this.jobRepository = jobRepository ;}@GetMappingpublic ResponseEntity<?> execl2Db() throws Exception {var builder = new JobBuilder("excelToDatabase", jobRepository);Job job = builder.start(excel2dbStep).build() ;Map<String, JobParameter<?>> parameters = Map.of("timestamp", new JobParameter<>(System.currentTimeMillis(), Long.class)) ;JobParameters jobParameters = new JobParameters(parameters ) ;this.jobLauncher.run(job, jobParameters) ;return ResponseEntity.ok("success") ;}}
准备如下excel



项目亮点!Spring Boot 多线程事务一致性方案,支持JDBC,MyBatis,JPA
技巧,非常实用!Spring Boot接口参数这样获取真优雅
Spring Boot 接口多实现注入的5种方法,最后一这种非常巧妙
Jackson!JSON处理的瑞士军刀ObjectMapper
Java Agent + ByteBuddy 实现 Spring Boot 运行时方法级监控

夜雨聆风