基于EasyExcel实现百万级数据导出

news/2023/5/28 7:17:40

基于EasyExcel实现百万级数据导出

在项目开发中往往需要使用到数据的导入和导出,导入就是从Excel中导入到DB中,而导出就是从DB中查询数据然后使用POI写到Excel上。

大数据的导入和导出,相信大家在日常的开发、面试中都会遇到。

很多问题只要这一次解决了,总给复盘记录,后期遇到同样的问题就好解决了。好啦,废话不多说开始正文!

1.传统POI的的版本优缺点比较

其实想到数据的导入导出,理所当然的会想到apache的poi技术,以及Excel的版本问题。

  • HSSFWorkbook

这个实现类是我们早期使用最多的对象,它可以操作Excel2003以前(包含2003)的所有Excel版本。在2003以前Excel的版本后缀还是.xls

  • XSSFWorkbook

这个实现类现在在很多公司都可以发现还在使用,它是操作的Excel2003–Excel2007之间的版本,Excel的扩展名是.xlsx

  • SXSSFWorkbook

这个实现类是POI3.8之后的版本才有的,它可以操作Excel2007以后的所有版本Excel,扩展名是.xlsx

HSSFWorkbook

它是POI版本中最常用的方式,不过:

  • 它的缺点是 最多只能导出 65535行,也就是导出的数据函数超过这个数据就会报错;
  • 它的优点是 不会报内存溢出。(因为数据量还不到7w所以内存一般都够用,首先你得明确知道这种方式是将数据先读取到内存中,然后再操作)

XSSFWorkbook

  • 优点:这种形式的出现是为了突破HSSFWorkbook的65535行局限,是为了针对Excel2007版本的1048576行,16384列,最多可以导出104w条数据;
  • 缺点:伴随的问题来了,虽然导出数据行数增加了好多倍,但是随之而来的内存溢出问题也成了噩梦。因为你所创建的book,Sheet,row,cell等在写入到Excel之前,都是存放在内存中的(这还没有算Excel的一些样式格式等等),可想而知,内存不溢出就有点不科学了!!!

SXSSFWorkbook

从POI 3.8版本开始,提供了一种基于XSSF的低内存占用的SXSSF方式:

优点:

  • 这种方式不会一般不会出现内存溢出(它使用了硬盘来换取内存空间,
  • 也就是当内存中数据达到一定程度这些数据会被持久化到硬盘中存储起来,而内存中存的都是最新的数据),
  • 并且支持大型Excel文件的创建(存储百万条数据绰绰有余)。

缺点:

  • 既然一部分数据持久化到了硬盘中,且不能被查看和访问那么就会导致,
  • 在同一时间点我们只能访问一定数量的数据,也就是内存中存储的数据;
  • sheet.clone()方法将不再支持,还是因为持久化的原因;
  • 不再支持对公式的求值,还是因为持久化的原因,在硬盘中的数据没法读取到内存中进行计算;
  • 在使用模板方式下载数据的时候,不能改动表头,还是因为持久化的问题,写到了硬盘里就不能改变了;

2.使用方式哪种看情况

经过了解也知道了这三种Workbook的优点和缺点,那么具体使用哪种方式还是需要看情况的:

我一般会根据这样几种情况做分析选择:

1、当我们经常导入导出的数据不超过7w的情况下,可以使用 HSSFWorkbook 或者 XSSFWorkbook都行;

2、当数据量查过7w并且导出的Excel中不牵扯对Excel的样式,公式,格式等操作的情况下,推荐使用SXSSFWorkbook;

3、当数据量查过7w,并且我们需要操做Excel中的表头,样式,公式等,这时候我们可以使用 XSSFWorkbook 配合进行分批查询,分批写入Excel的方式来做;

3.百万数据导入导出

想要解决问题我们首先要明白自己遇到的问题是什么?

1、 我遇到的数据量超级大,使用传统的POI方式来完成导入导出很明显会内存溢出,并且效率会非常低;

2、 数据量大直接使用select * from tableName肯定不行,一下子查出来300w条数据肯定会很慢;

3、 300w 数据导出到Excel时肯定不能都写在一个Sheet中,这样效率会非常低;估计打开都得几分钟;

4、 300w数据导出到Excel中肯定不能一行一行的导出到Excel中。频繁IO操作绝对不行;

