在 Spring Boot 中结合 EasyPoi 实现大文件 Excel 的导入导出,需要思考内存占用、分片处理等问题,避免 OOM(内存溢出)。以下是具体实现方案:
一、环境准备
1. 引入依赖(pom.xml)
xml
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- EasyPoi 核心依赖 -->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.4.0</version>
</dependency>
二、大文件 Excel 导出(分片处理)
大文件导出的核心是分片查询数据,避免一次性加载全部数据到内存。
1. 定义实体类(使用 EasyPoi 注解)
@Data
public class User {
@Excel(name = "ID", width = 10)
private Long id;
@Excel(name = "姓名", width = 20)
private String name;
@Excel(name = "年龄", width = 10)
private Integer age;
@Excel(name = "注册时间", format = "yyyy-MM-dd HH:mm:ss", width = 20)
private Date registerTime;
}
2. 导出工具类(分片查询 + 流式写入)
@Component
public class ExcelExportUtil {
/**
* 大文件Excel导出(分片处理)
* @param response 响应对象
* @param fileName 文件名
* @param clazz 实体类class
* @param dataProvider 数据提供器(分片查询数据)
*/
public <T> void exportBigExcel(HttpServletResponse response, String fileName,
Class<T> clazz, DataProvider<T> dataProvider) throws IOException {
// 设置响应头
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
// 初始化Excel导出参数
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF); // 大文件提议用XSSF(.xlsx)
// 流式写入Excel
Workbook workbook = null;
try {
workbook = ExcelExportUtil.exportBigExcel(exportParams, clazz, dataProvider);
workbook.write(response.getOutputStream());
} finally {
if (workbook != null) {
workbook.close();
}
}
}
// 数据提供器接口(分片查询数据)
@FunctionalInterface
public interface DataProvider<T> {
List<T> provide(int pageNum, int pageSize); // 返回指定页的数据
}
}
3. 控制器实现
@RestController
@RequestMapping("/excel")
public class ExcelController {
@Autowired
private ExcelExportUtil exportUtil;
@Autowired
private UserService userService; // 业务层(提供分片查询)
@GetMapping("/export/big")
public void exportBigExcel(HttpServletResponse response) throws IOException {
// 调用工具类导出,通过DataProvider分片查询数据
exportUtil.exportBigExcel(response, "用户列表", User.class,
(pageNum, pageSize) -> userService.queryByPage(pageNum, pageSize)); // 分页查询逻辑
}
}
4. 业务层分页查询(示例)
@Service
public class UserService {
// 模拟分页查询(实际项目中替换为数据库分页查询)
public List<User> queryByPage(int pageNum, int pageSize) {
int start = (pageNum - 1) * pageSize;
// 从数据库查询第start到start+pageSize的数据
return userMapper.selectByPage(start, pageSize);
}
}
三、大文件 Excel 导入(SAX 解析)
大文件导入需避免一次性加载全部行到内存,应使用SAX 解析(逐行读取)。
1. 自定义 SAX 解析监听器
public class BigExcelListener extends AnalysisEventListener<User> {
private static final int BATCH_SIZE = 1000; // 批量处理阈值
private List<User> batchList = new ArrayList<>(BATCH_SIZE);
private UserService userService; // 业务层(用于保存数据)
// 构造器注入业务层
public BigExcelListener(UserService userService) {
this.userService = userService;
}
// 逐行解析时触发
@Override
public void invoke(User user, AnalysisContext context) {
batchList.add(user);
// 达到阈值时批量保存
if (batchList.size() >= BATCH_SIZE) {
saveData();
batchList.clear(); // 清空列表释放内存
}
}
// 解析完成后触发(处理剩余数据)
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData(); // 保存最后一批数据
}
// 批量保存数据
private void saveData() {
userService.batchSave(batchList); // 批量插入数据库
}
}
2. 导入工具类
@Component
public class ExcelImportUtil {
/**
* 大文件Excel导入(SAX解析)
* @param file 上传的文件
* @param clazz 实体类class
* @param listener 自定义监听器
*/
public <T> void importBigExcel(MultipartFile file, Class<T> clazz, AnalysisEventListener<T> listener) throws Exception {
// 获取文件输入流
InputStream inputStream = file.getInputStream();
// 构建SAX解析器
ExcelReader reader = ExcelReaderBuilder
.create()
.file(inputStream)
.head(clazz) // 表头对应实体类
.autoTrim(true) // 自动trim字符串
.build();
// 开始解析(逐行处理)
reader.read(new Sheet(1, 0, clazz), listener); // 第1个sheet,从第0行开始读
inputStream.close();
}
}
3. 控制器实现
@RestController
@RequestMapping("/excel")
public class ExcelController {
@Autowired
private ExcelImportUtil importUtil;
@Autowired
private UserService userService;
@PostMapping("/import/big")
public String importBigExcel(@RequestParam("file") MultipartFile file) throws Exception {
// 使用自定义监听器处理导入
importUtil.importBigExcel(file, User.class, new BigExcelListener(userService));
return "导入成功";
}
}
4. 业务层批量保存(示例)
@Service
public class UserService {
// 批量保存(实际项目中使用MyBatis的batch插入)
@Transactional
public void batchSave(List<User> users) {
userMapper.batchInsert(users);
}
}
四、关键优化点
- 导出优化:使用XSSF格式(.xlsx),支持更大文件(相比 HSSF 的.xls 的 65536 行限制)。分片查询数据(如每次查 1000 行),避免一次性加载全量数据。
- 导入优化:基于 SAX 的逐行解析,内存占用低。批量插入数据库(减少 SQL 执行次数)。
- 其他提议:大文件处理提议用异步任务(如@Async),避免阻塞主线程。增加进度条反馈(可结合 WebSocket 实时更新进度)。限制文件大小(如 100MB 以内),避免恶意上传超大文件。
通过以上方式,可有效处理大文件 Excel 的导入导出,避免内存溢出问题,适用于十万级甚至百万级数据量的场景。
© 版权声明
文章版权归作者所有,未经允许请勿转载。



ExcelExportUtil里面的exportBigExcel自己调自己?玩死循环呢?
共享学习,欢迎点赞,感谢关注
收藏了,感谢分享