一文读懂 PageQueryUtil:分页查询的优雅打开方式

适用人群 :Java 开发者,想了解函数式编程在实际项目中的应用 前置知识:了解 Java 8 Lambda 表达式基础


一、先来看一个实际场景

场景:同步 10000 条债券数据到接口平台

传统做法:

复制代码
Java// 一次性查询所有数据List<BondQuotaQueryBO> allData = bondQuotaQueryService.queryAll(queryBO);// 逐条处理for (BondQuotaQueryBO bo : allData) {    syncSingleQuota(bo, tenantId);}

问题:

  • ❌ 10000 条数据一次性加载到内存,可能 OOM(内存溢出)

  • ❌ 如果数据有 10 万条呢?

  • ❌ 查询超时怎么办?

优雅做法:

复制代码
Java// 分页查询,逐页处理PageQueryUtil.queryAndProcess(    pageInfo -> bondQuotaQueryService.    queryBondQuotaQueryByPage(queryBO, pageInfo),    rows -> {        for (BondQuotaQueryBO bo : rows) {            syncSingleQuota(bo, tenantId);        }    },    100  // 每页 100 条);

好处:

  • ✅ 每次只加载 100 条到内存

  • ✅ 自动处理所有分页

  • ✅ 代码简洁,逻辑清晰


二、PageQueryUtil 的核心设计

1. 方法签名(记住这个公式)