5、 导入时300万数据存储到DB如果循环一条条插入也肯定不行;

6、导入时300w数据如果使用Mybatis的批量插入肯定不行,因为Mybatis的批量插入其实就是SQL的循环;一样很慢。

解决思路:

  • 针对1 :

其实问题所在就是内存溢出,我们只要使用对上面介绍的POI方式即可,主要问题就是原生的POI解决起来相当麻烦。

经过查阅资料翻看到阿里的一款POI封装工具EasyExcel,上面问题等到解决;

  • 针对2:

不能一次性查询出全部数据,我们可以分批进行查询,只不过时多查询几次的问题,况且市面上分页插件很多。此问题好解决。

  • 针对3:

可以将300w条数据写到不同的Sheet中,每一个Sheet写一百万即可。

  • 针对4:

不能一行一行的写入到Excel上,我们可以将分批查询的数据分批写入到Excel中。

  • 针对5:

导入到DB时我们可以将Excel中读取的数据存储到集合中,到了一定数量,直接批量插入到DB中。

  • 针对6:

不能使用Mybatis的批量插入,我们可以使用JDBC的批量插入,配合事务来完成批量插入到DB。即 Excel读取分批+JDBC分批插入+事务。

4.代码实现

需求:使用EasyExcel完成500w数据的导出。

500w数据的导出解决思路:

  • 首先在查询数据库层面,需要分批进行查询(比如每次查询20w)
  • 每查询一次结束,就使用EasyExcel工具将这些数据写入一次;
  • 当一个Sheet写满了100w条数据,开始将查询的数据写入到另一个Sheet中;
  • 如此循环直到数据全部导出到Excel完毕。

ps:我们需要计算Sheet个数,以及循环写入次数。特别是最后一个Sheet的写入次数

因为你不知道最后一个Sheet会写入多少数据,可能是100w,也可能是25w因为我们这里的500w只是模拟数据,有可能导出的数据比500w多也可能少

ps:我们需要计算写入次数,因为我们使用的分页查询,所以需要注意写入的次数。

其实查询数据库多少次就是写入多少次

准备工作

1.基于maven搭建springboot工程,引入easyexcel依赖,这里我是用的时3.0版本

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.5</version>
</dependency>

2.创建海量数据的sql脚本

CREATE TABLE dept( /*部门表*/
deptno MEDIUMINT   UNSIGNED  NOT NULL  DEFAULT 0,
dname VARCHAR(20)  NOT NULL  DEFAULT "",
loc VARCHAR(13) NOT NULL DEFAULT ""
) ;#创建表EMP雇员
CREATE TABLE emp
(empno  MEDIUMINT UNSIGNED  NOT NULL  DEFAULT 0, /*编号*/
ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/
job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/
mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上级编号*/
hiredate DATE NOT NULL,/*入职时间*/
sal DECIMAL(7,2)  NOT NULL,/*薪水*/
comm DECIMAL(7,2) NOT NULL,/*红利*/
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/
) ;#工资级别表
CREATE TABLE salgrade
(
grade MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
losal DECIMAL(17,2)  NOT NULL,
hisal DECIMAL(17,2)  NOT NULL
);#测试数据
INSERT INTO salgrade VALUES (1,700,1200);
INSERT INTO salgrade VALUES (2,1201,1400);
INSERT INTO salgrade VALUES (3,1401,2000);
INSERT INTO salgrade VALUES (4,2001,3000);
INSERT INTO salgrade VALUES (5,3001,9999);delimiter $$#创建一个函数,名字 rand_string,可以随机返回我指定的个数字符串
create function rand_string(n INT)
returns varchar(255) #该函数会返回一个字符串
begin
#定义了一个变量 chars_str, 类型  varchar(100)
#默认给 chars_str 初始值   'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'declare chars_str varchar(100) default'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; declare return_str varchar(255) default '';declare i int default 0; while i < n do# concat 函数 : 连接函数mysql函数set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));set i = i + 1;end while;return return_str;end $$#这里我们又自定了一个函数,返回一个随机的部门号
create function rand_num( )
returns int(5)
begin
declare i int default 0;
set i = floor(10+rand()*500);
return i;
end $$#创建一个存储过程, 可以添加雇员
create procedure insert_emp(in start int(10),in max_num int(10))
begin
declare i int default 0;
#set autocommit =0 把autocommit设置成0#autocommit = 0 含义: 不要自动提交set autocommit = 0; #默认不提交sql语句repeatset i = i + 1;#通过前面写的函数随机产生字符串和部门编号,然后加入到emp表insert into emp values ((start+i) ,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());until i = max_numend repeat;#commit整体提交所有sql语句,提高效率commit;end $$#添加8000000数据
call insert_emp(100001,8000000)$$#命令结束符,再重新设置为;
delimiter ;

