EasyExcel百万数据导入导出
2024-01-09 22:59:21
https://gitee.com/antirust/idooy-stable/tree/master/idooy-EasyExcel
开发中,导入导出
功能对于后台管理这样的系统来说太常用了,除了实现该功能外导入导出的性能也需要开发人员进行充分的考虑。一般情况下,针对导入导出功能的设计会面临如下几个问题:
- 如果同步导数据,系统的承载的数据量会不会导致接口超时。
- 如果把所有数据一次性装载到内存,很容易引起OOM。
- 数据量太大sql语句必定很慢。
- 如果走异步,如何通知用户导出结果?
- 如果excel文件太大,目标用户打不开怎么办?
个别系统导入导出业务复杂,出现问题的地方就不局限于上面的这几个点,开发中如果是优化的话,那也无法抛开复杂的业务场景而单纯的去讨论导入导出的功能。曾经就亲身经历过业务员导入7W条数据跑好几个小时,同步导出10W条数据接口超时。如果没有业务逻辑从中作梗;单纯的导入和导出10W条数据还是很轻松的。
总之,导出导入功能如果追求效率,就需要往多线程上靠;必要的话,还需要进行异步操作。
本小节基于EasyExcel使用多线程进行高效的导入导出操作
百万数据准备
300W的数据大概97M大小
存储过程
create procedure insert_emp(IN num int)
begin
declare i int default 0;
set autocommit = 0;
repeat
insert into employee(last_name, age, sex, salary, job_id)
values (concat('emp', lpad(i, 8, '0')), floor(rand() * 100), if(rand() > 0.5, '男', '女'),
round(rand() * 10000), floor(rand() * 10));
set i = i + 1;
until i = num end repeat;
commit;
end;
表结构创建语句
create table if not exists employee
(
employee_id int auto_increment primary key,
last_name varchar(100) null,
age int null,
sex varchar(1) null,
salary decimal null,
job_id int null
);
插入300W数据
call insert_emp(3000000);
EasyExcel导出
导出分两步:
- select查询数据(多线程分页查询)
- 数据write写入文件中(因为EasyExcel不支持并发写,即不管是多线程写入单个sheet,还是多线程写入多个sheet都是不允许的)
故EasyExcel高效率导出,就是要合理的使用多线程进行分页数据的查询,(当然还要考虑SQL有没有优化的空间,这里不进行讨论)
EasyExcel不支持并发写
EasyExcel版本3.3.3
,并发写的时候程序异常;查看官方文档,文档明确指出‘不支持并发写’
具体解决办法参看GitHub-issues#3020
导出功能的代码片段
Long count = baseMapper.selectCount(queryWrapper);
Long sheetNum = count % pageSize == 0 ? count / pageSize:count / pageSize + 1;
// 多线程去读
// 1.初始化map容量 防止扩容带来的效率损耗
Map<Integer, Page<T>> pageMap = new ConcurrentHashMap<>(Math.toIntExact(3));
CountDownLatch countDownLatch = new CountDownLatch(Math.toIntExact(3));
// 注意 easyexcel 暂时不支持多线程并发写入!!! 详情请看github上issues
for (int i = 0 ;i< sheetNum;i++){
int finali = i;
threadPoolTaskExecutor.submit(()->{
Page<T> page = new Page<>();
page.setCurrent(finali + 1);
page.setSize(pageSize);
// 获取数据存放到map中
Page<T> selectPage = baseMapper.selectPage(page,queryWrapper);
pageMap.put(finali,selectPage);
// 消耗掉一个
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 写入
try (ExcelWriter excelWriter = EasyExcel.write(out, pojoClass).build()) {
pageMap.forEach((k,v)->{
log.info("正在写入{}条数据",pageSize);
WriteSheet writeSheet = EasyExcel.writerSheet(k, "第"+(k+1)+"批数据").build();
excelWriter.write(v.getRecords(), writeSheet);
pageMap.remove(k);
});
excelWriter.finish();
}
EasyExcel导入
EasyExcel导入思路如下:
- 解析一行插入一行(速度太慢,不可取)
- 逐行解析到达指定行数(EasyExcel提供PageReadListener类);数据库批量插入
EasyExcel.read(resource.getInputStream(), Employee.class, new PageReadListener<Employee>((empList) -> {
// 方式三:多线程批量插入,每次批量插入10W数据,100W数据一共用时12s
threadPool.execute(() ->
employeeMapper.insertBatchSomeColumn(empList));
log.info("成功插入一次{}量的数据", batchSize);
}, batchSize)).sheet().doRead();
- 如果是多个sheet页
- 每个线程处理一个sheet;
- 解析指定的行数以后单线程批量插入。
- 解析指定的行数以后多线程批量插入。
- 每个线程处理一个sheet;
文章来源:https://blog.csdn.net/weixin_43859011/article/details/135419414
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!