MyBatis通用查询如何避免SELECT *?MetaLite如何按需返回字段

MyBatis 通用查询如何避免 SELECT *?MetaLite 如何按需返回字段

摘要: MyBatis 手写 SQL 时当然可以精确选择列,问题发生在通用 DAO:为了复用查询逻辑,很多项目退回 SELECT *,宽表、列表页和大字段读取随之浪费带宽。MetaLite ORM 把字段投影放进 Query.includeField/excludeFieldfindOneByQueryfindListByQuery 都能按需取字段,而且支持方法引用,字段重命名可获得编译期保护。

先澄清一个事实:MyBatis 并不会强迫开发者写 SELECT *。它最大的优势之一正是 SQL 完全可控。

真正的痛点是通用查询。条件、排序、分页都希望复用,返回字段又因场景不同而变化。团队要么复制多条 SQL,要么让公共方法查询整行数据。

MetaLite ORM 将字段选择提升为 Query 的标准组成部分。

一、findOne 和 findList 为什么都需要字段投影

典型场景包括:

  • 用户列表只需要 id、昵称和状态,不需要头像原图或个人资料;
  • 权限判断只需要 id 与状态;
  • 定时任务只扫描主键和时间字段;
  • Elasticsearch 列表页只需要摘要,不需要完整正文。

如果只有少量字段,读取整行会增加数据库 IO、网络传输、对象转换和 JVM 占用。宽表或大字段场景尤其明显。

二、Query 同时支持 include 与 exclude

只返回明确字段:

java 复制代码
Query query = Query.query(
        Criteria.where(UserEntity::getStatus, 1)
).includeField(
        UserEntity::getId,
        UserEntity::getNickname,
        UserEntity::getStatus
);

List<UserEntity> users = userDao.findListByQuery(query);

排除少数大字段:

java 复制代码
Query query = Query.query(criteria)
        .excludeField(
                UserEntity::getAvatar,
                UserEntity::getProfile
        );

UserEntity user = userDao.findOneByQuery(query);

includeFieldexcludeField 都提供字符串、字符串集合和方法引用重载。推荐业务代码优先使用方法引用,实体字段改名后更容易在编译阶段发现影响范围。

三、JDBC 最终如何生成 SELECT 列

JdbcHelper.select(Query) 遍历实体属性到数据列的映射:

  1. 字段出现在 excludeFields 中,直接跳过;
  2. 没有设置 includeFields,默认选择其余所有字段;
  3. 设置了 includeFields,只加入明确包含的字段;
  4. 最终生成实际列名列表,而不是固定 SELECT *

因此 include 与 exclude 同时出现时,exclude 拥有更高优先级。团队应避免写互相冲突的配置,否则可读性会变差。

还应保证最终至少保留一个有效字段。当前 JDBC SQL 生成逻辑会在拼接完成后删除最后一个逗号;如果 include 中没有合法实体字段,或所有字段又被 exclude 排除,不能期待框架自动退回 SELECT *。这类配置应在调用前校验,并作为后续框架防御性校验的改进点。

四、字段投影不只作用于列表查询

BaseEntityDao 提供:

  • findOneByQuery(Query)
  • findListByQuery(Query)
  • findListByQuery(Query, Pageable)

这意味着单对象查询、列表和分页列表都能复用相同的字段投影、条件、排序和 hints,而不必为了返回列不同创建多个 Mapper 方法。

Query 默认 limit 为 Pageable.MAX_PAGE_SIZE,当前是 2000,防止一个没有分页意识的通用查询无限拉取数据。Pageable 还限制页码与页大小范围。

五、MySQL 与 Elasticsearch 为什么共用同一选择方式

在 JDBC 实现中,字段投影生成 SQL 列表;在 Elasticsearch 实现中,对应的是 source filtering。

业务代码仍通过 Query.includeField/excludeField 表达意图。切换数据源实现时,调用层不用重新学习一套字段选择接口。

这就是 MetaLite 自研 ORM 的核心方向:不试图统一所有底层语法,只统一企业应用最常见、最值得治理的访问语义。

六、部分字段实体有一个容易踩的坑

返回类型仍然是实体类。没有被查询的字段会保持 Java 默认值,例如引用类型为 null,数值基本类型可能为 0

这不代表数据库中真的存储了这些值。因此:

  • 部分字段实体适合只读展示或后续投影;
  • 不要把它直接当完整实体执行全字段更新;
  • 返回给接口前最好转换成明确的 DTO;
  • 基本类型若需要区分"未查询"和 0,应考虑包装类型。

字段投影减少 IO,但也要求调用方理解"不完整实体"的语义。

七、与 MyBatis-Plus Wrapper.select 有什么区别

MyBatis-Plus 已经支持 LambdaQueryWrapper 的 select,同样可以避免字符串字段。这一点不应忽略。

MetaLite 的差异主要不在"能不能选字段",而在组合后的契约:

  • 字段投影与自研 CriteriaQueryPageable 同属于 common 模块;
  • JDBC 与 Elasticsearch 消费同一查询对象;
  • findOnefindList、分页列表共享 DAO 语义;
  • 与数据源组、DbRouter、TableRouter 一起工作。

如果项目只使用 MySQL 且 MyBatis-Plus 已经治理良好,仅为字段投影迁移 ORM 没有必要。

八、真正值得优化的不是语法,而是默认行为

MetaLite 并没有发明"选择部分字段"。它做的是把这项能力变成通用 DAO 的默认组成部分,并给字段方法引用、结果上限和多数据源实现一个共同入口。

对于宽表和高频列表页,这种统一约束比每次提醒开发者"别写 SELECT *"更容易长期执行。


框架简介 MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。

源码基线 JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3,具体组件版本以项目 backend-bom 为准。

作者简介 15 年 Spring 体系企业级开发经验,专注于 Java 微服务架构、工程治理与生产实践。

持续更新 MetaLite 系列内容将持续更新,围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者,及时获取后续内容。

在线演示 演示地址: admin.metalite.top/ 演示账号: guess 演示密码: admin@2026

相关推荐
Flynt2 小时前
Go 1.27 升了一波,泛型方法和 JSON v2 真香但有个坑
后端·go
我会冲击波2 小时前
vibe coding 一个多月,我把 Claude Code + Pi 做成了桌面 IDE,还给它移植了 VS Code 的编辑体验
前端·javascript·后端
我是谁的程序员2 小时前
Windows / Linux / Mac 上不用 Xcode 把 IPA 上传到 App Store,upload 命令详解
后端·ios
凌涘2 小时前
JWT 登录鉴权 Demo 拆解
后端
JimmtButler2 小时前
从 Java 到 JS:我终于把闭包想明白了
javascript·后端
掘金者阿豪3 小时前
Codex 怎么突然变慢了?一个需求跑几十分钟,我才发现它的工作方式已经变了
前端·后端
Zadig3 小时前
Zadig 全面支持 CRD,至此所有 K8s 资源类型均可一键发布!
后端·devops
得物技术3 小时前
EP-Harness:从个人 AI Coding 到团队级 Agent 工作流|得物技术
后端·程序员·架构
用户125758524363 小时前
对象存储 URL 为什么别到处拼:后台附件预览要验这一层
后端·go·ai编程