一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

MyBatis‑Plus中IPage分页与全量查询踩坑实战

时间:2026-08-07 09:51:50 编辑:袖梨 来源:一聚教程网

MyBatis‑Plus中IPage分页与全量查询踩坑实战并不只看表面做法,关键还要理解相关条件、限制和后续影响。

前言

业务开发中经常遇到一种场景:同一个查询逻辑,根据前端传参动态选择分页查询 / 查询全部数据。很多同学第一想法:复用同一套 Mapper 接口,传入Page(pageNum,-1)实现不分页,一行代码搞定。

MyBatis‑Plus中IPage分页与全量查询踩坑实战

但实际项目落地时,经常出现各种诡异问题:设置 size=-1,打印 SQL 依然出现LIMIT;IPage 的 records 为空;拦截器不生效等。尤其在二次封装框架环境下,分页拦截器加载异常会进一步放大问题。

这篇内容以真实业务实战,给出一套稳定、可维护的生产级实现方案。

环境:SpringBoot + MyBatis‑Plus,自定义 XML 手写 SQL,非 MP 自带 Wrapper 查询。

业务场景

业务需求:查询业务明细,支持两种模式

  1. 分页模式:按照页码、页大小返回 IPage 对象,用于前端表格分页渲染;
  2. 全量模式:不需要分页,查询全部数据,做后续业务计算处理,接口返回结构依然统一返回IPage

最初想法:只写一个 Mapper 方法,通过传入new Page<>(0,-1),依靠 MyBatis‑Plus 内置特性关闭分页,一份 Mapper、一份 XML 搞定两种逻辑。

遇到的现实问题

  1. Page(0,-1)不分页特性强依赖 MyBatis‑Plus 分页拦截器正常注册、生效
  2. 如果项目框架存在拦截器重复 / 缺失、版本差异、XML 手写 LIMIT,size=-1会直接失效,SQL 依旧拼接 LIMIT,只能查到部分数据。
  3. Mapper 接口直接传null给 IPage 参数,会触发空指针异常。
  4. MyBatis 本身不支持 Java 接口重载绑定同一个 XML 的id,同名不同参数重载会抛出 Mapper 绑定异常。

核心结论:生产环境不建议依赖Page(size=-1)做全量查询,它属于便捷语法,环境稍有变化就会出线上 bug。

实战最优方案:双 Mapper 方法 + SQL 片段复用

核心思路:

  1. Mapper 层提供两个不同名称的方法:一个带 IPage 用于分页;一个不带 IPage 直接返回 List,用于查询全部;
  2. XML 使用<sql>抽取公共查询片段,两个<select>通过<include>复用同一套查询逻辑,SQL 只维护一份,避免复制粘贴带来的维护灾难;
  3. Service 层做分支判断,全量查询拿到 List 后手动封装为 IPage 对象,保证对外接口返回对象统一。

1、Mapper 接口定义

public interface BizDataMapper {    /**     * 分页查询业务明细     * @param page MP分页对象     * @param queryDto 查询条件     * @return IPage分页结果     */    IPage<BizDataDTO> queryBizDetail(IPage<BizDataDTO> page,                                     @Param("dto") BizQueryDTO queryDto);    /**     * 查询全部业务明细(不分页)     * @param queryDto 查询条件     * @return 全量数据List     */    List<BizDataDTO> queryBizDetailAll(@Param("dto") BizQueryDTO queryDto);}

注意:两个方法方法名不能相同,MyBatis 依靠 id 与接口方法名绑定,不支持重载。

2、Mapper XML 实现,复用 SQL 片段

关键点:业务查询逻辑抽成<sql>公共片段,两个 select 标签直接引用;分页查询不要手写 limit,交给 MyBatis‑Plus 拦截器自动处理。

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.xxx.mapper.BizDataMapper">    <!-- 公共查询SQL片段,分页、全量查询共用,业务条件只维护一处 -->    <sql id="query_biz_detail_common">        SELECT            t.*,            rel.ext_info        FROM biz_main_table t        LEFT JOIN biz_rel_table rel ON rel.rel_id = t.rel_id        WHERE t.group_id = #{dto.groupId}        <if test="dto.bizStatus != null">            AND t.biz_status = #{dto.bizStatus}        </if>        <!-- 此处追加所有业务查询条件 -->    </sql>    <!-- 分页查询 -->    <select id="queryBizDetail" resultType="com.xxx.dto.BizDataDTO">        <include refid="query_biz_detail_common"/>        <!-- 禁止手写limit,由MyBatis‑Plus分页拦截器自动拼接 -->    </select>    <!-- 全量查询,不分页 -->    <select id="queryBizDetailAll" resultType="com.xxx.dto.BizDataDTO">        <include refid="query_biz_detail_common"/>    </select></mapper>

3、Service 业务层逻辑

对外返回统一 IPage,内部根据业务标识分支调用不同 Mapper 方法。

@Servicepublic class BizDataService {    @Autowired    private BizDataMapper bizDataMapper;    public IPage<BizDataDTO> getBizDetailList(BizQueryDTO reqDto, boolean needPage) {        IPage<BizDataDTO> result;        if (needPage) {            // 分页场景            Page<BizDataDTO> page = new Page<>(reqDto.getPageNo(), reqDto.getPageSize());            result = bizDataMapper.queryBizDetail(page, reqDto);            handleBizAfterProcess(result.getRecords());        } else {            // 不分页场景,直接调用全量查询方法            List<BizDataDTO> dataList = bizDataMapper.queryBizDetailAll(reqDto);            handleBizAfterProcess(dataList);            // 手动封装Page对象,保证出参统一            result = new Page<>();            result.setRecords(dataList);            result.setTotal(dataList.size());        }        return result;    }    private void handleBizAfterProcess(List<BizDataDTO> list) {        // 业务后置处理逻辑    }}

两种方案横向对比

方案优点缺点适用场景
双 Mapper 方法 + SQL 片段复用(本文方案)1. 不依赖分页拦截器状态,稳定性高; 2. 代码语义清晰,一眼区分分页 / 全量; 3. 不会出现 LIMIT 残留 bug; 4. 条件只维护一份,好维护需要新增一个 Mapper 方法生产环境首选,尤其框架封装较重的项目
new Page<>(0,-1)关闭分页只写一套 Mapper 方法强依赖拦截器,框架 / 版本变化容易失效;XML 手写 limit 直接失效测试、简单 demo,不建议生产使用

生产环境关键注意点

  1. 分页 XML 中禁止手写 LIMIT,分页 SQL 交给 MyBatis‑Plus 拦截器自动拼接;一旦手写 LIMIT,size=-1完全失效。
  2. 不要试图对 Mapper 接口 做方法重载,MyBatis 不支持同名不同参数绑定同一个 XML id。
  3. 全量查询返回 List 后手动组装Page,对外接口保持返回IPage,不需要修改上层接口定义。
  4. 使用<sql>+<include>复用 SQL,杜绝复制粘贴大段 SQL,避免后续改查询条件漏改一处。
  5. 分页拦截器必须正确配置,保证分页模式正常工作。

总结

Page(size=-1)是一个语法糖,适合简单 Demo;真实业务系统,框架环境复杂,拦截器有可能被 干扰,不建议依赖这种隐式特性。

通过两个 Mapper 方法 + MyBatis SQL 片段复用,只是少量代码成本,换来更高的稳定性、可读性与可维护性,规避线上诡异分页 bug,是更稳妥的工程实践。

热门栏目