导读


由于项目需要,处理百万级数据导出问题。

使用



导入依赖

  1. <!-- 文件导出-->
  2. <dependency>
  3. <groupId>org.apache.poi</groupId>
  4. <artifactId>poi</artifactId>
  5. <version>3.17</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.apache.poi</groupId>
  9. <artifactId>poi-ooxml</artifactId>
  10. <version>3.17</version>
  11. </dependency>

**

工具类

PoiUtil导出工具类

  1. package com.aa.utils;
  2. import com.aa.excel.ExcelConstant;
  3. import com.aa.excel.WriteExcelDataDelegated;
  4. import org.apache.poi.xssf.streaming.SXSSFCell;
  5. import org.apache.poi.xssf.streaming.SXSSFRow;
  6. import org.apache.poi.xssf.streaming.SXSSFSheet;
  7. import org.apache.poi.xssf.streaming.SXSSFWorkbook;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import javax.servlet.http.HttpServletResponse;
  11. import java.io.FileOutputStream;
  12. import java.io.IOException;
  13. import java.io.OutputStream;
  14. import java.util.ArrayList;
  15. import java.util.Date;
  16. /**
  17. * @Author hyanchao
  18. * @create 2019/11/19 17:19
  19. * @About POI导出工具类
  20. */
  21. public class PoiUtil {
  22. private final static Logger logger = LoggerFactory.getLogger(PoiUtil.class);
  23. /**
  24. * 初始化EXCEL(sheet个数和标题)
  25. *
  26. * @param totalRowCount 总记录数
  27. * @param titles 标题集合
  28. * @return XSSFWorkbook对象
  29. */
  30. public static SXSSFWorkbook initExcel(Integer totalRowCount, String[] titles) {
  31. // 在内存当中保持 100 行 , 超过的数据放到硬盘中在内存当中保持 100 行 , 超过的数据放到硬盘中
  32. SXSSFWorkbook wb = new SXSSFWorkbook(100);
  33. Integer sheetCount = ((totalRowCount % ExcelConstant.PER_SHEET_ROW_COUNT == 0) ?
  34. (totalRowCount / ExcelConstant.PER_SHEET_ROW_COUNT) : (totalRowCount / ExcelConstant.PER_SHEET_ROW_COUNT + 1));
  35. // 根据总记录数创建sheet并分配标题
  36. for (int i = 0; i < sheetCount; i++) {
  37. SXSSFSheet sheet = wb.createSheet("sheet" + (i + 1));
  38. SXSSFRow headRow = sheet.createRow(0);
  39. for (int j = 0; j < titles.length; j++) {
  40. SXSSFCell headRowCell = headRow.createCell(j);
  41. headRowCell.setCellValue(titles[j]);
  42. }
  43. }
  44. return wb;
  45. }
  46. /**
  47. * 下载EXCEL到本地指定的文件夹
  48. *
  49. * @param wb EXCEL对象SXSSFWorkbook
  50. * @param exportPath 导出路径
  51. */
  52. public static void downLoadExcelToLocalPath(SXSSFWorkbook wb, String exportPath) {
  53. FileOutputStream fops = null;
  54. try {
  55. fops = new FileOutputStream(exportPath);
  56. wb.write(fops);
  57. } catch (Exception e) {
  58. e.printStackTrace();
  59. } finally {
  60. if (null != wb) {
  61. try {
  62. wb.dispose();
  63. } catch (Exception e) {
  64. e.printStackTrace();
  65. }
  66. }
  67. if (null != fops) {
  68. try {
  69. fops.close();
  70. } catch (Exception e) {
  71. e.printStackTrace();
  72. }
  73. }
  74. }
  75. }
  76. /**
  77. * 下载EXCEL到浏览器
  78. *
  79. * @param wb EXCEL对象XSSFWorkbook
  80. * @param response
  81. * @param fileName 文件名称
  82. * @throws IOException
  83. */
  84. public static void downLoadExcelToWebsite(SXSSFWorkbook wb, HttpServletResponse response, String fileName) throws IOException {
  85. response.setHeader("Content-disposition", "attachment; filename="
  86. + new String((fileName + ".xlsx").getBytes("utf-8"), "ISO8859-1"));//设置下载的文件名
  87. OutputStream outputStream = null;
  88. try {
  89. outputStream = response.getOutputStream();
  90. wb.write(outputStream);
  91. } catch (Exception e) {
  92. e.printStackTrace();
  93. } finally {
  94. if (null != wb) {
  95. try {
  96. wb.dispose();
  97. } catch (Exception e) {
  98. e.printStackTrace();
  99. }
  100. }
  101. if (null != outputStream) {
  102. try {
  103. outputStream.close();
  104. } catch (Exception e) {
  105. e.printStackTrace();
  106. }
  107. }
  108. }
  109. }
  110. /**
  111. * 导出Excel到本地指定路径
  112. *
  113. * @param totalRowCount 总记录数
  114. * @param titles 标题
  115. * @param exportPath 导出路径
  116. * @param writeExcelDataDelegated 向EXCEL写数据/处理格式的委托类 自行实现
  117. * @throws Exception
  118. */
  119. public static final void exportExcelToLocalPath(Integer totalRowCount, String[] titles, String exportPath, WriteExcelDataDelegated writeExcelDataDelegated) throws Exception {
  120. logger.info("开始导出:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));
  121. // 初始化EXCEL
  122. SXSSFWorkbook wb = PoiUtil.initExcel(totalRowCount, titles);
  123. // 调用委托类分批写数据
  124. int sheetCount = wb.getNumberOfSheets();
  125. for (int i = 0; i < sheetCount; i++) {
  126. SXSSFSheet eachSheet = wb.getSheetAt(i);
  127. for (int j = 1; j <= ExcelConstant.PER_SHEET_WRITE_COUNT; j++) {
  128. int currentPage = i * ExcelConstant.PER_SHEET_WRITE_COUNT + j;
  129. int pageSize = ExcelConstant.PER_WRITE_ROW_COUNT;
  130. int startRowCount = (j - 1) * ExcelConstant.PER_WRITE_ROW_COUNT + 1;
  131. int endRowCount = startRowCount + pageSize - 1;
  132. writeExcelDataDelegated.writeExcelData(eachSheet, startRowCount, endRowCount, currentPage, pageSize);
  133. }
  134. }
  135. // 下载EXCEL
  136. PoiUtil.downLoadExcelToLocalPath(wb, exportPath);
  137. logger.info("导出完成:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));
  138. }
  139. /**
  140. * 导出Excel到浏览器
  141. *
  142. * @param response
  143. * @param totalRowCount 总记录数
  144. * @param fileName 文件名称
  145. * @param titles 标题
  146. * @param writeExcelDataDelegated 向EXCEL写数据/处理格式的委托类 自行实现
  147. * @throws Exception
  148. */
  149. public static final void exportExcelToWebsite(HttpServletResponse response, Integer totalRowCount, String fileName, String[] titles, WriteExcelDataDelegated writeExcelDataDelegated) throws Exception {
  150. logger.info("开始导出:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));
  151. // 初始化EXCEL
  152. SXSSFWorkbook wb = PoiUtil.initExcel(totalRowCount, titles);
  153. int num = totalRowCount % ExcelConstant.PER_WRITE_ROW_COUNT == 0 ? totalRowCount / ExcelConstant.PER_WRITE_ROW_COUNT : (totalRowCount / ExcelConstant.PER_WRITE_ROW_COUNT) + 1;
  154. // 调用委托类分批写数据
  155. int sheetCount = wb.getNumberOfSheets();
  156. //判断是需要几个sheet
  157. //假设为13,最终结果要求
  158. //0,10,20,30,40
  159. //50,60,70,80,90
  160. //100,110,120
  161. ArrayList<Integer> list = new ArrayList<>();
  162. if (num <= ExcelConstant.PER_WRITE_ROW_COUNT) {
  163. list.add(num);
  164. } else {
  165. for (int i = 0; i < num / ExcelConstant.PER_WRITE_ROW_COUNT; i++) {
  166. list.add(ExcelConstant.PER_WRITE_ROW_COUNT);
  167. }
  168. if (num % ExcelConstant.PER_WRITE_ROW_COUNT != 0) {
  169. list.add(num % ExcelConstant.PER_WRITE_ROW_COUNT);
  170. }
  171. }
  172. //此list.size相当于sheet的页数。
  173. for (int i = 0; i < list.size(); i++) {
  174. SXSSFSheet eachSheet = wb.getSheetAt(i);
  175. for (int j = 1; j <= list.get(i); j++) {
  176. //获取当前页等信息
  177. int currentPage = ((i * ExcelConstant.PER_WRITE_ROW_COUNT) + j - 1) * ExcelConstant.PER_WRITE_ROW_COUNT;
  178. int pageSize = ExcelConstant.PER_WRITE_ROW_COUNT;
  179. int startRowCount = (j - 1) * ExcelConstant.PER_WRITE_ROW_COUNT + 1;
  180. int endRowCount = startRowCount + pageSize - 1;
  181. //调用方法从数据库查询,并且写入到Excel中
  182. writeExcelDataDelegated.writeExcelData(eachSheet, startRowCount, endRowCount, currentPage, pageSize);
  183. }
  184. }
  185. // 下载EXCEL
  186. PoiUtil.downLoadExcelToWebsite(wb, response, fileName);
  187. logger.info("导出完成:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));
  188. }
  189. }

ExcelConstant常量类

  1. /**
  2. * @create 2019/11/19 17:42
  3. * @About EXCEL常量类
  4. */
  5. public class ExcelConstant {
  6. /**
  7. * 每个sheet存储的记录数 100W
  8. */
  9. public static final Integer PER_SHEET_ROW_COUNT = 100000;
  10. /**
  11. * 每次向EXCEL写入的记录数(查询每页数据大小) 20W
  12. */
  13. public static final Integer PER_WRITE_ROW_COUNT = 20000;
  14. /**
  15. * 每个sheet的写入次数 5
  16. */
  17. public static final Integer PER_SHEET_WRITE_COUNT = PER_SHEET_ROW_COUNT / PER_WRITE_ROW_COUNT;
  18. }

WriteExcelDataDelegated

  1. import org.apache.poi.xssf.streaming.SXSSFSheet;
  2. /**
  3. * @create 2019/11/19 17:45
  4. * @About EXCEL写数据委托类
  5. */
  6. public interface WriteExcelDataDelegated {
  7. /**
  8. * EXCEL写数据委托类 针对不同的情况自行实现
  9. *
  10. * @param eachSheet 指定SHEET
  11. * @param startRowCount 开始行
  12. * @param endRowCount 结束行
  13. * @param currentPage 分批查询开始页
  14. * @param pageSize 分批查询数据量
  15. * @throws Exception
  16. */
  17. public abstract void writeExcelData(SXSSFSheet eachSheet, Integer startRowCount, Integer endRowCount, Integer currentPage, Integer pageSize) throws Exception;
  18. }

实现层使用

  1. //userInfoQuery传入的参数
  2. @Override
  3. public List<userInfoVO> getJcFullCodeListExports(userInfoQuery userInfoQuery, HttpServletResponse resp) {
  4. // 总记录数
  5. Integer totalRowCount = userInfoQuery.getTotal();
  6. // 导出EXCEL文件名称
  7. String filaName = "用户表导出";
  8. String[] titles = {"名称", "编号", "编码", "班级"};
  9. // 开始导入
  10. try {
  11. PoiUtil.exportExcelToWebsite(resp, totalRowCount, filaName, titles, new WriteExcelDataDelegated() {
  12. @Override
  13. public void writeExcelData(SXSSFSheet eachSheet, Integer startRowCount, Integer endRowCount, Integer currentPage, Integer pageSize) throws Exception {
  14. userInfoQuery.setPage(currentPage);//设置当前页
  15. userInfoQuery.setSize(pageSize);//设置页面查询条数
  16. //根据条件从数据库查询数据
  17. List<userInfoVO> exportVOList = statisticalAnalysisMapper.getJcFullCodeInfoList(userInfoQuery);
  18. //绑定数据
  19. if (!CollectionUtils.isEmpty(exportVOList)) {
  20. // -------------- 这一块变量照着抄就行 强迫症 后期也封装起来 ----------------------
  21. for (int i = startRowCount; i <= endRowCount; i++) {
  22. SXSSFRow eachDataRow = eachSheet.createRow(i);
  23. if ((i - startRowCount) < exportVOList.size()) {
  24. userInfoVO eachUserVO = exportVOList.get(i - startRowCount);
  25. // --------- 这一块变量照着抄就行 强迫症 后期也封装起来 -----------------------
  26. int column = 0;
  27. eachDataRow.createCell(column++).setCellValue(StringUtils.defaultString(eachUserVO.getQame()));
  28. eachDataRow.createCell(column++).setCellValue(StringUtils.defaultString(eachUserVO.getACode()));
  29. eachDataRow.createCell(column++).setCellValue(StringUtils.defaultString(eachUserVO.getQCode()));
  30. eachDataRow.createCell(column++).setCellValue(StringUtils.defaultString(eachUserVO.getOName()));
  31. }
  32. }
  33. }
  34. }
  35. });
  36. } catch (Exception e) {
  37. e.printStackTrace();
  38. }
  39. return null;
  40. }

前端页面JS

  1. //导出指定数据
  2. $scope.exportByParams = function () {
  3. $('.load-bg').fadeIn(200);
  4. var req_data = $.extend({}, $scope.form, $scope.page);
  5. if (req_data['total'] != 0) {
  6. var params = Object.keys(req_data).map(function (key) {
  7. return encodeURIComponent(key) + "=" + encodeURIComponent(req_data[key]);
  8. }).join("&");
  9. var URLINFO = '';
  10. if ($scope.form.type === '1') {
  11. URLINFO = "/statistical/getUserListExports";
  12. } else if ($scope.form.type === '2') {
  13. URLINFO = "/statistical/getClassInfoListExports";
  14. }
  15. window.location.href = URLINFO + "?" + params;
  16. alert("点击确定后请耐心等待片刻,约2分钟,待下载完成后可继续操作!");
  17. $('.load-bg').fadeOut(200);
  18. } else {
  19. $('.load-bg').fadeOut(200);
  20. alert("无数据!")
  21. $('.details_export').fadeOut(200);
  22. }
  23. };
  • 页面按钮

image.png

  • 导出文件

image.png

END


搞定~