“禁止用 select * 作为查询字段列表”落地指南

简介: 《阿里巴巴 Java 开发手册》 MySQL 数据库部分,ORM 映射部分,谈到:【强制】 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。说明: 1)增加查询分析器解析成本。 2)增减字段容易与 resultMap 配置不一致。 3)无用字段增加网络消耗,尤其是 text 类型的字段。甚至有些公司还会对代码进行扫描,当发现代码或者 MyBatis 配置中出现 `select *` 时会给出告警要求修改。

一、背景

《阿里巴巴 Java 开发手册》 MySQL 数据库部分,ORM 映射部分,谈到:

【强制】 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
说明:
1)增加查询分析器解析成本。
2)增减字段容易与 resultMap 配置不一致。
3)无用字段增加网络消耗,尤其是 text 类型的字段。

甚至有些公司还会对代码进行扫描,当发现代码或者 MyBatis 配置中出现 select * 时会给出告警要求修改。
在这里插入图片描述

规范中将这么规定的原因给出了解释,但是落地时又会遇到一些抉择。

二、问题

先看一个正例和一个反例。

反例:

 UserDO getEmailById(Long id);

对应 xml 语句

<select id="getEmailById" parameterType="java.lang.Long" resultMap="resultMap">
  SELECT * FROM user WHERE id = #{id}
</select>

正例:

 String getEmailById(Long id);

对应 xml 语句:

<select id="getEmailById" parameterType="java.lang.Long" resultType="java.lang.String">
  SELECT email FROM user WHERE id = #{id}
</select>

正如手册上所说的,这种写法带来的好处是:

1)增加查询分析器解析成本。
2)增减字段容易与 resultMap 配置不一致。
3)无用字段增加网络消耗,尤其是 text 类型的字段。

很奇怪的是,很多文章最多提到这里就结束了。

居然就这样结束了???


那么如果查询部分字段怎么办?是继续使用 UserDO 还是定义新的 DO 类?

【1】继续使用 UserDO 作为方法返回值:

<<优点>>:
省事,减少对象定义
<<缺点>>:
无法根据函数名或返回值明确知道哪些属性被赋值哪些属性没有被赋值。

【2】定义新的 DO 对象

<<优点>>:
1)可以根据方法名和返回值,明确感知当前业务获取的字段
2)专用查询和通用查询很好地作区分
<<缺点>>:
当场景较多时,需要定义的 DO 对象过多

如 user 表中有 20 个字段,A 业务需要查询其中 18个字段,B 业务需要其中 8 个字段,C 业务需要所有字段,D 业务需要其中 5个字段,E 业务需要其中7 个字段等等,并且这些场景都是根据 ID 进行查询。

三、抉择

3.1 大逻辑

1)一般情况下多查几个字段,性能差异并不大
2)很多场景下,性能不是我们做决定的最重要因素,代码的可读性、可维护性非常重要
3)编码时要坚持做正确的事,而不是怎么省事怎么来
4)代码要符合设计模式的一些原则,要高内聚弱耦合

3.2 类比

【1】如果你是接口的调用方,服务方给你提供了一个接口,返回的 DTO 里面有 10个字段,你只需要其中的 2 个字段,你就要求对方提供新的接口,只返回这2个字段?虽然这样做性能更好,但实际工作中通常不会这么做。

如果你需要 2 个字段,他需要3 个字段,另外一个人也需要 3 个字段但是字段还不一样,都定义新的接口,服务提供方要崩溃了。

再如领域驱动设计中,领域对象(如 User )不会因为上游防腐层需要几个属性,而返回不同的专有领域对象。

如<<你去市场去买菜>>这个场景,菜农不可能因为你这次只需要 2 个鸡蛋,就摊位上就只能摆 2 个鸡蛋。他通常会把所有的摆放出来,你根据需要自己去挑选。
如 <<你去互联网平台上买菜>> 骑手送菜的场景,此时对于当前订单而言,只应该送给你订单对应数量的蔬菜,而不是把超市所有菜都带来,送到你家门口时,再全部摆出来,让你现场自己数。

