跳转到正文

查询与分页

Base Business 使用 Query 承载分页参数,使用 Wrapper 或应用 SQL 执行筛选。新增一个 Query 字段不会自动生成 WHERE 条件;用户、租户或工作空间范围需要明确写入查询逻辑。

Query 生命周期

字段/方法当前行为
page首次 init() 时,空或不大于 0 取 1
rows首次 init() 时,空或不大于 0 取 10,大于 100 限制为 100
keywords仅承载字符串,需 Service 声明搜索字段才参与默认 Wrapper
idList首次 init() 重建为空列表;复杂分页在查出 ID 后回填
page()init(),再执行 PageHelper.startPage(page, rows)
getFrom()init(),返回 page * rows - rows

initidList 标记为 JSON 忽略。初始化是一次性的:之后修改 pagerows 不会再执行默认值修正。复制构造器和 extendQuery 只复制基础查询字段,不复制子类条件,idList 也不是深拷贝。

每次请求创建新的 Query。BaseEntityQuery.DEFAULT 是共享可变对象,默认 defaultQuery() 还将它强转为泛型 Query,使用子类 Query 时既有共享状态问题,也可能发生类型转换错误。具体服务应像快速开始一样重写 defaultQuery() 返回新的实际类型。

组合筛选与排序

应用可以重写 queryWrapper(QUERY) 加业务条件,并重写 keywords(QUERY) 返回用于模糊搜索的 SFunction 列表。默认关键字逻辑将这些字段组合成一个括号中的 OR LIKE 条件,再与其他条件 AND 连接;默认字段列表为空,因此仅传 keywords 不会筛选。

lambdaQueryWrapper(query, false) 跳过关键字拼接,但仍调用 queryWrapper(query)。默认 queryWrapper(query) 只是调用无参数 queryWrapper(),不会反射读取 Query 的业务字段。

排序有两个不同入口:无参数 lambdaQueryWrapper() 调用 defaultSort(wrapper)非空 Query 分支不会自动调用 defaultSort。因此分页需要在 Query 条件构造中显式加排序,通常将唯一 ID 作为最终排序字段。快速开始在 queryWrapper(query) 中按创建时间、ID 倒序排列。

调用方直接传入 Wrapper 的列表、计数和删除方法使用该 Wrapper;按 ID 查询使用 mapper.selectById,不会经过 queryWrapper(query)。不能仅重写一个 Query Hook 就认定详情、批量 ID 查询及更新删除都已具备相同的数据权限。

简单分页

service.page(query) 的顺序为:

  1. query.page() 开启 PageHelper 分页。
  2. listAllVo(query) 调用单表查询并把 PO 转为 VO。
  3. 调用 afterList 补充当前页数据。
  4. 将列表和 PageHelper 的总数、页码封装为 PageModel

需要 PageHelper 插件实际装配,单独注册 Mapper 不足以提供分页。分页开启后应紧接目标查询;在 Wrapper 构造或其他前置处理里另查数据库,可能让分页落到错误的查询上。基类没有包裹异常清理逻辑,应用自己编排 query.page() 时应在 finally 中清理 PageHelper 上下文,避免未执行目标查询时遗留状态。

page(query) 不接受 null Query。listAllVo(query) 本身不分页,listPoByQuery(null) 则使用默认无参数 Wrapper 查询;不要用列表接口返回全表,再误认为 rows 已限制结果。

复杂分页

联表场景可使用 pageForComplex(query),但必须先实现 BasePageMapper 的 SQL:

方法需要提供的结果
listIds(query)根据完整筛选条件、权限范围和稳定排序,查询当前页主键
listByIds(query)依据回填后的 query.idList 读取 VO,保持与 ID 页一致的顺序和结果粒度
list(query)map(query)额外的自定义列表契约;默认简单分页和 MapVo 列表不会自动调用它们

流程是开启分页 → listIds → 将 ID 写入 Query → listByIdsafterList → 包装结果。ID 为空时直接返回空列表与已有分页元数据,不执行第二条查询。两次查询之间没有默认事务或快照一致性保证。

应用 XML 或注解 SQL 必须自行完成字段映射、逻辑删除条件和权限过滤;这些方法不会经过 Service 的 Wrapper Hook。联表产生重复主键时,应在 ID 查询阶段按目标实体去重,否则总数及页内实体数量可能不一致。第二条 SQL 的 IN 条件不会自动保留 ID 列表顺序,需要显式排序。

列表与详情的后处理

方法后处理边界
listAllVo(query) / 无参数版本PO 转 VO 后调用 afterList
listAllVo(wrapper)仅转换,不调用 afterList
listAllMapVo(...)调用 toMapVo,不调用完整 VO 的 afterList
listAllEntity(...)使用指定 converter;不调用 afterList,converter 为 null 时返回空列表
queryPo* / queryVo*未查到时可返回 null,部分重载支持默认 Supplier
getPo* / getVo*未查到且无可用默认对象时抛出 business_not_found,可传自定义响应码

afterList 收到的列表可能由 Stream.toList() 产生,不应假定可增删;需要替换结构时构造新列表返回。详情 Hook 的重复调用边界见使用与配置

countEqual / listEqualcountLike / listLike 使用单字段条件;mapPoByIdList 按主键收集为 Map,不保证顺序,重复主键会导致收集异常。selectOne 查询若匹配多条也不能当作任意取第一条使用。

PageModel 的含义

标准业务字段为 pagerowstotallistisLastPagetotal 是记录总数,Java 类型为 Integer;来自 PageHelper 或 MyBatis-Plus 的 long 总数会转为 int,不能用它准确表示超过 Integer 范围的统计结果。

构造方式行为
列表 + Page / PageInfo,或 IPage使用数据库分页元数据;列表应已是当前页
列表 + page/rows/total,或列表 + Query + total接收已分页列表,不再次切片
列表 + Query把传入列表当作完整数据,在内存中 subList 切页;先调用 query.init()
仅 Query创建 total=0 的空页;先初始化 Query
仅列表只设置 list,分页元数据仍为空
无参、createEmptyModel()、静态 EMPTY字段默认为空,不是完整的标准空页;EMPTY 还是共享对象

通常 isLastPagepage * rows >= total 计算;PageInfo 构造器使用其结果。内存分页不会减少数据库取数,且 subList 与原列表共享底层数据,不适合替代大表 SQL 分页。

转换和后处理

  • convert(function) 转换当前列表并保留分页元数据;replace(list) 替换列表但不重算总数。
  • filterpeek 只登记操作,调用 collect() 才执行;先过滤,再执行消费者。它们只处理当前列表,不重算 total、rows 或 isLastPage,不能替代数据库条件。
  • collect() 不清空已登记操作,再次调用会再次执行。putconcat 会修改当前列表,要求字段已初始化且列表可变。
  • put 累加 rows,使用另一个模型的 total 和末页标记;concat 累加 total 并把末页标记设为 false,都不是自动重新分页。
  • offset(offset, rows) 只按参数计算 page 和末页标记,不设置模型 rows、不切片,offset=0 会得到 page=0,不能直接套用常规从 1 开始的页码语义。

向客户端返回时再使用 R.success(pageModel) 包装,外层业务成功标识遵循 Base 约定