3.实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp implements Serializable {@ExcelProperty(value = "员工编号")private Integer empno;@ExcelProperty(value = "员工名称")private String ename;@ExcelProperty(value = "工作")private String job;@ExcelProperty(value = "主管编号")private Integer mgr;@ExcelProperty(value = "入职日期")private Date hiredate;@ExcelProperty(value = "薪资")private BigDecimal sal;@ExcelProperty(value = "奖金")private BigDecimal comm;@ExcelProperty(value = "所属部门")private Integer deptno;}

4.vo类

@Data
public class EmpVo {@ExcelProperty(value = "员工编号")private Integer empno;@ExcelProperty(value = "员工名称")private String ename;@ExcelProperty(value = "工作")private String job;@ExcelProperty(value = "主管编号")private Integer mgr;@ExcelProperty(value = "入职日期")private Date hiredate;@ExcelProperty(value = "薪资")private BigDecimal sal;@ExcelProperty(value = "奖金")private BigDecimal comm;@ExcelProperty(value = "所属部门")private Integer deptno;}

导出核心代码

public class ExcelConstants {//一个sheet装100w数据public static final Integer PER_SHEET_ROW_COUNT = 1000000;//每次查询20w数据,每次写入20w数据public static final Integer PER_WRITE_ROW_COUNT = 200000;
}
@Override
public void export() throws IOException {OutputStream outputStream =null;try {//记录总数:实际中需要根据查询条件进行统计即可//LambdaQueryWrapper<Emp> lambdaQueryWrapper = new QueryWrapper<Emp>().lambda().eq(Emp::getEmpno, 1000001);Integer totalCount = empMapper.selectCount(null);//每一个Sheet存放100w条数据Integer sheetDataRows = ExcelConstants.PER_SHEET_ROW_COUNT;//每次写入的数据量20w,每页查询20WInteger writeDataRows = ExcelConstants.PER_WRITE_ROW_COUNT;//计算需要的Sheet数量Integer sheetNum = totalCount % sheetDataRows == 0 ? (totalCount / sheetDataRows) : (totalCount / sheetDataRows + 1);//计算一般情况下每一个Sheet需要写入的次数(一般情况不包含最后一个sheet,因为最后一个sheet不确定会写入多少条数据)Integer oneSheetWriteCount = sheetDataRows / writeDataRows;//计算最后一个sheet需要写入的次数Integer lastSheetWriteCount = totalCount % sheetDataRows == 0 ? oneSheetWriteCount : (totalCount % sheetDataRows % writeDataRows == 0 ? (totalCount / sheetDataRows / writeDataRows) : (totalCount / sheetDataRows / writeDataRows + 1));ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletResponse response = requestAttributes.getResponse();outputStream = response.getOutputStream();//必须放到循环外,否则会刷新流ExcelWriter excelWriter = EasyExcel.write(outputStream).build();//开始分批查询分次写入for (int i = 0; i < sheetNum; i++) {//创建SheetWriteSheet sheet = new WriteSheet();sheet.setSheetName("测试Sheet1"+i);sheet.setSheetNo(i);//循环写入次数: j的自增条件是当不是最后一个Sheet的时候写入次数为正常的每个Sheet写入的次数,如果是最后一个就需要使用计算的次数lastSheetWriteCountfor (int j = 0; j < (i != sheetNum - 1 ? oneSheetWriteCount : lastSheetWriteCount); j++) {//分页查询一次20wPage<Emp> page = empMapper.selectPage(new Page(j + 1 + oneSheetWriteCount * i, writeDataRows), null);List<Emp> empList = page.getRecords();List<EmpVo> empVoList = new ArrayList<>();for (Emp emp : empList) {EmpVo empVo = new EmpVo();BeanUtils.copyProperties(emp, empVo);empVoList.add(empVo);}WriteSheet writeSheet = EasyExcel.writerSheet(i, "员工信息" + (i + 1)).head(EmpVo.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();//写数据excelWriter.write(empVoList, writeSheet);}}// 下载EXCELresponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止浏览器端导出excel文件名中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("员工信息", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");excelWriter.finish();outputStream.flush();} catch (IOException e) {e.printStackTrace();} catch (BeansException e) {e.printStackTrace();}finally {if (outputStream != null) {outputStream.close();}}
}

这是我电脑测试时内存占用和CPU使用情况,当然开了其他一些应用。

image-20230119203725607

导出500w数据共计耗时,可以看到差不多400s左右,当然还要考虑业务复杂度已经电脑配置,我这里只是一个导出的demo并不涉及其他业务逻辑,在实际开发中可能时间会比这个更长一些

image-20230119204722601

看下导出效果,我上面的脚本向插入了500w数据,100w一个sheet因此正好五个

image-20230119205106593

image-20230119204933287

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.exyb.cn/news/show-4564234.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

C实现hough变换拟合直线

原理&#xff1a; 对于平面上的一个点&#xff08;x1,y1&#xff09;,满足方程&#xff1a;y1mx1b&#xff0c;经过点&#xff08;x1&#xff0c;y1&#xff09;的直线有无数条&#xff0c;只要其满足刚才的直线方程。然而&#xff0c;可以把直线方程变形一下&#xff0c;b-x1…

力扣(LeetCode)2299. 强密码检验器 II(C++/Python3)

题目描述 模拟 仅当密码包含强密码的所有特性&#xff0c;它是一个 强 密码。提示我们&#xff0c;遍历密码&#xff0c;维护 444 个标志&#xff0c;标志记录特性。遍历结束&#xff0c;根据标志判断特性。 class Solution { public:bool strongPasswordCheckerII(string pa…

hough变换 python+OpenCV的简单实现

直线的hough变换 import cv2 import numpy as np# 导入图片&#xff0c;处理图片&#xff0c;变为灰图 img cv2.imread(lines.jpg) drawing np.zeros(img.shape[:], dtypenp.uint8) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 50, 150)# hough直线…

Hough变换之Hough直线检测

原创文章&#xff0c;保留所有版权。转载请注明出处&#xff1a;http://www.letao.ai/?p282 Hough变换的主要思想是&#xff0c;基于已知边缘点的&#xff0c;对所有可能的参数空间中的参数进行投票。在正确的参数取值处&#xff0c;形成峰值&#xff0c;最终得到要求的结果。…

Pr 入门教程:如何创建电影的结尾片名?

欢迎观看 Premiere Pro 教程&#xff0c;小编带大家学习 Pr 的基本编辑技巧&#xff0c;了解如何创建电影的结尾片名。 遵循传统的两栏设计结束信用名&#xff0c;将设计与他们的名字不同的工作人员的职称。现在已经将职位名称放置在标题图形的一层上&#xff0c;需要将名称放…

转载 | 访问控制是什么?数据安全的关键组成

访问控制验证用户身份&#xff0c;并授予用户访问许可范围内信息的权限。 谁能访问公司的数据&#xff1f;怎样确保尝试访问的人切实得到授权&#xff1f;何种情况下拒绝有权限用户的访问请求&#xff1f; 为有效保护数据&#xff0c;公司访问控制策略必须解决这些&#xff08;…

周源:知乎的未来是什么

送给真正的互联网人一顿干货早餐【小咖导读】这篇文章是小咖看过的为数不多的创始人如此坦诚的访谈&#xff0c;文章里面&#xff0c;你可以看到作为知乎的掌舵者&#xff0c;周源这些年在知乎的思考和挣扎。文章较长&#xff0c;很值得耐心阅读。为知乎寻找未来是怎样一种体验…

常见职位的英文简称_职场中常见的英文缩写是什么意思?4P是哪4P?各个岗位和部门的英文缩写是什么?...

在大一点的企业或者外企中&#xff0c;你一定要知道的英文缩写&#xff0c;了解各个岗位和部门的英文缩写&#xff01;【4P是哪4P】Product&#xff1a;产品Price&#xff1a;价格Place&#xff1a;渠道Promotion&#xff1a;促销【职位缩写】首席品牌官【CBO】——chief brand…

企业域名是什么?域名代表网站流量

10月12日&#xff0c;有网友反映猎聘网无法打开&#xff0c;猎聘APP无法登录。截至10月14日&#xff0c;仍有网友不断向猎聘官方微博下反映该情况&#xff0c;而猎聘官方微博未发布相关情况说明&#xff0c;只是在微博内对反映该情况的用户进行回复&#xff0c;“抱歉&#xff…

pr cpu100%_打工度假签证拿PR三步搞定!高薪,稳定工作,分分钟成为人生赢家!...

国内苦苦工作多年的996上班族&#xff0c;想要换个新环境&#xff0c;丰富人生经历&#xff1f;完全可以&#xff01;顺利拿到打工度假签来澳洲打工就结束了嘛&#xff1f;这只是第一步&#xff01;合法高薪工作之余&#xff0c;你还有机会移民澳洲&#xff01;符合以下条件的打…

JNPF 3.4.5 java+.Net6 旗舰版企业版 简搭-敏捷业务低代码开发平台

JNPF 低代码通常是指APaaS产品&#xff0c;通过为开发者提供可视化的应用开发环境&#xff0c;降低或去除应用开发对原生代码编写的需求量&#xff0c;进而实现便捷构建应用程序的一种解决方案。广义上低代码概念涵盖所有能够完成代码的集成&#xff0c;减少代码开发的应用过程…

2023麦肯锡中国消费者报告

下载报告去公众号&#xff1a;硬核刘大 后台回复“ 中国消费者”&#xff0c;即可下载完整PDF文件。更多报告内容&#xff0c;可加微信&#xff1a;yw5201a1 领取。(ps&#xff1a;加过微信&#xff1a;chanpin628 的不要再加&#xff0c;分享的内容一样&#xff0c;有一个号就…

Linux常用命令——time命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) time 统计给定命令所花费的总时间 补充说明 time命令用于统计给定命令所花费的总时间。 语法 time(参数)参数 指令&#xff1a;指定需要运行的额指令及其参数。 实例 当测试一个程序或比较不同算法时&…

