SpringBoot实现百万级数据高效导出Excel和CSV实战
SpringBoot实现百万级数据高效导出Excel和CSV实战
你是否曾因为导出大量数据而导致系统响应缓慢甚至崩溃?用户抱怨导出功能卡顿,服务器CPU飙升,内存溢出?今天我就来分享一套完整的解决方案,让你轻松应对百万级数据导出的挑战!
一、为什么传统导出方式会崩溃?
在传统的数据导出实现中,我们通常会一次性将所有数据加载到内存中,然后再写入文件。这种方式在数据量较小时还能接受,但当数据达到百万级别时,就会暴露出严重的问题:
- 内存溢出:一次性加载百万条记录到内存,很容易超出JVM堆内存限制
- 响应时间长:用户需要等待很长时间才能获得导出结果
- 系统资源占用高:大量占用CPU和内存资源,影响其他功能正常使用
- 用户体验差:浏览器可能因等待时间过长而超时
二、高效导出的核心思路
要解决这些问题,我们需要采用分批处理和流式写入的策略:
- 分批查询:每次只从数据库查询固定数量的记录
- 流式写入:边查询边写入文件,避免数据堆积在内存中
- 异步处理:对于大数据量导出,采用异步方式处理,避免阻塞主线程
三、技术选型对比
3.1 CSV vs Excel
| 特性 | CSV | Excel |
|---|---|---|
| 文件大小 | 小 | 大 |
| 处理速度 | 快 | 慢 |
| 内存占用 | 低 | 高 |
| 功能丰富度 | 简单 | 丰富 |
3.2 导出工具选型
- CSV导出:使用Java原生IO流即可
- Excel导出:推荐使用阿里巴巴开源的EasyExcel
四、核心实现代码
4.1 CSV导出实现
@Service
public class CSVExportService {
@Autowired
private UserMapper userMapper;
@Value("${data.export.batch-size}")
private int batchSize;
public String exportUsersToCSV() throws IOException {
// 生成文件名
String fileName = "users_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".csv";
String fullPath = filePath + fileName;
// 创建文件写入器
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fullPath))) {
// 写入CSV头部
writer.write("ID,用户名,邮箱,手机号,年龄,地址,创建时间,更新时间");
writer.newLine();
// 分批查询并写入数据
int offset = 0;
int totalCount = userMapper.selectUserCount();
while (offset < totalCount) {
List<User> users = userMapper.selectUsersByPage(offset, batchSize);
for (User user : users) {
// 写入一行数据
writer.write(formatUserToCSV(user));
writer.newLine();
}
offset += batchSize;
}
return fullPath;
}
}
}
4.2 Excel导出实现(基于EasyExcel)
@Service
public class ExcelExportService {
public String exportUsersToExcel() {
// 生成文件名
String fileName = "users_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xlsx";
String fullPath = filePath + fileName;
// 使用EasyExcel进行导出
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fullPath, User.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("用户数据").build();
// 分批查询并写入数据
int offset = 0;
int totalCount = userMapper.selectUserCount();
while (offset < totalCount) {
List<User> users = userMapper.selectUsersByPage(offset, batchSize);
// 写入一批数据
excelWriter.write(users, writeSheet);
offset += batchSize;
}
return fullPath;
} finally {
// 关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
}
4.3 异步导出实现
@RestController
@RequestMapping("/export")
public class ExportController {
@Autowired
private ExportTaskService exportTaskService;
/**
* 异步导出CSV文件
*/
@GetMapping("/csv/async")
public String exportCSVAsync() {
String taskId = UUID.randomUUID().toString();
exportTaskService.exportCSVAsync(taskId);
return "CSV异步导出任务已提交,任务ID: " + taskId;
}
/**
* 异步导出Excel文件
*/
@GetMapping("/excel/async")
public String exportExcelAsync() {
String taskId = UUID.randomUUID().toString();
exportTaskService.exportExcelAsync(taskId);
return "Excel异步导出任务已提交,任务ID: " + taskId;
}
}
@Service
public class ExportTaskService {
@Async
public void exportCSVAsync(String taskId) {
try {
String filePath = csvExportService.exportUsersToCSV();
// 可以将结果存储到数据库或Redis中,供前端查询
} catch (IOException e) {
log.error("CSV导出任务失败,任务ID: {}", taskId, e);
}
}
@Async
public void exportExcelAsync(String taskId) {
try {
String filePath = excelExportService.exportUsersToExcel();
// 可以将结果存储到数据库或Redis中,供前端查询
} catch (Exception e) {
log.error("Excel导出任务失败,任务ID: {}", taskId, e);
}
}
}
五、性能优化要点
5.1 数据库查询优化
- 合理设置批次大小:建议设置为1000-5000条记录
- 添加索引:确保查询字段有合适的索引
- 避免全表扫描:使用LIMIT子句分页查询
5.2 内存优化
- 及时释放资源:使用try-with-resources确保流正确关闭
- 避免对象堆积:每批数据处理完后,让JVM及时回收
- 调整JVM参数:适当增加堆内存大小
5.3 文件存储优化
- 临时文件清理:定期清理过期的导出文件
- 文件分片存储:对于超大文件可以考虑分片存储
- CDN加速:将导出文件存储到CDN上,提高下载速度
六、异常处理与监控
6.1 异常处理
@Async
public void exportCSVAsync(String taskId) {
try {
String filePath = csvExportService.exportUsersToCSV();
log.info("CSV导出任务完成,任务ID: {},文件路径: {}", taskId, filePath);
// 更新任务状态为成功
} catch (IOException e) {
log.error("CSV导出任务失败,任务ID: {}", taskId, e);
// 更新任务状态为失败
} catch (Exception e) {
log.error("CSV导出任务未知异常,任务ID: {}", taskId, e);
// 更新任务状态为异常
}
}
6.2 监控指标
- 导出任务成功率
- 平均导出耗时
- 内存使用情况
- 数据库查询性能
七、最佳实践建议
- 合理设置批次大小:根据实际测试结果调整batchSize参数
- 添加进度提示:对于长时间运行的任务,提供进度反馈
- 限制导出数据量:避免用户一次性导出过多数据
- 使用异步处理:大数据量导出务必使用异步方式
- 文件压缩:对于大文件可以考虑ZIP压缩后再提供下载
- 权限控制:确保只有授权用户才能执行导出操作
八、总结
通过分批处理、流式写入和异步处理这三大核心技术,我们可以轻松应对百万级数据的导出需求。相比传统的全量加载方式,这种方案具有以下优势:
- 内存占用低:始终只在内存中保留少量数据
- 响应速度快:异步处理不会阻塞用户操作
- 系统稳定性好:避免了内存溢出和系统崩溃的风险
- 用户体验佳:用户可以继续其他操作,导出完成后通知用户
掌握了这套方案,再也不用担心大数据量导出带来的各种问题了。赶紧在你的项目中试试吧!
关注我,获取更多后端技术干货!
标题:SpringBoot实现百万级数据高效导出Excel和CSV实战
作者:jiangyi
地址:http://jiangyi.space/articles/2025/12/21/1766304290456.html
0 评论