复制代码
Javapublic static <T> long queryAndProcess(    Function<PageInfo, PageResult<T>> queryFunction,  //     ← 怎么查    Consumer<List<T>> pageHandler,                     //     ← 怎么处理    int pageSize                                       //     ← 每页多少条)

通俗理解:

  • 参数 1:你告诉它"怎么查数据"(给个查询函数)

  • 参数 2:你告诉它"查到数据后怎么处理"(给个处理函数)

  • 参数 3:每页查多少条

返回值:一共处理了多少条数据


三、实战演练:完整代码拆解

步骤 1:准备查询条件

复制代码
JavaBondQuotaQueryBO queryBO = new BondQuotaQueryBO();queryBO.setLockStatus(2);      // 已审批queryBO.setNotEndType(1);      // 未终止

步骤 2:调用 queryAndProcess

复制代码
Javalong total = PageQueryUtil.queryAndProcess(    // 参数 1:查询函数(怎么查)    pageInfo -> bondQuotaQueryService.    queryBondQuotaQueryByPage(queryBO, pageInfo),        // 参数 2:处理函数(怎么处理)    rows -> {        for (BondQuotaQueryBO bo : rows) {            syncSingleQuota(bo, tenantId);        }    },        // 参数 3:每页条数    PAGE_SIZE  // 100);

步骤 3:理解 Lambda 表达式

参数 1 详解:
复制代码
JavapageInfo -> bondQuotaQueryService.queryBondQuotaQueryByPage(queryBO, pageInfo)

翻译成人话:

"给我一个分页信息(pageInfo),我返回一页数据给你"

等价于:

复制代码
Javanew Function<PageInfo, PageResult<BondQuotaQueryBO>>() {    @Override    public PageResult<BondQuotaQueryBO> apply(PageInfo     pageInfo) {        return bondQuotaQueryService.        queryBondQuotaQueryByPage(queryBO, pageInfo);    }}

关键点:

  • pageInfo 是 Lambda 的参数(占位符)

  • queryBO 是从外部捕获的变量(闭包)

  • 这个 Lambda 现在不执行,只是定义了一个"规则"

参数 2 详解:
复制代码
Javarows -> {    for (BondQuotaQueryBO bo : rows) {        syncSingleQuota(bo, tenantId);    }}

翻译成人话:

"给我一页数据(rows),我逐条处理它们"

等价于:

复制代码
Javanew Consumer<List<BondQuotaQueryBO>>() {    @Override    public void accept(List<BondQuotaQueryBO> rows) {        for (BondQuotaQueryBO bo : rows) {            syncSingleQuota(bo, tenantId);        }    }}

四、PageQueryUtil 内部是如何工作的?

执行流程图:

复制代码
PlainTextPageQueryUtil.queryAndProcess()│├─ 【第 1 步】创建第 1 页的分页参数│  PageInfo pageInfo = new PageInfo(1, 100);│├─ 【第 2 步】调用你的查询函数│  PageResult<T> result = queryFunction.apply(pageInfo);│  ↑│  └─ 实际执行:bondQuotaQueryService.queryBondQuotaQueryByPage(queryBO, pageInfo)│├─ 【第 3 步】调用你的处理函数│  pageHandler.accept(result.getRows());│  ↑│  └─ 实际执行:for (BondQuotaQueryBO bo : rows) { syncSingleQuota(bo, tenantId); }│├─ 【第 4 步】获取总条数,计算总页数│  long total = result.getTotal();  // 比如 1000│  int totalPages = 1000 / 100 = 10;│├─ 【第 5 步】循环查询第 2~10 页│  for (int page = 2; page <= 10; page++) {│      PageInfo pageInfo = new PageInfo(page, 100);│      PageResult<T> result = queryFunction.apply(pageInfo);  // 查询│      pageHandler.accept(result.getRows());                  // 处理│  }│└─ 【第 6 步】返回总条数   return processedCount;

时序图:

复制代码
PlainText调用方              PageQueryUtil              你的查询函数              你的处理函数  │                      │                            │                        │  │──queryAndProcess──> │                            │                        │  │                      │                            │                        │  │                      │──apply(pageInfo)──>       │                        │  │                      │                            │                        │  │                      │<─PageResult 返回──         │                        │  │                      │                            │                        │  │                      │────────accept(rows)  ──────────────────────────>   │  │                      │                            │                        │  │                      │  (循环第 2~N   页...)                               │  │                      │                            │                        │  │<─返回总条数───────── │                            │                        │  │                      │                            │                        │

五、为什么需要 pageInfo ->?

常见疑惑:

"pageInfo 不是在 queryAndProcess 里面定义了吗?为什么 Lambda 还要写一次?"

答案:

Lambda 是在定义"函数",不是在"调用函数"!

类比理解:

场景:你点外卖

传统方式:

复制代码
Java// 你直接去餐厅吃List<Food> allFood = restaurant.getAllFood();eat(allFood);

外卖方式(PageQueryUtil):

复制代码
Java// 你告诉外卖平台:FoodDelivery.orderAndEat(    // 参数 1:怎么取餐    box -> restaurant.getFoodByBox(box),        // 参数 2:怎么吃    foods -> {        for (Food food : foods) {            eat(food);        }    },        // 参数 3:每箱装多少    10);

Lambda 的 box -> 就像:

"给我一个餐盒编号(box),我去取对应的菜"

外卖平台会:

  1. 准备第 1 个餐盒 → 调用你的取餐函数 → 送到你家 → 你开吃

  2. 准备第 2 个餐盒 → 调用你的取餐函数 → 送到你家 → 你开吃

  3. ...

关键: 餐盒编号是外卖平台准备的,你只需要定义"收到编号后怎么做"!


六、闭包:Lambda 的超能力

问题:

queryBO 没有作为参数传给 queryAndProcess,为什么查询函数里能用?

答案:

Lambda 可以"记住"定义时的上下文!

复制代码
JavaBondQuotaQueryBO queryBO = new BondQuotaQueryBO();  // ← 外部变量queryBO.setLockStatus(2);PageQueryUtil.queryAndProcess(    // Lambda"捕获"了外部的 queryBO    pageInfo -> bondQuotaQueryService.    queryBondQuotaQueryByPage(queryBO, pageInfo),    //                                                            ↑    //                                                            └─ 外部变量,被 Lambda 捕获    ...);

通俗理解:

Lambda 就像一个" closures(闭包)",把定义时能看到的变量都装进包里,以后随时能用!


七、举一反三:更多使用场景

场景 1:批量发送邮件

复制代码
JavaEmailQueryBO queryBO = new EmailQueryBO();queryBO.setStatus("PENDING");PageQueryUtil.queryAndProcess(    pageInfo -> emailService.queryPendingEmails(queryBO,     pageInfo),    emails -> {        for (Email email : emails) {            sendEmail(email);        }    },    50);

场景 2:导出 Excel

复制代码
JavaPageQueryUtil.queryAndProcess(    pageInfo -> orderService.queryOrders(queryBO,     pageInfo),    orders -> {        // 逐页写入 Excel        excelWriter.write(orders);    },    1000);

场景 3:数据清洗

复制代码
JavaPageQueryUtil.queryAndProcess(    pageInfo -> dataService.queryDirtyData(pageInfo),    dataList -> {        for (Data data : dataList) {            cleanData(data);        }    },    200);

八、常见错误避坑

❌ 错误 1:直接调用方法(没有 Lambda)

复制代码
Java// 错误!pageInfo 未定义PageQueryUtil.queryAndProcess(    bondQuotaQueryService.queryBondQuotaQueryByPage    (queryBO, pageInfo),    ...);

正确写法:

复制代码
JavaPageQueryUtil.queryAndProcess(    pageInfo -> bondQuotaQueryService.    queryBondQuotaQueryByPage(queryBO, pageInfo),    ...);

❌ 错误 2:忘记写 ->

复制代码
Java// 错误!这不是 LambdaPageQueryUtil.queryAndProcess(    (PageInfo pageInfo) bondQuotaQueryService.    queryBondQuotaQueryByPage(queryBO, pageInfo),    ...);

正确写法:

复制代码
JavaPageQueryUtil.queryAndProcess(    (PageInfo pageInfo) -> bondQuotaQueryService.    queryBondQuotaQueryByPage(queryBO, pageInfo),    ...);

❌ 错误 3:类型不匹配

复制代码
Java// 错误!queryFunction 返回值类型不对PageQueryUtil.queryAndProcess(    pageInfo -> bondQuotaQueryService.queryCount    (queryBO),  // ← 返回 Integer,不是 PageResult    ...);

正确写法:

复制代码
JavaPageQueryUtil.queryAndProcess(    pageInfo -> bondQuotaQueryService.    queryBondQuotaQueryByPage(queryBO, pageInfo),    ...);

九、核心要点总结

1. 方法签名(背下来)

复制代码
JavaqueryAndProcess(    Function<PageInfo, PageResult<T>> queryFunction,  //     怎么查    Consumer<List<T>> pageHandler,                     //     怎么处理    int pageSize                                       //     每页多少条)

2. Lambda 表达式

复制代码
Java// 查询函数:接收 pageInfo,返回 PageResultpageInfo -> service.query(queryBO, pageInfo)// 处理函数:接收 rows,没有返回值rows -> { for (T item : rows) { process(item); } }

3. 闭包特性

复制代码
JavaSomeBO queryBO = new SomeBO();  // 外部变量pageInfo -> service.query(queryBO, pageInfo);  // Lambda 捕获外部变量

4. 执行流程

复制代码
PlainText1. 创建第 1 页 PageInfo2. 调用 queryFunction.apply(pageInfo) → 查询3. 调用 pageHandler.accept(rows) → 处理4. 循环第 2~N 页5. 返回总条数

十、课后练习

练习题 1:补全代码

复制代码
Java// 需求:分页查询用户,每页 50 条,打印每个用户的名字UserQueryBO queryBO = new UserQueryBO();queryBO.setStatus("ACTIVE");PageQueryUtil.queryAndProcess(    // 补全参数 1:查询函数    ________________________________,        // 补全参数 2:处理函数    ________________________________,        // 补全参数 3:每页条数    ____);

参考答案:

复制代码
JavaPageQueryUtil.queryAndProcess(    pageInfo -> userService.queryUsers(queryBO, pageInfo),    users -> {        for (User user : users) {            System.out.println(user.getName());        }    },    50);

练习题 2:判断正误

复制代码
Java// 这段代码有什么问题?PageQueryUtil.queryAndProcess(    pageInfo -> {        userService.queryUsers(queryBO, pageInfo);    },    users -> System.out.println(users.size()),    100);

答案: 查询函数缺少 return 语句!应该改为:

复制代码
JavapageInfo -> {    return userService.queryUsers(queryBO, pageInfo);}

结语

PageQueryUtil.queryAndProcess 是模板方法模式 + 函数式编程的完美结合:

  • 模板方法模式:PageQueryUtil 定义流程,你填充具体逻辑

  • 函数式编程:用 Lambda 表达式传递行为,代码简洁优雅

掌握这个工具,让你的分页查询代码:

  • ✅ 内存友好

  • ✅ 逻辑清晰

  • ✅ 易于复用

  • ✅ 优雅简洁

记住这个公式:

复制代码
PlainTextqueryAndProcess(    怎么查(Lambda),    怎么处理(Lambda),    每页多少条)

下次遇到分页查询,试试这个优雅的打开方式吧!🎉

相关推荐
2501_924952691 小时前
C++中的适配器模式
开发语言·c++·算法
2301_776508721 小时前
使用Python处理计算机图形学(PIL/Pillow)
jvm·数据库·python
全栈凯哥1 小时前
25.Python SSH 远程执行完全指南(SSHExecutor)
python·ssh
李昊哲小课1 小时前
PySide6 记事本应用开发教程
python·pyqt·pyside
良木生香1 小时前
【C++初阶】:C++类和对象(中):类的默认成员函数---万字解说(最主要的四点)
c语言·开发语言·c++
2501_945424801 小时前
实战:用Python开发一个简单的区块链
jvm·数据库·python
☆5661 小时前
C++安全编程指南
开发语言·c++·算法
bestadc1 小时前
智能体构建的三种经典套路:从零开始理解ReAct、Plan-and-Solve和Reflection
python
无心水1 小时前
【时间利器】4、JavaScript时间处理全解:Date/moment/dayjs/Temporal
开发语言·前端·javascript·状态模式·openclaw·date/moment·dayjs/temporal
星轨初途1 小时前
类和对象(中):六大默认成员函数与运算符重载全解析
开发语言·c++·经验分享·笔记·ajax·servlet