easy-excel批量导出数据

easy-excel批量导出

前言

最近遇到个用户数据批量导出excel的需求,第一次看到这个需求大家第一时间想到的应该大多都是easy-excel这个框架吧,哈哈,我第一时间想到的也是这个框架。
但是对于少量的数据,比如有10个用户这样的数据肯定没有啥太大的问题,但是对于百万级数据可能就会有问题,总不能一次性吧100w数据从数据库里面查出来吧,这无疑是sql炼狱,同时也面临着oom的风险,面对一个线上服务这显然是不理智的。

解决思路

面对如上情况大多数情况的解决思路都是分批量查询数据,比如100w数据每次查询10w或者1w分多次查询出来,以时间换空间。面对如此情况easy-excel版本可能支持的不是很好,那么下面就提供一下该场景的解决方案。

实际解决

实际在解决该需求时还要考虑到excel的最大可承载数据量为 1048576行(该数据由百度查询得到,也可自行下拉excel表格到最底端查看),但是如果数据超过这些行数怎么办,直接报错?显然这对于一个功能来说是不可取的,这时可以设定一个边界值来指定超过该边界值后导出多个excel,但是把多个excel直接导出到服务器上,把地址返回给前端,让前端拿着地址去web服务器上面下载,也不是一个可取的方法。但是如何能让多个excel作为一个文件导出呢?方案到了这大家可能都会想到了,那就是.zip这种压缩文件的形式。现在有了思路了,那么说干就干,下面我们直接来看一下代码。

/**
 * 导出excel工具
 * @author wangshaoyu
 * @date 2022/11/20
 */
public class ExcelUtils {
	/**
	 * 导出多个sheet到多个excel文件,并压缩到一个zip文件
	 * @param zipFilename 下载时压缩包名称
	 * @param response 请求返回流
	 * @param header excel实体类类型
	 * @param splitTimes 次数分界,excel数据导入多少次后创建下一个excel文件
	 * @param supplier 供给侧函数式接口,提供get方法,该方法可自定义
	 * @return void   
	 */
	public static <T> void exportZip(String zipFilename, HttpServletResponse response, Class<T> header, int splitTimes, Supplier<List<T>> supplier) {
		if (zipFilename == null || zipFilename.isEmpty()) {
			zipFilename = "export";
		} else if (zipFilename.toLowerCase(Locale.ROOT).endsWith(".zip")) {
			zipFilename = zipFilename.substring(0, zipFilename.length() - 3);
		}
		try {
			// 这里URLEncoder.encode可以防止中文乱码
			String downFileName = URLEncoder.encode(zipFilename, "utf-8");
			response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + downFileName + ".zip");
			response.setContentType("application/x-msdownload");
			response.setCharacterEncoding("utf-8");
			//开始存入
			try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
				List<T> exportData = null;
				int count = 0;
				int fileIndex = 1;
				ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
				ExcelWriterBuilder builder = EasyExcel.write(outputStream).autoCloseStream(false)
					.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
				ExcelWriter excelWriter = builder.build();
				zipOut.putNextEntry(new ZipEntry(String.format("%s-%d.xls", zipFilename, fileIndex)));
				WriteSheet writeSheet = EasyExcel.writerSheet(zipFilename).head(header).build();
				exportData = supplier.get();
				if (Func.isEmpty(exportData)) {
					throw new ServiceException("导出数据为空");
				}
				while (Func.isNotEmpty(exportData)) {
					count += 1;
					excelWriter.write(exportData, writeSheet);
					exportData = supplier.get();
					//如果到了分割点,则证明该excel文件已经到达存储临界值,直接保存该文件
					if (count % splitTimes == 0) {
						fileIndex += 1;
						excelWriter.finish();
						outputStream.writeTo(zipOut);
						zipOut.closeEntry();
						//如果导出数据不为空则证明存在下一个excel文件,则在压缩包中新增一个excel文件
						if (Func.isNotEmpty(exportData)) {
							outputStream = new ByteArrayOutputStream();
							builder = EasyExcel.write(outputStream).autoCloseStream(false)
								// 自动适配
								.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
							excelWriter = builder.build();
							zipOut.putNextEntry(new ZipEntry(String.format("%s-%d.xls", zipFilename, fileIndex)));
							writeSheet = EasyExcel.writerSheet(zipFilename).head(header).build();
						}
					}
				}

				//count % splitTimes == 0 代表本次导出刚好导出整数个文件,且每个文件中都有规定大小的数据
				if (count % splitTimes == 0) {
					return;
				}
				excelWriter.finish();
				outputStream.writeTo(zipOut);
				zipOut.closeEntry();
			} catch (IOException e) {
				throw new RuntimeException("导出Excel异常", e);
			}
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("导出excel异常");
		}
	}

	/**
	 * 导出单个excel文件,适用于大量数据但是未超过excel最大限制的情况
	 * @param fileName 文件名称
	 * @param response 请求返回数据流
	 * @param header excel实体类类型
	 * @param supplier 供给侧函数
	 * @return void   
	 * */
	public static <T> void exportExcel(String fileName, HttpServletResponse response, Class<T> header, Supplier<List<T>> supplier) {
		try {
			if (fileName == null || fileName.isEmpty()) {
				fileName = "export";
			} else if (fileName.toLowerCase(Locale.ROOT).endsWith(".xls")) {
				fileName = fileName.substring(0, fileName.length() - 3);
			} else if (fileName.toLowerCase(Locale.ROOT).endsWith(".xlsx")) {
				fileName = fileName.substring(0, fileName.length() - 4);
			}
			response.setContentType("application/vnd.ms-excel");
			response.setCharacterEncoding("utf-8");
			String excelName = URLEncoder.encode(fileName, "utf-8");
			response.setHeader("Content-disposition", "attachment;filename=" + excelName + ".xls");
			ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).autoCloseStream(false)
				.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();
			WriteSheet writeSheet = EasyExcel.writerSheet(fileName).head(header).build();
			List<T> exportData = null;
			while (Func.isNotEmpty(exportData = supplier.get())) {
				excelWriter.write(exportData, writeSheet);
			}
			excelWriter.finish();
		} catch (Exception e) {
			throw new ServiceException("导出excel异常");
		}
	}
}

结语

对于工具类使用、代码bug、优化问题欢迎评论提出,作者会努力优化,耐心回答。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值