Java实现mysql的分页及流式查询导出

news/2024/11/17 1:52:55/

一、前言
在实际应用中,当我们需要导出大数据量数据时,可能会存在一些问题,因为当我们一下子将数据全部加载出来到内存中,很可能会发生OOM(内存溢出),而且查询会很慢,因为框架耗费大量的时间和内存去把数据库查询的结果封装成我们想要的对象(实体类)。

那么如何实现大数据量导出呢?下面会具体说明。

二、如何是实现?
1.常规查询:一次性读取大量数据或者分页读取。
分页查询实现大数据量导出。
(1)首先引入easyexcel依赖。

 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.0</version></dependency>

(2)定义工具类实现分页导出。

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.function.UnaryOperator;@Slf4j
public class PageExcelUtils {public static final Integer EXCEL_SHEET_ROW_MAX_SIZE = 1000001; // excel sheet最大行数(算标题)private static final long DEF_PAGE_SIZE = 1000; // 默认页大小private HttpServletResponse httpServletResponse;private long pageSize = DEF_PAGE_SIZE;private String fileName;private PageExcelUtils() {}public static PageExcelUtils export(HttpServletResponse response, String fileNamePrefix) throws UnsupportedEncodingException {PageExcelUtils excelUtil = new PageExcelUtils();excelUtil.httpServletResponse = response;excelUtil.fileName = fileNamePrefix + "_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN) + ".xlsx";response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");String downloadFileName = URLEncoder.encode(excelUtil.fileName, "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + downloadFileName);return excelUtil;}public PageExcelUtils pageSize(int pageSize) {this.pageSize = pageSize;return this;}private static <T> void pageExcelWriter(OutputStream outputStream, String fileName, Class<T> head, long pageSize, UnaryOperator<IPage<T>> pageFunction) {long start = System.currentTimeMillis();log.debug("fileName:{}, excel writer start", fileName);try (ExcelWriter excelWriter = EasyExcel.write(outputStream, head).build()) {IPage<T> page = null;WriteSheet writeSheet = EasyExcel.writerSheet(0).build();do {long pageSearchStartTime = System.currentTimeMillis();page = pageFunction.apply(page == null ? new Page<>(1, pageSize) : new Page<>(page.getCurrent() + 1, page.getSize(), page.getTotal(), false)); // 分页查询long pageExcelWriteStartTime = System.currentTimeMillis();writeSheet.setSheetNo((int) (page.getCurrent() * page.getSize() / EXCEL_SHEET_ROW_MAX_SIZE));excelWriter.write(page.getRecords(), writeSheet); // excel写入数据log.debug("fileName:{}, total:{}, pageSize:{}, totalPage:{}, pageNo:{}, sheetNo:{}, pageSearchTime:{}ms, pageExcelWriterTime:{}ms", fileName, page.getTotal(), page.getSize(), page.getPages(), page.getCurrent(), writeSheet.getSheetNo(), pageExcelWriteStartTime - pageSearchStartTime, System.currentTimeMillis() - pageExcelWriteStartTime);} while (page.getCurrent() < page.getPages()); // 是否还有下一页}log.debug("fileName:{}, excel writer done, totalTime:{}ms", fileName, System.currentTimeMillis() - start);}
}

(3)新建controller实现分页导出接口。

@RestControllerpublic class DemoController {@Resourceprivate DemoService demoService;/*** 分页导出Excel(先查后写)* @param response 响应* @throws IOException*/@GetMapping("/exportExcel")public void exportExcel(HttpServletResponse response) throws IOException {PageExcelUtils.export(response, "测试").pageSize(5000).pageExcelWriter(Demo.class, page -> demoService.lambdaQuery().page(page));}}

其中demoService如下:

/*** 数据访问层接口实现类*/
@Service
public class DemoServiceImpl extends BaseServiceImpl<DemoMapper, Demo> implements DemoService {
}

Demo类示例:

@TableName
@Data
public class Demo {@ExcelProperty("编号")@TableId(type = IdType.AUTO)private Integer id;@ExcelProperty("标题")private String title;@ExcelProperty("内容")private String content;
}

这样就基本实现了数据分页导出了。

