easyExcel分页导出数据实现

easyExcel分页导出数据实现

在现代软件开发中,数据导出功能是企业应用中的一个常见需求。本文将介绍如何使用Java编写一个通用导出接口,从SQL查询数据并将结果导出为Excel文件。我们将详细讲解代码实现,并分享其中的一些关键技术点。

项目背景

本项目的目标是实现一个通用导出接口,通过接收用户提供的SQL查询,执行查询并将结果导出为Excel文件供用户下载。该接口具备分页查询、动态表头生成以及数据字典翻译等功能,满足大数据量导出的需求。

主要技术栈

  • Spring Boot:用于快速构建和配置Spring应用程序。
  • EasyExcel:一个快速、简单的处理Excel的开源工具。
  • JPA:用于数据持久化和分页查询。

核心代码实现

Controller层:导出接口

首先,我们来看导出接口的实现。通过接收ExcelQueryDto对象中的SQL查询,执行查询并将结果导出为Excel文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@PostMapping("/excel/export")
@ApiOperation(value = "通用导出")
@LogAnnotation(module = IasrConstants.MODULE, recordRequestParam = true)
public void exportQueryData(@RequestBody ExcelQueryDto excelQueryDto, HttpServletResponse response) {
long startTime = System.currentTimeMillis(); // 记录开始时间
if(ObjectUtils.isEmpty(excelQueryDto) || StringUtils.isEmpty(excelQueryDto.getQuerySql())){
throw new BusinessException("无导出sql,请检查!");
}
ExcelWriter excelWriter = null;
try {
Pageable pageable = PageRequest.of(0, Integer.parseInt(exportDataNums));
Page<Map<String, Object>> dataPage = projPlanReportRepository.findReportPage(pageable, excelQueryDto.getQuerySql(), excelQueryDto.getParamList());
String fileName = excelQueryDto.getFileName();
fileName = Optional.ofNullable(fileName).orElse("导出数据");
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");

String dataSheetName = StringUtils.isEmpty(excelQueryDto.getDataSheetName()) ? "查询数据" : excelQueryDto.getDataSheetName();
WriteSheet writeSheet = EasyExcel.writerSheet(dataSheetName).build();
excelWriter = EasyExcel.write(response.getOutputStream()).build();
if(!ObjectUtils.isEmpty(dataPage) && !CollectionUtils.isEmpty(dataPage.getContent())){
List<String> excelTitleList = CollectionUtils.isEmpty(excelQueryDto.getExcelTitleList())?new ArrayList<>(dataPage.getContent().get(0).keySet()):excelQueryDto.getExcelTitleList();
List<List<String>> headerList = Collections.singletonList(excelTitleList);
excelWriter.write(headerList, writeSheet);

int totalPage = dataPage.getTotalElements()>100000?100000/Integer.parseInt(exportDataNums):dataPage.getTotalPages();
int currPage = 0;
logger.info("查询数据共"+totalPage+"页,数据共"+dataPage.getTotalElements()+"条。。。");
while (currPage<=totalPage){
logger.info("开始查询第"+currPage+"页。。。。");
try {
pageable = PageRequest.of(currPage, Integer.parseInt(exportDataNums));
Page<Map<String, Object>> pageData = projPlanReportRepository.findReportPage(pageable, excelQueryDto.getQuerySql(), excelQueryDto.getParamList());
if (!ObjectUtils.isEmpty(pageData) && !CollectionUtils.isEmpty(pageData.getContent())) {
Map<String, Map<String, Object>> dictionaryMap = excelQueryDto.getDictitoryMap();
List<List<Object>> dataList = getExcelDateList(excelQueryDto, pageData, headerList, dictionaryMap);
excelWriter.write(dataList, writeSheet);
}
} catch (Exception exception) {
logger.error("查询异常", exception);
}
logger.info("查询第"+currPage+"页结束");
currPage++;
}
if(!ObjectUtils.isEmpty(excelQueryDto.getExportSqlFlag()) && excelQueryDto.getExportSqlFlag()){
String sqlSheetName = StringUtils.isEmpty(excelQueryDto.getSqlSheetName()) ? "查询sql" : excelQueryDto.getSqlSheetName();
WriteSheet writeSqlSheet = EasyExcel.writerSheet(2,sqlSheetName).build();
List<List<String>> dataList = Collections.singletonList(Collections.singletonList(excelQueryDto.getQuerySql()));
excelWriter.write(dataList,writeSqlSheet);
}
excelWriter.finish();
}
} catch (Exception e) {
logger.error("导出excel异常", e);
throw new BusinessException("导出excel异常:"+e.getMessage());
} finally {
if (excelWriter != null) {
excelWriter.finish();
}
logger.info("导出excel耗时:" + (System.currentTimeMillis()-startTime)/1000 + "s");
}

}

