很多人刚开始使用 MyBatis-Plus 的 Wrapper 时,都会有一种“终于解放了”的感觉:不用写 XML,不用拼 SQL,条件一条条往上加,看起来既优雅又好维护。
但项目跑久了,经常会出现一个反差很大的现象:Wrapper 越写越顺,SQL 却越来越怪。
条件逻辑开始变得难以理解,偶尔还会出现一些“解释不清”的线上问题。问题其实不在 Wrapper 本身,而在于使用方式悄悄发生了变化。
图:表达困惑的表情包
1. 条件是顺序追加的,但业务逻辑不是线性的
这是 Wrapper 最容易让人产生误判的地方。
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (status != null) {
wrapper.eq(User::getStatus, status);
}
if (type != null) {
wrapper.eq(User::getType, type);
}
if (keyword != null) {
wrapper.like(User::getName, keyword)
.or()
.like(User::getPhone, keyword);
}
代码看起来很自然,但生成的 SQL 实际上是按调用顺序线性拼接的。
一旦 or() 出现,如果没有显式控制范围,就会直接影响后面的整体逻辑。这类问题在代码层面几乎看不出来,只有在日志里看 SQL 才能意识到不对。
2. 一旦出现 or,SQL 结构就很容易失控
很多 SQL“怪”的根源,都来自 or() 的滥用。
wrapper.eq(User::getDeleted, 0)
.eq(User::getStatus, 1)
.or()
.eq(User::getLevel, 3);
这段代码在视觉上很容易被理解为“几种条件之一”,但 SQL 并不会替你补全业务语义。
如果没有明确的分组,or 就会和前面的所有条件发生关系。代码看起来没问题,逻辑却已经悄悄变了。
3. 条件复用越多,问题越容易被放大
很多项目里都会封装一些“公共条件”:
wrapper.eq(User::getDeleted, 0);
然后在不同业务场景中不断往上叠加新的条件。
一开始没问题,但当后面有人加了 or、范围查询或模糊匹配之后,这些公共条件就可能被无意中“带偏”。
Wrapper 很适合简单直观的查询,不太适合被拆成零碎条件到处拼。
4. 条件一多,调试成本会急剧上升
Wrapper 的另一个隐性问题是:代码层面可读,不代表 SQL 层面清晰。
当条件超过一定数量之后,你很难只通过代码判断最终 SQL 的结构,只能依赖日志。这也是为什么很多问题只能在线上通过 SQL 日志才能复现。
写 Wrapper 的成本很低,排查 Wrapper 生成的 SQL 成本却很高。
5. Wrapper 不擅长表达“分支型查询结构”
当查询逻辑开始出现明显分支,比如:
- 不同参数组合,对应不同查询结构
- 某些条件只在特定场景下生效
- and / or 的关系随业务变化
继续强行用一条 Wrapper 链路去拼,SQL 基本都会变得不可控。
这时候问题已经不是“写法问题”,而是结构不适合。
6. SQL 越怪,往往不是工具的问题
很多人会怀疑是不是 Wrapper 有 bug,其实大多数情况下不是。
真正的问题在于:查询条件本身没有被当成一个“结构”去设计。
当你只是不断地往 Wrapper 上加条件,而没有先想清楚查询到底分几种形态,最终生成的 SQL 一定会越来越乱。
7. Wrapper 写得顺,不代表逻辑是安全的
链式调用非常容易让人忽略整体 SQL 的形态。代码写起来流畅,但一旦业务变复杂,这种“顺手”的写法反而更危险。
真正能把 Wrapper 用好的人,通常不是 API 最熟的,而是对业务条件边界最清楚的。在复杂的 Java 项目中,清晰地规划数据访问层逻辑至关重要。
希望这些分析能帮助你重新审视项目中的 Wrapper 使用。如果你有更多关于ORM框架或SQL优化的困惑,欢迎到云栈社区与其他开发者一起交流探讨。
图:表情兴奋的卡通形象
图1:终于搞懂了!