字节新CEO 梁汝波:凭什么,能让张一鸣放心交棒?

作者&#xff1a;流水不争先 编辑&#xff1a;Emma来源| 技术领导力(ID&#xff1a;jishulingdaoli)张一鸣退幕&#xff0c;梁汝波接棒&#xff0c;全网人民立刻磕起瓜子儿——梁汝波是“何方神圣”&#xff1f;凭什么能成为继承者&#xff1f;本期人物故事&#xff0c;我来给…

变质量力学问题与虚功原理解答

FΔy η*(yΔy)g Δy 1/2η*(yΔy)v^2-1/2η*y*v^2 略去二阶小量 Fηygηv^2

[有限元]虚位移原理和虚力原理的证明的统一逻辑

原来的可能位移/可能力的约束方程是&#xff1a; 力边界上 可能力常数1 位移边界上 可能位移常数2 体内 可能平衡方程常数3 所以可能功原理的右边有三项 由定义&#xff0c; 虚位移可能位移1-可能位移2 虚力可能力1-可能力2 所以 ①只考虑虚位移时 位移边界上 虚位移常数2-…

有限元学习笔记-虚功平衡方程的线性化及牛顿-拉普森迭代算法

有限元学习笔记-虚功平衡方程的线性化及牛顿-拉普森迭代算法 张量微分是解连续介质物理问题的利器。

能量原理与变分法笔记10:虚位移原理

参考视频链接 考虑虚功原理的逆命题&#xff1a; 这实际上和 a 0 ,b 0 得到 a b一样,仅仅知道ab是无法确定a或b的具体数值的&#xff0c;需要增强已知条件。 虚功原理的重新证明(从虚位移状态和可能力状态开始) 注意 例题

能量原理与变分法笔记09: 虚功原理的例子

参考视频连接 注&#xff1a;例子中的可能位移状态应该是横向的位移&#xff0c;但是为了表示&#xff0c;图示画了拱形&#xff1a; σNA为应力和轴力的关系\sigma \frac{N}{A}为应力和轴力的关系σAN​为应力和轴力的关系