作者:微信小助手
发布时间:2022-10-17T13:16:02
在项目开发中往往需要使用到数据的导入和导出,导入就是从Excel中导入到DB中,而导出就是从DB中查询数据然后使用POI写到Excel上。 写本文的背景是因为在工作中遇到了大数据的导入和导出,问题既然来了逃跑不如干掉它!!! 只要这一次解决了,后期遇到同样的问题就好解决了。 废话不多说,开始撸起来!!! 其实想到数据的导入导出,理所当然的会想到apache的poi技术,以及Excel的版本问题。 既然要做导入导出,那么我们就先来大致看一下传统poi技术的版本以及优缺点对比吧! 首先我们知道POI中我们最熟悉的莫过于WorkBook这样一个接口,我们的POI版本也在更新的同时对这个几口的实现类做了更新: 这个实现类是我们早期使用最多的对象,它可以操作Excel2003以前(包含2003)的所有Excel版本。在2003以前Excel的版本后缀还是.xls 这个实现类现在在很多公司都可以发现还在使用,它是操作的Excel2003--Excel2007之间的版本,Excel的扩展名是.xlsx 这个实现类是POI3.8之后的版本才有的,它可以操作Excel2007以后的所有版本Excel,扩展名是.xlsx 大致知道了我们在导入导出操作的时候会用到这样三个实现类以及他们可以操作的Excel版本和后缀之后,我们就要从优缺点分析他们了 它是POI版本中最常用的方式,不过: 从POI 3.8版本开始,提供了一种基于XSSF的低内存占用的SXSSF方式: 优点: 缺点: 经过了解也知道了这三种Workbook的优点和缺点,那么具体使用哪种方式还是需要看情况的: 我一般会根据这样几种情况做分析选择: 1、当我们经常导入导出的数据不超过7w的情况下,可以使用 2、当数据量查过7w并且导出的Excel中不牵扯对Excel的样式,公式,格式等操作的情况下,推荐使用 3、当数据量查过7w,并且我们需要操做Excel中的表头,样式,公式等,这时候我们可以使用 铺垫也做了不少,那么现在开始讲讲我在工作中遇到的超百万数据的导入导出解决方案: 想要解决问题我们首先要明白自己遇到的问题是什么? 1、 我遇到的数据量超级大,使用传统的POI方式来完成导入导出很明显会内存溢出,并且效率会非常低; 2、 数据量大直接使用 3、 300w 数据导出到Excel时肯定不能都写在一个Sheet中,这样效率会非常低;估计打开都得几分钟; 4、 300w数据导出到Excel中肯定不能一行一行的导出到Excel中。频繁IO操作绝对不行; 5、 导入时300万数据存储到DB如果循环一条条插入也肯定不行; 6、导入时300w数据如果使用Mybatis的批量插入肯定不行,因为Mybatis的批量插入其实就是SQL的循环;一样很慢。 解决思路: 其实问题所在就是内存溢出,我们只要使用对上面介绍的POI方式即可,主要问题就是原生的POI解决起来相当麻烦。 经过查阅资料翻看到阿里的一款POI封装工具EasyExcel,上面问题等到解决; 不能一次性查询出全部数据,我们可以分批进行查询,只不过时多查询几次的问题,况且市面上分页插件很多。此问题好解决。 可以将300w条数据写到不同的Sheet中,每一个Sheet写一百万即可。 不能一行一行的写入到Excel上,我们可以将分批查询的数据分批写入到Excel中。 导入到DB时我们可以将Excel中读取的数据存储到集合中,到了一定数量,直接批量插入到DB中。 不能使用Mybatis的批量插入,我们可以使用JDBC的批量插入,配合事务来完成批量插入到DB。即 Excel读取分批+JDBC分批插入+事务。 附上GitHub地址:https://github.com/alibaba/easyexcel GitHub地址上教程和说明很详细,并且附带有读和写的demo代码,这里对它的介绍我就不再详细说了。 至于EasyExcel底层怎么实现的这个还有待研究。 EasyExcel完成300w数据的导出。技术难点已经知道了,接下来就是针对这一难点提供自己的解决思路即可。 300w数据的导出解决思路: 注意: 1、我们需要计算Sheet个数,以及循环写入次数。特别是最后一个Sheet的写入次数 因为你不知道最后一个Sheet选哟写入多少数据,可能是100w,也可能是25w因为我们这里的300w只是模拟数据,有可能导出的数据比300w多也可能少 2、我们需要计算写入次数,因为我们使用的分页查询,所以需要注意写入的次数。 其实查询数据库多少次就是写入多少次来源:blog.csdn.net/weixin_44848900/article/details/117701981
前景
1 传统POI的的版本优缺点比较
HSSFWorkbook
XSSFWorkbook
HSSFWorkbook
的65535行局限,是为了针对Excel2007版本的1048576行,16384列,最多可以导出104w条数据;
SXSSFWorkbook
sheet.clone()
方法将不再支持,还是因为持久化的原因;
2 使用方式哪种看情况
HSSFWorkbook
或者 XSSFWorkbook
都行;SXSSFWorkbook
;XSSFWorkbook
配合进行分批查询,分批写入Excel的方式来做;3 百万数据导入导出(正菜)
select * from tableName
肯定不行,一下子查出来300w条数据肯定会很慢;针对1 :
针对2:
针对3:
针对4:
针对5:
针对6:
3.1 EasyExcel 简介
3.2 300w数据导出
//导出逻辑代码
public void dataExport300w(HttpServletResponse response) {
{
OutputStream outputStream = null;
try {
long startTime = System.currentTimeMillis();
System.out.println("导出开始时间:" + startTime);
outputStream = response.getOutputStream();
ExcelWriter writer = new ExcelWriter(outputStream, ExcelTypeEnum.XLSX);
String fileName = new String(("excel100w").getBytes(), "UTF-8");
//title
Table table = new Table(1);
List<List<String>> titles = new ArrayList<List<String>>();
titles.add(Arrays.asList("onlineseqid"));
titles.add(Arrays.asList("businessid"));
titles.add(Arrays.asList("becifno"));
titles.add(Arrays.asList("ivisresult"));
titles.add(Arrays.asList("createdby"));
titles.add(Arrays.asList("createddate"));
titles.add(Arrays.asList("updateby"));
titles.add(Arrays.asList("updateddate"));
titles.add(Arrays.asList("risklevel"));
table.setHead(titles);
//模拟统计查询的数据数量这里模拟100w
int count = 3000001;
//记录总数:实际中需要根据查询条件进行统计即可
Integer totalCount = actResultLogMapper.findActResultLogByCondations(count);
//每一个Sheet存放100w条数据
Integer sheetDataRows = ExcelConstants.PER_SHEET_ROW_COUNT;
//每次写入的数据量20w
Integer 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));
//开始分批查询分次写入
//注意这次的循环就需要进行嵌套循环了,外层循环是Sheet数目,内层循环是写入次数
List<List<String>> dataList = new ArrayList<>();
for (int i = 0; i < sheetNum; i++) {
//创建Sheet
Sheet sheet = new Sheet(i, 0);
sheet.setSheetName("测试Sheet1" + i);
//循环写入次数: j的自增条件是当不是最后一个Sheet的时候写入次数为正常的每个Sheet写入的次数,如果是最后一个就需要使用计算的次数lastSheetWriteCount
for (int j = 0; j < (i != sheetNum - 1 ? oneSheetWriteCount : lastSheetWriteCount); j++) {
//集合复用,便于GC清理
dataList.clear();
//分页查询一次20w
PageHelper.startPage(j + 1 + oneSheetWriteCount * i, writeDataRows);
List<ActResultLog> reslultList = actResultLogMapper.findByPage100w();
if (!CollectionUtils.isEmpty(reslultList)) {
reslultList.forEach(item -> {
dataList.add(Arrays.asList(item.getOnlineseqid(), item.getBusinessid(), item.getBecifno(), item.getIvisresult(), item.getCreatedby(), Calendar.getInstance().getTime().toString(), item.getUpdateby(), Calendar.getInstance().getTime().toString(), item.getRisklevel()));
});
}
//写数据
writer.write0(dataList, sheet, table);
}
}
// 下载EXCEL
response.setHeader("Content-Disposition", "attachment;filename=" + new String((fileName).getBytes("gb2312"), "ISO-8859-1") + ".xlsx");
response.setContentType("multipart/form-data");
response.setCharacterEncoding("utf-8");
&nbs