全面解构传统持久层框架,拥抱真正的 SQL-First

全面解构传统持久层框架,拥抱真正的 SQL-First


写在前面

我从一开始就反复强调一句话:

“数据库只认 SQL。”

50 年了,没有任何东西能替代它。所有试图封装 SQL、隐藏 SQL、生成 SQL、翻译 SQL 的框架,最终都逃不开一个结局——你还是要写 SQL,而且你还要多学一层框架的垃圾话。

今天我把这些年对持久层框架的观察、踩坑、思考,一次性摊开来讲。


第一部分:解构 MyBatis——XML 不是优雅,是噪音

MyBatis 号称“SQL-First”,我承认它确实让你写 SQL。但问题在于:你在哪里写?用什么方式写?

答案是:在 XML 里,用一堆标签包着 SQL 写。

1. SQL 和 Java 分离?分明是割裂

他们说“SQL 和 Java 分离更清晰”。但分离不等于解耦,更不等于更好的维护性。

你把 SQL 从 Java 里挪到了 XML,但代价是什么?

  • 两个文件之间来回切——改个字段名,Java 改一遍,XML 改一遍
  • 四种语言混在一起——Java、XML、OGNL、SQL,你要在同一个文件里处理四种语法
  • 无法跳转、无法重构、无法追踪引用——你的 IDE 帮不了你,因为 XML 不是 Java

这叫解耦?这叫耦合Plus,耦合Max。Java 和 SQL 之间没有“分离”,只是从一种耦合变成了更隐蔽、更难维护的耦合。

2. 动态 SQL?本质是低配版字符串拼接

MyBatis 吹得最响的就是“动态 SQL”:

<if test="name != null and name != ''">
    AND name = #{name}
</if>

我来问一个问题:这跟你在 Java 里写 if (name != null) { sql.append("AND name = ?"); params.add(name); } 有什么本质区别?

答案是:没有。都是字符串拼接。

唯一的区别是:Java 里写是一行 if,XML 里写是三行标签。Java 里你能用 replaceformat、正则、流式处理,XML 里你只能靠那几套标签死撑。

动态 SQL 的灵魂在于“动态”二字,在于逻辑。逻辑用 Java 写是天经地义的,用 XML 写是在自缚手脚。

3. resultMap 的“灵活”是个定时炸弹

<resultMap id="userMap" type="User">
    <association property="dept" column="dept_id" select="selectDept"/>
</resultMap>

这个东西看起来很美好:你查一个用户,它自动帮你把部门也带出来。

但实际生产环境没人敢用。因为你不知道它什么时候炸,怎么炸,炸多响。你查 100 条用户,它就发 101 条 SQL;查 1000 条,就发 1001 条。

有经验的 MyBatis 开发者会怎么做?

退回到“1+1 查询”——主表查一次,子表查一次,然后在内存里用 Java 组装。

一旦退回到这一步,MyBatis 的 association 就成了摆设。你付出的是每一次查询都要解析 XML 的代价,换来的只是自动把 ResultSet 转成对象。这个功能 Spring 的 BeanPropertyRowMapper 也能做到,而且不用解析 XML。

那 MyBatis 还剩什么优势?

4. 31 类异常 vs 2 类异常

MyBatis 自己制造了 31 类异常。你花时间学的不是“数据库出了什么问题”,而是“框架的哪一层又炸了”。

你调试的不是 SQL 逻辑,是 XML 标签、是 OGNL 语法、是插件冲突、是缓存配置。你伺候的是框架,不是业务。

Spring JDBC 只有两到三类异常。SimpleDAO 也继承 Spring JDBC,同样是两到三类。你看到的异常就是数据库报的异常,没有框架中间层给你包装一层“框架相关”的废话。

5. 生态繁荣?那是补坑生态

为什么 MyBatis 有那么多插件?PageHelper、MyBatis-Plus、通用 Mapper、MyBatis-Flex……

因为这些功能 MyBatis 自己都不提供。分页不提供,你要装插件;数据权限不提供,你要自己写拦截器;脱敏不提供,你要自己实现 TypeHandler;单表 CRUD 简化不提供,你要用 Plus 或 Flex。

这叫生态繁荣?这叫框架缺了太多东西,全靠别人补。


第二部分:解构 ORM——单表一时爽,联表火葬场

ORM 最大的谎言是:“单表解决 80% 的问题。”

真实的企业开发中,单表查询占比连 10% 都不到。

用户权限、数据统计、业务流程落地、报表聚合……全部都需要多表联查。单表只存在于基础增删改查的测试阶段或极简模块。

1. 单表对象化的价值,只在增删改

  • 插入:对象 → 一行记录,天然映射
  • 更新:对象 → 一行记录,天然映射
  • 删除:按主键删一行,天然映射

这三件事,ORM 做得好,做得省力。但增删改在业务代码中的占比有多少?不到 10%。

2. 查询场景下,ORM 只能躺平

联表查询、聚合查询、子查询、半连接、窗口函数……你一进入查询场景,ORM 就废了。

你写 JPQL、HQL、Criteria、QueryDSL,最后发现它们的能力连 SQL 的 1/3 都发挥不出来。标量子查询写不出来,派生子查询写不出来,窗口函数没有,CTE 没有。

于是 ORM 厂商在文档里写:复杂查询请使用原生 SQL。

这句话翻译一下就是:“我们帮你解决了 10% 的单表增删改,剩下的 90% 请你自己写 SQL。”

3. 关系对象化,根本是错的

ORM 试图把“关系数据库”变成“对象数据库”,把“关系”变成“对象树”。

但数据库的本质是关系,不是对象。两张表 JOIN 之后的结果,既不是“父对象”也不是“子对象”,它就是“两张表的部分字段组合”。你硬要把它变成一个对象树的嵌套结构,就是在扭曲数据的本质。

