准备工作
maven+springboot+mybatis
pom添加.
// maven依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
整体思路
因为之前的项目一直使用poi进行excel的导出导入,而poi对大数据的的导出对内存使用较大,数据量一大很容易出现OOM。而此时使用的easyExcel对poi的这些问题进行了处理,这是alibaba出品的简单、省内存的Java解析Excel工具。
- 准备20万数据;
- 页面请求之后开始对文件生成做异步处理,文件生成后通知客户端已生成,客户端下载之后将文件从服务器删除;
- 对需要导出的数据做分页处理;
- 利用线程池将每一页数据导出一个excel;
- 最后压缩成一个zip文件供客户端下载;
- 通知客户端下载的url;
controller层
// controller层
@GetMapping("excelUser")
public String excelUser(){
int size = 40000;
String url = "http://localhost/fileName.zip";
String fileName = userService.excelUser(size);
url = url.replace("fileName", fileName);
return url;
}
service层
// service层
@Override
public String excelUser(Integer size) {
String fileName = String.valueOf(System.currentTimeMillis());
ExcelUtil.pool.submit(()->{
//查询总数
int count = this.mapper.findCount();
int totalPage = count % size == 0 ? count / size : count / size + 1;
int beginIndex = 0;
List<Callable<List<UserVO>>> tasks = new ArrayList<>();
//分页查询
for (int i = 0; i < totalPage; i++) {
UserMapper mapper = this.mapper;
int finalBeginIndex = beginIndex;
Callable<List<UserVO>> task = () -> mapper.findByPage(finalBeginIndex, size);
beginIndex = beginIndex + size ;
tasks.add(task);
}
ZipOutputStream zipout = null;
InputStream inputStream = null;
try {
//导出压缩文件的全路径(此地址改成服务器存放地址)
String zipFilePath = "D:\\nginx\\html\\" + fileName +".zip";
//导出zip
File zip = new File(zipFilePath);
zipout = new ZipOutputStream(new FileOutputStream(zip));
List<Future<List<UserVO>>> futures = ExcelUtil.pool.invokeAll(tasks);
int num = 0;
if (futures.size() > 0) {
for (Future<List<UserVO>> future : futures) {
//sheetName页名称
String sheetName = "sheetName";
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ExcelWriter writer = EasyExcel.write(outputStream, UserVO.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
//导出excel
writer.write(future.get(), writeSheet);
writer.finish();
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
//excel文件写入zip
ZipEntry zipEntry = new ZipEntry(fileName + "-" + num + ".xlsx");
zipout.putNextEntry(zipEntry);
int len;
byte[] buf = new byte[1024];
while ((len = inputStream.read(buf)) > 0) {
zipout.write(buf, 0, len);
}
num ++;
}
}
//通知客户端已完成
log.info("success" + "==========================" + zipFilePath);
} catch (Exception e) {
log.error(e.getMessage());
} finally {
try {
zipout.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
return fileName;
}
mapper层
// mapper层
<select id="findCount" resultType="java.lang.Integer">
SELECT COUNT(id) FROM user;
</select>
<select id="findByPage" resultType="com.eoil.entity.vo.UserVO">
SELECT id, name, age, money, create_time
FROM user LIMIT #{begin}, #{size};
</select>
VO
// VO
@Data
public class UserVO {
@ExcelProperty(value = "id", index = 0)
private Integer id;
@ExcelProperty(value = "姓名", index = 1)
private String name;
@ExcelProperty(value = "年龄", index = 2)
private Integer age;
@ExcelProperty(value = "金额", index = 3)
private String money;
@ColumnWidth(22)
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "创建时间", index = 4)
private Date createTime;
}
表结构
// 表结构
CREATE TABLE `user` (
`id` bigint(10) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
`age` int(10) DEFAULT NULL,
`money` int(10) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=262127 DEFAULT CHARSET=utf8;
测试
启动项目,访问http://ip:端口/excelUser将异步返回一段可下载的url,此时客服端无需等待,等程序处理完成可以使用webSocket,推送通知,等通知方式,告知客户端已完成(本文暂时没有这样做)。
访问url将返回一段可下载路径,例如我本地访问路径。
此时复制url进入浏览器下载即可
文件内容
打开第一个文件
打开最后一个文件
备注
1:这样解决了客户端同步阻塞式的下载方式。
2:减少服务器一次性查询大量数据和导出数据的压力。
3:主要的性能瓶颈和sql的性能有关,根据测试导出这么多数据大概需要6秒(无索引的情况下),索引是否全面覆盖,表数据量非常大,是否进行了分区,只要控制sql执行效率高,基本导出不会太慢。
easyExcel
链接: easyExcel官网.