但是需要注意的是:在使用分页查询需要注意深分页问题,否则会导致导出效率非常低。而分页查询效率取决于表设计,如果设计的不好,就无法执行高效的分页查询。因此流式查询是一个数据库访问框架必须具备的功能。

2.流式查询实现大数据量导出。

(1)定义一个mapper

 @Select("select * from data t ${ew.customSqlSegment}")@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = -2147483648)@ResultType(User.class)void exportData(@Param(Constants.WRAPPER) QueryWrapper<Demo> wrapper, ResultHandler<Demo> handler);

(2)通过service来实现调用逻辑,示例如下:

QueryWrapper<Demo> wrapper = new QueryWrapper<Demo>();demoMapper.exportData(wrapper,resultContext -> {Demo data = resultContext.getResultObject();//这边循环调用就可以实现导出相关业务了System.out.println(data);});

这种方式就大致可以实现流式导出。

因此,使用流式查询导出可以有效避免大数据量导出时的OOM问题,但是流式查询需要注意长时间占用数据库连接的问题。


http://www.ppmy.cn/news/1456050.html

相关文章

Linux下网络编程-简易Epll服务器和客户端

Linux下网络编程-简易Epll服务器和客户端 简易Epll服务器: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/epoll.h>#define B…

Llama3-Tutorial之XTuner微调Llama3个人小助手

Llama3-Tutorial之XTuner微调Llama3个人小助手 使用XTuner微调llama3模型。 参考&#xff1a; https://github.com/SmartFlowAI/Llama3-Tutorial 1. web demo部署 参考上一节内容已经完成web demo部署&#xff0c;进行对话测试, 当前回答基于llama3官方发布的模型进行推理生成&…

[Linux深度学习笔记4.28]

隐藏权限 : 防止root用户误操作删除, 查看隐藏权限 : lsattr设置隐藏权限 : chattrchattr a : 可以追加内容,不能编辑不能删除chattr i : 不能编辑不能删除文件chattr A :文件访问时间固定下来stat : 查看文件的详细信息 进程管理 : 进程 : 一个正在运行的程序,主进程,子进程线…

MES系统:优化生产执行,实现高效、灵活的制造管理

MES系统作为操作执行层可以缩短排产周期&#xff0c;解决紧急插单问题&#xff1b;通过计划、采集、管控等功能来改进生产执行&#xff1b;与实际生产即时接轨车间时间驱动上层的商务活动。 MES系统包含基础数据、物料和工艺管理、生产过程管理、APS排产、人员管理、设备与工具…

ue引擎游戏开发笔记(33)——武器与角色的匹配,将新武器装备到角色身上

1.需求分析&#xff1a; 武器能出现在世界中&#xff0c;完成了第一步&#xff0c;下一步需要角色和武器适配&#xff0c;即不论角色跑动&#xff0c;射击等&#xff0c;武器和角色都相匹配&#xff0c;将武器装备到角色身上。 2.操作实现&#xff1a; 1.首先先把角色原有的武…

论文笔记ColdDTA:利用数据增强和基于注意力的特征融合进行药物靶标结合亲和力预测

ColdDTA发表在Computers in Biology and Medicine 的一篇一区文章 突出 • 数据增强和基于注意力的特征融合用于药物靶点结合亲和力预测。 • 与其他方法相比&#xff0c;它在 Davis、KIBA 和 BindingDB 数据集上显示出竞争性能。 • 可视化模型权重可以获得可解释的见解。 …

Go Web 开发 Demo【用户登录、注册、验证】

前言 这篇文章主要是学习怎么用 Go 语言&#xff08;Gin&#xff09;开发Web程序&#xff0c;前端太弱了&#xff0c;得好好补补课&#xff0c;完了再来更新。 1、环境准备 新建项目&#xff0c;生成 go.mod 文件&#xff1a; 出现报错&#xff1a;go: modules disabled by G…

17.Blender RC大佬EEVEE皮肤节点预设导入

如何添加节点预设 在底下的左下角打开Geometry Node Editor 选中正方体&#xff0c;点击新建 当鼠标指针在两个模块之间&#xff0c;是十字的样子时 可以拖出一个新的板块 然后打开文件浏览器 找到节点预设然后拖入到底下的节点编辑界面就可以了或者是blend文件&#xf…