关系是网,不是树。


第三部分:解构 DSL——翻译带来的只有成本,没有收益

jOOQ 把 DSL 推向极致:全面翻译。每个关键字、每个函数、每个操作符都翻译成 Java 方法。

1. 类型安全?收益极低

jOOQ 的逻辑是:我帮你提前检查字段名拼写错误,这叫类型安全。

但你想想:

  • 你写完程序不点一下吗? 点一下不就发现报错了吗?
  • 你不自测吗? 自测一下不就发现字段名写错了吗?
  • 团队没有测试吗? 测试不就能覆盖吗?
  • 如果有人偷偷改数据库字段, 你手写 SQL 不也一样报错吗?

类型安全唯一的收益是“编译时报错比运行时快一步”,但这一步的收益,根本不足以覆盖它带来的成本。

2. 逻辑错误,你永远检查不出来

SELECT * FROM user WHERE age > 18 写成 SELECT * FROM user WHERE age > 80,语法完全正确,类型完全正确,jOOQ 不会报任何错。但业务数据全漏了。

类型安全只覆盖“拼写错误”这 1% 的问题,另外 99% 的逻辑错误和性能问题它一个都帮不上。

3. 成本收益严重倒挂

你写一行 SQL:

SELECT * FROM user WHERE age > 18

jOOQ 要写:

context.selectFrom(USER).where(USER.AGE.gt(18)).fetch()

代码长了两倍,还要学一整套 API,还要生成一堆类,还要维护一个代码生成流程。你付出的一切,只是为了把字段名拼错的风险降到最低——而这个风险本来就不高。

4. DSL 能力上限的排序

jOOQ(上限最高)> MyBatis Flex > MyBatis Plus

jOOQ 能做“全面翻译”,理论上能覆盖 SQL 全部能力。Flex 覆盖联表场景,留了原生 SQL 后门。Plus 只能覆盖单表查询。

但无论哪一种 DSL,只要它还在“翻译”,就永远不可能比“直写 SQL”更简洁、更透明、更容易维护。因为你学 DSL 的逻辑,本质上是在学一套 SQL 的方言。你付出的每一分精力,都是在为“翻译”买单。


第四部分:SimpleDAO——真正的 SQL-First

前面说了这么多“什么是错的”,那什么是对的?

SimpleDAO 的定位很简单:极致的字符串拼接和参数收集。

1. 三条底线

  • 不封装关键字:SELECT、FROM、JOIN、GROUP BY 你手写,框架不碰
  • 不封装运算符:=、>=、LIKE、IN、EXISTS 你手写,框架不碰
  • 不做语法翻:不解析 SQL、不生成 SQL、不改写 SQL

2. 一套心智模型打到底

单表:

UserCond cond = UserCond.builder().name("张").ageMin(18).build();
userDao.page(cond);

联表(同样的 API):

String sql = "SELECT u.*, d.name dept_name FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id = d.id";
userDao.page(sql, cond, UserVO.class);

复杂条件(还是同样的 API):

cond.add("AND EXISTS (SELECT 1 FROM bus_order WHERE user_id = u.id)", flag);
cond.add("AND u.age BETWEEN ? AND ?", ageMin, ageMax);
cond.add("AND (u.status = ? OR u.status = ?)", status1, status2);

从单表到联表到子查询到 CTE 到窗口函数,全是这一套。没有 XML、没有 DSL、没有三套语法切换。SQL 还是 SQL,Java 还是 Java。

3. 你写的是 SQL,框架只帮你拼条件和收集参数

这是 SimpleDAO 和所有传统框架最本质的区别:

框架做的事情
MyBatis在 XML 里写 SQL,用标签控制动态
ORM单表对象化,联表躺平
jOOQ把 SQL 翻译成 Java 方法链
SimpleDAOSQL 就是 SQL,放在 Java 里,框架只帮你拼条件和收集参数

4. 没有上限

因为数据库只认 SQL,而你写的就是 SQL。

你的能力上限 = SQL 的能力上限 = 无限。

只要是 SQL 合法的语法——BETWEENOREXISTSCASE WHEN、窗口函数、递归 CTE——全都能拼进去。框架不拦着你,框架不限制你。


结语

这些年,我见过太多人在框架里绕弯子:

  • 学 XML 标签、学 OGNL 表达式、学 resultMap 嵌套
  • 学 QueryWrapper 链式调用、学 Lambda 表达式构建条件
  • 学 JPQL、HQL、Criteria、QueryDSL……
  • 学 jOOQ 那套从数据库生成代码的流程

最终,你发现所有这些学习曲线,都是为了绕开一个你本来就该会的东西:SQL。

SimpleDAO 不让你绕弯子。它的价值从来不是“功能最多、最智能、最花哨”。它的价值是:

“让你在写 SQL 的时候,少写那些重复的 ifparams.add()。”

仅此而已。但正是这个“仅此而已”,让 SimpleDAO 成为我从 2017 年一直迭代到今天的项目——因为它没有谎言,没有泡沫,没有承诺它做不到的事情。它是一个勤勤恳恳的 SQL 脚手架。

如果你也受够了 XML 的折磨、OR M 的谎言、DSL 的翻译成本,欢迎试试 SimpleDAO。


开源地址

  1. 核心框架源码:https://gitee.com/gao_zhenzhong/simple-dao
  2. 系统底座:https://gitee.com/gao_zhenzhong/simple-dao-starter
  3. 代码生成器:https://gitee.com/gao_zhenzhong/simple-dao-coder
  4. 实战案例(全部源码):https://gitee.com/gao_zhenzhong/simple-dao-demo

把时间留给 SQL,而不是框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值