在这里插入图片描述

【2】如果你依赖的二方服务给你返回一个全的 DTO,让你根据调用的方法名去“猜测” 里面哪些属性会被赋值(不看他的源码,你咋知道哪些被赋值哪些没有被赋值),是不是很可怕?

如果你将一个全的 DTO 或者通用的 VO 给前端,不保证所有属性都被赋值,让他根据调用的方法去“猜测”当前场景哪些属性被赋值过,是不是很可怕?

可能有些同学可能会说,给一个文档约定下也可以啊。
可是,有什么能比参数和返回值来约定更合适呢?
后面任何改动都要去增删文档?
人员变动之后代码如何维护?

通常两个选择:
(1)提供一个大而全的,保证有的字段都赋值,上游按需获取;
(2)提供一个专用的对象,被赋值的字段都在这个对象的属性中。

3.3 结论

【推荐】如果业务上明确只需要部分字段时,可以使用通用接口获取所有字段,然后上层只取用需要的字段即可。

[1] 如果查询条件走索引,查询的字段里不含大字段,查询单个字段和查询多个字段的性能差异微乎其微几乎可以忽略不计。
[2] 传统的三层架构,防腐层调用服务层、服务层调用数据访问层,某种程度上是为了复用。使用通用查询接口(通过id 获取整个DO 对象),可以更大程度上实现代码复用。

[2.1] 如上面所说上面不同业务需要不同数量的字段,定义六七个对象比较繁琐,业务需要应该在 DTO 或者 VO 层面控制字段,DO 层面可以复用。
[2.2] 如果你的业务VO 需要下游服务的 3 个字段,那么你要求下游服务单独给你提供只返回这3个字段的 DTO/ BO ?? 显然不合理吧?
[2.3] 不应该让每个查询场景都影响到 DAO 层,如果是这样,那么分层的意义何在?

【推荐】如果需要定制化查询,函数名不能有歧义,要体现出业务含义;不允许使用通用 DO 对象,需使用包装类型或者定义专有 DO 。
反例:
UserDO getUserDetailById(Long id)

这里的方法名是对 “用户详情页面需要字段”的业务描述,还是“用户全部字段”的描述?

UserDO getUserEmailById(Long id)

如果调用方代码较长,后续使用 UserDO 时“要时刻牢记” 这个 UserDO 只有 email 这个属性有值。

正例:
String getEmailById(Long id)
UserSimpleDO getSimpleById(Long id)

[1] 如果使用容易歧义的类通用化的函数名称,返回值是通用的DO,使用方很容易误用。
[2] 创建 DO 工作量并不大,对象的转换也可以通过工具类加以转化。
[3] 符合接口隔离原则,“使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口” 转换下 “不应该依赖不需要的字段”
[4] 符合迪米特法则 Talk only to your immediate friends and not to strangers 当前业务所需的字段才是 immediate friends,其他字段是 strangers ,符合高内聚、弱耦合的软件设计原则

设想一下 如果 UserDO getSimpleById(Long id) 这么定义,你不看 mybatis 的 xml 你知道有多少个属性有被值?
调用方更应该用哪个方法,关注参数和返回值,不应该“被迫”去了解底层实现。

四、总结

我们在做出抉择时,应该牢记软件设计的一些典型原则,如高内聚、弱耦合;设计模式的几大原则:单一职责、高内聚弱耦合、里氏替换、接口隔离、迪米特法则;降低复杂度等等。

坚持做正确的事,而不是怎么省事怎么来。

不能因为性能而牺牲可读性,可维护性。