服务层:数据查询和处理

接下来,我们来看数据处理部分的实现,包括分页查询和数据字典翻译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static List<List<Object>> getExcelDateList(ExcelQueryDto excelQueryDto, Page<Map<String, Object>> pageData, List<List<String>> headerList, Map<String, Map<String, Object>> dictionaryMap) {
List<List<Object>> dataList;
if(MapUtils.isNotEmpty(excelQueryDto.getDictitoryMap())){
dataList = pageData.getContent().stream()
.map(data -> headerList.get(0).stream()
.map(header -> {
Object originalValue = data.get(header);
if (dictionaryMap.containsKey(header)) {
Map<String, Object> valueMap = dictionaryMap.get(header);
return valueMap.getOrDefault(ObjectUtils.isEmpty(originalValue)?"":originalValue.toString(), originalValue);
} else {
return originalValue;
}
})
.collect(Collectors.toList()))
.collect(Collectors.toList());
} else {
dataList = pageData.getContent().stream().map(data -> headerList.get(0).stream().map(data::get).collect(Collectors.toList())).collect(Collectors.toList());
}
return dataList;
}

数据处理层

分页查询数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
@Transactional
public Page<Map<String, Object>> findReportPage(Pageable pageable, String sql, List<Object> paramList) {
Page<Map> pageResult;
if(CollectionUtils.isNotEmpty(paramList)){
pageResult = JpaUtils.nativeQuery(sql, Map.class, pageable,paramList.toArray());
}else {
pageResult = JpaUtils.nativeQuery(sql, Map.class, pageable);
}
return new PageImpl<>(pageResult.getContent().stream()
.map(m -> {
Map<String, Object> stringObjectMap = new HashMap<>();
m.forEach((key, value) -> {
if (value instanceof Date || value instanceof Character){
value = value.toString();
}
stringObjectMap.put(String.valueOf(key), value);
});
return stringObjectMap;
}).collect(Collectors.toList()),
pageResult.getPageable(),
pageResult.getTotalElements());
}

关键技术点解析

  1. 分页查询
    为了处理大数据量的导出,我们采用分页查询的方式,每次查询一部分数据,避免内存溢出。使用Pageable和Page接口实现分页。

  2. 动态表头生成
    根据查询结果的字段动态生成Excel表头,确保数据的灵活性和通用性。如果用户提供了自定义表头,则使用自定义表头;否则,使用查询结果中的字段名作为表头。

  3. 数据字典翻译
    在数据导出前,进行数据字典翻译,将原始数据中的代码值翻译为对应的描述。通过在ExcelQueryDto中传递数据字典映射,实现灵活的翻译功能。

  4. 异常处理和日志记录
    在导出过程中,进行异常处理,捕获并记录异常信息,确保系统的稳定性。同时,通过日志记录导出过程中的关键节点,便于后续问题排查和性能优化。

结论

通过本文介绍的通用导出接口,您可以实现从SQL查询数据并导出为Excel文件的功能。该接口具备分页查询、动态表头生成、数据字典翻译等功能,适用于多种业务场景。希望本文对您在实际项目中的应用有所帮助。如果您有任何问题或建议,欢迎在评论区与我们讨论。

感谢阅读!如果本文对您有所帮助,请分享给更多人。您也可以关注我的博客,获取更多技术分享。