先搞个工具类:
public class EasyExcelUtil {
/**
* @param response
* @param exportTypeEnum
* @param fileName
* @return java.io.OutputStream
* @Author JH
* @Description 设置导出响应请求头
* @Date 13:57 2022/10/27
**/
public static OutputStream getOutputStream(HttpServletResponse response, ExportTypeEnum exportTypeEnum, String fileName) throws IOException {
response.setContentType(exportTypeEnum.getContentType());
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
response.setCharacterEncoding(Constants.UTF8);
// 这里URLEncoder.encode可以防止中文乱码
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + new String((fileName + exportTypeEnum.getFileExtName()).getBytes()));
response.setHeader("download-filename", fileName + exportTypeEnum.getFileExtName());
return response.getOutputStream();
}
/**
* @param templateEmun
* @return java.io.InputStream
* @Author JH
* @Description 获取模板 的 InputStream
* @Date 14:09 2022/10/27
**/
public static InputStream getTemplate(TemplateEmun templateEmun) {
String templateName = templateEmun.getName() + templateEmun.getExportTypeEnum().getFileExtName();
ClassLoader classLoader = EasyExcelUtil.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("/template/" + templateName);
if (Objects.isNull(inputStream)) {
inputStream = classLoader.getResourceAsStream("template/" + templateName);
}
/* if (Objects.isNull(inputStream)) {
throw new TemplateNotFoundException("下载模板没有找到,请核对后重试");
}*/
Assert.isTrue(StringUtils.isNotNull(inputStream), "下载模板没有找到,请核对后重试");
return inputStream;
}
/**
*
* @Author ljy
* @Description 优化后的导出方法,支持高性能导出
* @Date 16:02 2024/9/9
* @param response
* @param templateEmun
* @param fileName
* @param exportList
**/
public static void exportExcelBetter(HttpServletResponse response, TemplateEmun templateEmun, String fileName, List exportList) throws IOException {
// 使用流式写入
OutputStream outputStream = getOutputStream(response, templateEmun.getExportTypeEnum(), fileName);
// 关闭自动流关闭
ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(outputStream)
.registerConverter(new ExcelLocalDateTimeConverter())
.registerConverter(new ExcelLocalDateConverter())
.withTemplate(getTemplate(templateEmun))
.autoCloseStream(Boolean.FALSE);
ExcelWriter excelWriter = excelWriterBuilder.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
// 分批写入数据
// 每批写入的数量
int batchSize = 5000;
for (int i = 0; i < exportList.size(); i += batchSize) {
int end = Math.min(i + batchSize, exportList.size());
List<?> batchList = exportList.subList(i, end);
excelWriter.fill(batchList, fillConfig, writeSheet);
}
// 手动关闭流
excelWriter.finish();
outputStream.close();
}
/**
*
* @Author ljy
* @Description
* @Date 14:40 2024/9/12
* @param file
* @param templateEmun
* @param fileName
* @param exportList
**/
public static void exportExcelFile(File file, TemplateEmun templateEmun, String fileName, List exportList) throws IOException {
// 使用流式写入
OutputStream outputStream = new FileOutputStream(file);
// 关闭自动流关闭
ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(outputStream)
.registerConverter(new ExcelLocalDateTimeConverter())
.registerConverter(new ExcelLocalDateConverter())
.withTemplate(getTemplate(templateEmun))
.autoCloseStream(Boolean.FALSE);
ExcelWriter excelWriter = excelWriterBuilder.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
// 分批写入数据
/* int batchSize = 5000;
for (int i = 0; i < exportList.size(); i += batchSize) {
int end = Math.min(i + batchSize, exportList.size());
List<?> batchList = exportList.subList(i, end);
}*/
excelWriter.fill(exportList, fillConfig, writeSheet);
// 手动关闭流
excelWriter.finish();
outputStream.close();
}
/**
* @param response
* @param templateEmun
* @param fileName 下载文件名
* @param exportList 导出的数据
* @Author JH
* @Description
* @Date 15:56 2022/10/27
**/
public static void exportExcel(HttpServletResponse response, TemplateEmun templateEmun, String fileName, List exportList) throws IOException {
exportExcel(response, null, templateEmun, fileName, exportList);
}
/**
* @param response
* @param writeHandler 单元格处理类
* @param templateEmun
* @param fileName 下载文件名
* @param exportList 导出的数据
* @Author JH
* @Description
* @Date 15:56 2022/10/27
**/
public static void exportExcel(HttpServletResponse response, WriteHandler writeHandler, TemplateEmun templateEmun, String fileName, List exportList) throws IOException {
OutputStream outputStream = getOutputStream(response, templateEmun.getExportTypeEnum(), fileName);
ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(outputStream).registerConverter(new ExcelLocalDateTimeConverter()).registerConverter(new ExcelLocalDateConverter())
.withTemplate(getTemplate(templateEmun))
.autoCloseStream(Boolean.TRUE);
if (writeHandler != null) {
excelWriterBuilder.registerWriteHandler(writeHandler);
}
ExcelWriter excelWriter = excelWriterBuilder.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(exportList, fillConfig, writeSheet);
excelWriter.finish();
}
/**
* @param inputstream
* @param tclass
* @param rowNum 表头所在行
* @return java.util.List<T>
* @Author JH
* @Description
* @Date 16:50 2022/10/27
**/
public static <T> List<T> readExcel(InputStream inputstream, Class<T> tclass, int rowNum) throws InstantiationException, IllegalAccessException {
return EasyExcel.read(inputstream, tclass, new ReadExcelListener(tclass)).head(tclass).registerConverter(new ExcelLocalDateTimeConverter())
.sheet().headRowNumber(rowNum).doReadSync();
}
/**
*
* @Author ljy
* @Description 分批次读取
* @Date 10:37 2024/9/11
* @param inputstream
* @param tclass
* @param rowNum
* @return java.util.List<T>
**/
public static <T> List<T> readExcelBatch(InputStream inputstream, Class<T> tclass, int rowNum) throws InstantiationException, IllegalAccessException {
List<T> resultList = new ArrayList<>();
List<T> all = new ArrayList<>();
EasyExcel.read(inputstream, tclass, new ReadExcelListener<T>(tclass) {
@Override
public void invoke(T data, AnalysisContext context) {
resultList.add(data);
if (resultList.size() >= 5000) {
// 处理当前批次数据
processBatch(resultList, all);
resultList.clear();
}
}
})
.head(tclass)
.registerConverter(new ExcelLocalDateTimeConverter())
.sheet()
.headRowNumber(rowNum)
.doRead();
// 处理剩余数据
if (!resultList.isEmpty()) {
processBatch(resultList, all);
}
return all;
}
private static <T> void processBatch(List<T> resultList, List<T> all) {
all.addAll(resultList);
}
/**
* @Author lx
* @Date 上午10:01 2024/8/5
* @param inputstream
* @param tclass
* @param headerRowNumber 表头所在行
* @param startRow 数据所在行
* @return java.util.List<T>
**/
public static <T> List<T> readExcel(InputStream inputstream, Class<T> tclass, int headerRowNumber, int startRow) throws InstantiationException, IllegalAccessException {
ReadExcelListener readExcelListener = new ReadExcelListener(tclass, headerRowNumber, startRow);
EasyExcel.read(inputstream).headRowNumber(headerRowNumber).head(tclass).registerReadListener(readExcelListener).registerConverter(new ExcelLocalDateTimeConverter())
.sheet().doReadSync();
return readExcelListener.getList();
}
/**
* @param inputstream
* @param tclass
* @param rowNum 表头所在行
* @param sheetName sheet名称
* @return java.util.List<T>
* @Author JH
* @Description
* @Date 16:50 2022/10/27
**/
public static <T> List<T> readExcelSheet(InputStream inputstream, Class<T> tclass, int rowNum, String sheetName) throws InstantiationException, IllegalAccessException {
return EasyExcel.read(inputstream).headRowNumber(rowNum).head(tclass).registerReadListener(new ReadExcelListener(tclass, rowNum - 1)).registerConverter(new ExcelLocalDateTimeConverter())
.sheet(sheetName).doReadSync();
}
现在有一个这样的需求:根据指定一个所属地市的所有电缆数据导出成文件(电缆的数据量达到10万级别哦)
对于这个需求要注意的地方是:
具体实现的思路,先查询出指定所属地市数据,然后再excelWriter.fill填充到excel表格,最后响应。
对于庞大的数据量接口要保证接口响应不能太久,要避免OOM
因此在实现过程中可以采用分批次查询,分批次写入excel表格,最后将每个批次的excel文件放到一个压缩文件夹里面,最后一起再一起响应。
分析工具类里面的方法:方法的主要目的是将一个数据列表导出为一个 Excel 文件,使用指定的模板和格式化选项。它通过流式写入的方式高效地处理数据,并确保在完成后正确关闭资源。
public static void exportExcelFile(File file, TemplateEmun templateEmun, String fileName, List exportList) throws IOException {
// 使用流式写入
OutputStream outputStream = new FileOutputStream(file);
// 关闭自动流关闭
ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(outputStream)
.registerConverter(new ExcelLocalDateTimeConverter())
.registerConverter(new ExcelLocalDateConverter())
.withTemplate(getTemplate(templateEmun))
.autoCloseStream(Boolean.FALSE);
ExcelWriter excelWriter = excelWriterBuilder.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(exportList, fillConfig, writeSheet);
// 手动关闭流
excelWriter.finish();
outputStream.close();
}
-
创建输出流:
OutputStream outputStream = new FileOutputStream(file);
- 使用
FileOutputStream
创建一个输出流,以便将数据写入指定的 Excel 文件。
- 使用
-
构建 ExcelWriter:
ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(outputStream) .registerConverter(new ExcelLocalDateTimeConverter()) .registerConverter(new ExcelLocalDateConverter()) .withTemplate(getTemplate(templateEmun)) .autoCloseStream(Boolean.FALSE);
- 使用
EasyExcel
库的write
方法创建一个ExcelWriterBuilder
。 - 注册了两个转换器:
ExcelLocalDateTimeConverter
和ExcelLocalDateConverter
,用于处理日期和时间格式。 - 使用指定的模板(通过
getTemplate(templateEmun)
获取)来格式化 Excel 文件。 - 设置
autoCloseStream
为false
,表示在写入完成后不会自动关闭输出流。
- 使用
-
构建写入工作表:
WriteSheet writeSheet = EasyExcel.writerSheet().build();
- 创建一个
WriteSheet
对象,表示要写入的工作表。
- 创建一个
-
配置填充设置:
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
- 创建一个
FillConfig
对象,设置forceNewRow
为true
,表示在填充数据时强制换行。
- 创建一个
-
填充数据:
excelWriter.fill(exportList, fillConfig, writeSheet);
- 使用
excelWriter
将exportList
中的数据填充到指定的工作表中。
- 使用
-
关闭流:
excelWriter.finish(); outputStream.close();
- 调用
finish()
方法完成写入操作,并手动关闭输出流,确保资源被释放
- 调用
具体业务使用:
/**
*
* @Author ljy
* @Description 数据导出成文件
* @Date 14:54 2024/9/12
* @param city 所属地市
* @param type 选择要到处的数据类型
* @param response 响应体
**/
@GetMapping("/export/all")
public void exportDataToExcelAll(@RequestParam(value = "city", required = true)String city,
@RequestParam(value = "type", required = true)String type,
HttpServletResponse response) throws IOException {
accountCableService.exportDataToExcelAll(city, type, response);
}
public void exportDataToExcelAll(String city, String type, HttpServletResponse response) throws IOException {
ExcelExportObjectEnum exportObjectEnum = ExcelExportObjectEnum.ofByCode(type);
Assert.notNull(exportObjectEnum, "没找到该类型对应的模板");
switch (exportObjectEnum){
case CABLE:
commonService.exportDataToExcelCable(city, response);
break;
case CABLE_SECTION:
commonService.exportDataToExcelCableSection(city, response);
break;
case CABLE_TERMINAL:
commonService.exportDataToExcelCableTerminal(city, response);
break;
case LINE_JOINT:
commonService.exportDataToExcelCableLineJoint(city, response);
break;
case LINE:
commonService.exportDataToExcelCableLine(city, response);
break;
case DKX:
commonService.exportDataToExcelDkx(city, response);
break;
default:
}
public void exportDataToExcelCable(String city, HttpServletResponse response) throws IOException {
// 当前页码
int pageNum = 1;
int j = 0;
int pageSize = 8000;
ExportExcelRequestVO vo = new ExportExcelRequestVO();
vo.setCity(city);
vo.setPageNum(pageNum);
vo.setPageSize(pageSize);
PageResult<AccountCable> pageResult = iAccountCableCrud.findCableListAll(vo);
long total = pageResult.getTotal();
int pages = (int) Math.ceil((double) total / pageSize);
// 创建一个临时文件用于存储压缩文件
File zipFile = File.createTempFile("cables", ".zip");
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
for (int i = 1; i <= pages; i++) {
vo.setPageNum(i);
List<AccountCable> list = iAccountCableCrud.findCableListAll(vo).getData();
// 创建每个Excel文件
File tempExcelFile = File.createTempFile("cable_page_" + j, ".xlsx");
EasyExcelUtil.exportExcelFile(tempExcelFile, TemplateEmun.CABLE_ACCOUNT_ALL_TEMPLATE, "电缆记录表", list);
// 将Excel文件添加到压缩文件中
try (FileInputStream fis = new FileInputStream(tempExcelFile)) {
ZipEntry zipEntry = new ZipEntry(tempExcelFile.getName());
zos.putNextEntry(zipEntry);
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) >= 0) {
zos.write(buffer, 0, length);
}
zos.closeEntry();
}
// 删除临时Excel文件
tempExcelFile.delete();
j++;
}
}
// 设置响应头并发送压缩文件
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=cables.zip");
try (FileInputStream fis = new FileInputStream(zipFile);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) >= 0) {
os.write(buffer, 0, length);
}
}
// 删除临时压缩文件
zipFile.delete();
}
分析核心业务方法exportDataToExcelCable():
这个方法实现了从数据库中提取电缆记录数据,并将其导出为一个包含多个 Excel 文件的 ZIP 文件,供用户下载。
-
获取数据:
- 使用
iAccountCableCrud.findCableListAll(vo)
方法获取电缆记录的总数和分页信息。
- 使用
-
创建临时 ZIP 文件:
- 创建一个临时 ZIP 文件,用于存储生成的 Excel 文件。
-
分页处理:
- 根据总记录数和每页大小计算总页数。
- 循环遍历每一页,获取电缆记录列表。
-
生成 Excel 文件:
- 为每一页创建一个临时 Excel 文件,并使用
EasyExcelUtil.exportExcelFile
方法将数据写入该文件。
- 为每一页创建一个临时 Excel 文件,并使用
-
将 Excel 文件添加到 ZIP:
- 将生成的 Excel 文件读取并写入到 ZIP 文件中。
-
清理临时文件:
- 删除每个生成的临时 Excel 文件。
-
设置响应头并发送 ZIP 文件:
- 设置 HTTP 响应的内容类型和文件名。
- 将 ZIP 文件的内容写入 HTTP 响应流中,发送给客户端。
-
删除临时 ZIP 文件:
- 在完成文件发送后,删除临时 ZIP 文件以释放资源