创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。
在这里插入图片描述
相关文章
|
3月前
|
人工智能 弹性计算 监控
OpenClaw到底是啥?能做什么?怎样部署?一文讲透!
2026年初爆火的开源AI智能体OpenClaw,被网友爱称“小龙虾”。它不止能对话,更能本地执行文件管理、邮件发送、代码运行等真实任务,实现AI从“动口”到“动手”的跨越。阿里云支持一键部署,零门槛拥有专属AI助理!
1225 13
|
算法 前端开发 Java
信创环境下达梦数据库唯一索引异常无法拦截DuplicateKeyException
迁移到达梦数据库后,发现我们的全局异常拦截中的唯一索引异常 无法被正常拦截,给前端直接抛出了数据库原始的错误信息,对用户极其不友好。如果不对唯一索引异常拦截,则默认 与 的异常信息如下:在 中通过 注解,实现对异常响应的统一封装。可参考:全栈开发之后端脚手架:SpringBoot集成MybatisPlus代码生成,分页,雪花算法,统一响应,异常拦截,Swagger3接口文档以下是对数据库唯一索引异常的拦截,统一返回:编号不可重复。 问题分析 对主流的数据库的异常进行了封装与翻译,对于 都可以进行拦截,但是到了国产数据库,比如这里是达梦8,那么其异常信息 `Spring` 就不认识
2449 0
|
2月前
|
存储 人工智能 NoSQL
让 Agent 拥有记忆 —— 表格存储记忆服务邀测指南
本文将介绍表格存储记忆服务的产品能力、接入方式和接口说明,帮助您快速了解和体验表格存储记忆服务的相关功能。
345 2
|
8月前
|
缓存 安全 调度
阿里云渠道商:如何使用CDN加速全球业务?
阿里云CDN通过全球2800+节点,将内容分发至用户附近,实现“本地化”访问,显著降低延迟。支持智能压缩、动态缓存与HTTPS加密,兼具加速、优化与安全,助力企业高效出海,提升全球用户体验。
|
3月前
|
存储 数据采集 API
如何通过1688开放平台API获取指定店铺所有商品
本文详解如何调用1688开放平台官方API(alibaba.product.getSellerProductList),通过授权、签名、分页请求,批量获取店铺全量商品信息(标题、价格、SKU等),含Python实现、错误处理与优化建议,适用于电商ERP及竞品分析系统开发。(239字)
|
11月前
|
前端开发 Java 数据库连接
后端开发中的错误处理实践:原则与实战
在后端开发中,错误处理是保障系统稳定性的关键。本文介绍了错误分类、响应设计、统一处理机制及日志追踪等实践方法,帮助开发者提升系统的可维护性与排障效率,做到防患于未然。
|
10月前
|
弹性计算 安全 Linux
阿里云服务器ECS安装宝塔Linux面板、安装网站(新手图文教程)
本教程详解如何在阿里云服务器上安装宝塔Linux面板,涵盖ECS服务器手动安装步骤,包括系统准备、远程连接、安装命令执行、端口开放及LNMP环境部署,手把手引导用户快速搭建网站环境。
|
人工智能 前端开发 安全
构建现代交互式平台:CodeBuddy如何简化复杂系统开发
文章探讨了构建交互式平台的复杂架构挑战,涵盖前后端分离的五层架构设计。重点介绍了AI编程助手CodeBuddy在简化开发中的作用,包括智能代码生成、架构优化建议、跨技术栈支持、安全实践集成及文档生成等功能。通过实战案例展示,CodeBuddy显著提升开发效率与代码质量,助力团队应对复杂系统开发挑战,成为开发者不可或缺的工具。下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴。
593 10
|
Java Spring
JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: ● JDK动态代理只提供接口的代理,不支持类的代理Proxy.newProxyInstance(类加载器, 代理对象实现的所有接口, 代理执行器) ● CGLIB是通过继承的方式做的动态代理 , 如果某个类被标记为final,那么它是无法使用 CGLIB做动态代理的。Enhancer.create(父类的字节码对象, 代理执行器)
|
存储 分布式计算 并行计算
Spark学习---2、SparkCore(RDD概述、RDD编程(创建、分区规则、转换算子、Action算子))(一)
Spark学习---2、SparkCore(RDD概述、RDD编程(创建、分区规则、转换算子、Action算子))(一)