持久层框架的评价标准:只有一个
你得知道你在跟谁打交道。
写代码十几年,我见过无数持久层框架的选型争论。Hibernate 粉丝说"不用写 SQL 就是爽",MyBatis 信徒说"SQL 控制权最重要",JOOQ 拥趸说"类型安全才是未来"。
争论到最后,谁也说服不了谁。
直到有一天,我看到这样一段代码:
java
@Repository
public class UserDao extends BaseDao<User> {
// 空的,什么都没写
}
就这么一个空类,单表 CRUD、分页、按 ID 查询、批量操作,全有了。
然后我又看到了条件类的写法:
java
@Setter
@Getter
public class UserCond extends BaseCondition {
private String userName;
private Integer ageMin;
private Integer ageMax;
private Long deptId;
@Override
protected void addCondition() {
add("AND user_name LIKE ?", userName, 3);
add("AND age >= ?", ageMin);
add("AND age <= ?", ageMax);
add("AND dept_id = ?", deptId);
}
}
再看到联表查询的写法:
java
private static final String JOIN_SQL = """
SELECT u.*, d.dept_name
FROM sys_user u
JOIN sys_dept d ON u.dept_id = d.dept_id
""";
public Page<UserVO> pageJoin(UserCond cond) {
return page(JOIN_SQL, cond, UserVO.class);
}
同一个 UserCond,单表用、联表用,条件拼接方式完全一致。没有任何切换成本。
整个持久层工具的核心代码加起来,不到 1500 行。
然后我回头看了看 MyBatis 的源码------几万行。MyBatis-Plus------几万行。Hibernate------几十万行。
问题来了:这 1500 行代码,跟那几十万行代码,解决的是不是同一个问题?
如果是,那那些几十万行的框架,到底"多"了些什么?
市面上常见的评价标准,都跑偏了
大多数人评价一个持久层框架,看的是这些东西:
- 功能列表:支持多少种数据库?有没有分页插件?有没有代码生成器?
- 生态大小:有多少人在用?有多少扩展插件?文档多不多?
- 大厂背书:是不是 Apache 项目?有没有大厂在使用?
- 封装程度:它帮我写了多少代码?我是不是少写了很多行?
这些标准听起来合理,但仔细一想------它们全都是在问"这个框架给了我什么",而不是在问"这个框架让我失去了什么"。
一个框架给了你 100 个功能,如果其中 99 个你本来就不需要,那这 99 个功能就是噪音。你不仅没赚到,还亏了------因为你得花时间去知道"自己不需要它们"。
真正的评价标准:SQL 浓度
我提出一个朴素的标准,大家听听看:
看代码里的 SQL 浓度。SQL 浓度越高,说明框架越好;噪音越高,说明框架越垃圾。
什么叫 SQL 浓度?就是你写的代码里,真正属于 SQL 的那部分占多少。剩下的那部分------不管叫配置、叫注解、叫 Wrapper、叫 DSL------都是噪音。
拿这个标准去看市面上的框架:
MyBatis XML:
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ...>
<mapper namespace="com.gzz.sys.user.UserMapper">
<select id="queryList" resultMap="userMap">
SELECT * FROM sys_user WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
</select>
</mapper>
SQL 浓度:不到 30%。剩下的全是 XML 头、命名空间、标签、resultMap。你写了 10 行代码,其中 7 行是在伺候框架。
MyBatis-Plus LambdaWrapper:
java
lambdaQuery()
.eq(User::getAge, 18)
.like(User::getName, "张")
.list();
SQL 浓度:0%。你根本看不见 SQL。你要靠脑补去猜它会生成什么。
而且,更致命的问题来了------单表一时爽,联表火葬场。
单表查询确实爽,链式调用一行搞定。但一旦你的查询涉及到两张表以上的 JOIN、子查询、聚合函数、UNION,Wrapper 立马就露馅了。你只能退回去写 XML 或者 @Select 注解。然后你的项目里就出现了两套条件构造方式:单表用 Wrapper,联表写 XML。你前面学的那些 .eq()、.like() 在复杂场景下全是废的。
Hibernate HQL:
java
"from User where age = :age and name like :name"
SQL 浓度:50%。但你还是看不见最终发给数据库的 SQL 是什么。而且 HQL 只是 SQL 的一个子集,很多数据库特有的功能你根本用不了。
SimpleDAO 方案(你的框架):
java
// 条件类------SQL 浓度 100%
@Override
protected void addCondition() {
add("AND user_name LIKE ?", userName, 3);
add("AND age >= ?", ageMin);
add("AND dept_id = ?", deptId);
}
// 单表查询
userDao.list(cond);
// 联表查询------同一个 cond,同一个方法,只是多了一个 SQL 字符串
String sql = "SELECT u.*, d.dept_name FROM sys_user u JOIN sys_dept d ON u.dept_id = d.dept_id";
userDao.page(sql, cond, UserVO.class);
关键点来了:单表和联表用的是同一套 BaseCondition,没有任何切换成本。 你学的 add 方法,单表能用,联表也能用。学一次,用一辈子。
噪音从哪里来?
所有的噪音,本质上是同一个东西:你为了"伺候框架"而写的代码。
- MyBatis 的 XML 头、namespace、resultMap,是噪音。你写它们不是为了解决业务问题,是为了让框架能找到你的 SQL。
- Wrapper 的
.eq()、.like()、.ge(),是噪音。你写它们不是因为 SQL 更清晰,是因为框架不允许你直接写 SQL。而且 Wrapper 的噪音有个特点:它只在单表场景下"看起来"低噪音,一旦遇到联表,它连用都用不了,你被迫回到 XML 里写标签------噪音一下子暴涨。 - 分页插件、数据权限插件的配置,是噪音。你写它们是因为框架没有提供原生支持,你得用插件去"补"。
- 代码生成器生成的模板代码,是噪音。你写它们是因为框架要求你写,你不写就没法跑。
你仔细看:这些噪音,没有一行是在解决业务问题。它们全是在解决"框架带来的问题"。
框架制造了一个黑盒,然后告诉你:你得学我这一套规则,才能在黑盒上开个洞。你学的那些规则、写的那些配置、配的那些插件------框架管这叫"生态丰富",实际上它只是"补丁生态"。
而你的 BaseDao 呢?它只是一个空类。你继承它,单表 CRUD 全有了。你想联表?自己写 SQL,用 BaseSql 的方法执行。没有任何多余的配置,没有任何多余的注解,没有任何多余的插件。
你写的每一行代码,都在解决业务问题。没有一行是在"伺候框架"。
你得知道你在跟谁打交道
所有持久层框架,本质上都在做同一件事:帮你把数据从数据库里拿出来,放进去。
但框架们对待这件事的态度,分成了两派:
一派认为:SQL 太底层了,我们要把它封装起来,让开发者不用写 SQL。
于是有了 Hibernate,有了 JPA,有了各种 ORM。它们试图用对象模型覆盖关系模型。
另一派认为:SQL 还是有价值的,但我们要用一套"更优雅"的方式来写它。
于是有了 MyBatis 的 XML 标签,有了 JOOQ 的 DSL,有了各种 Wrapper。
这两派的共同点是什么?
它们都在你和你真正要打交道的东西之间,插了一个中间人。
你本来要跟数据库打交道。现在你得先跟 Hibernate 打交道,再让它去跟数据库打交道。你本来要写 SQL。现在你得先写 HQL,再让 Hibernate 翻译成 SQL。
这个中间人如果靠谱也就算了。问题是------
- 你写的 HQL,它翻译出来的 SQL 可能根本不是你想的那样。
- 你写的 LambdaWrapper,它生成的 SQL 可能没走索引。
- 你写的 XML,那个
<if>标签可能漏了一条条件。
你得知道你在跟谁打交道。你在跟数据库打交道。你在跟 SQL 打交道。
你写的每一行代码,最终都要变成 SQL,发给数据库。既然如此,为什么要在中间加一层"翻译官"?这个翻译官既不比你聪明,也不比你快,它只是让你多了一道"猜它翻译得对不对"的步骤。
那不用 MyBatis,用什么?
兄弟,你这个问题,你的 simple-dao 就是答案。
- 开源地址: https://gitee.com/gao_zhenzhong/simple-dao
- 系统底座: https://gitee.com/gao_zhenzhong/simple-dao-starter
- 代码生成器: https://gitee.com/gao_zhenzhong/simple-dao-coder
- 实战案例(全部源码): https://gitee.com/gao_zhenzhong/simple-dao-demo
这个框架的核心就是三件套:
BaseCondition ------ 拼条件。你用 add("AND age >= ?", ageMin) 手写 SQL 片段,框架帮你收集参数。单表用它,联表也用它,ON 条件用它,EXISTS 子查询也用它。学一次,用一辈子。
BaseDao ------ 单表对象化。你继承它,就获得了完整的单表 CRUD、分页、批量操作能力。自动填充审计字段(createTime、createBy、updateTime、updateBy),自动处理逻辑删除(dr)。就一个空类,啥都不用写。
BaseSql ------ 执行 SQL。你写任何 SQL(单表、联表、子查询、CTE、窗口函数),用 list()、page()、row()、field()、columns() 五种方法覆盖所有结果形态。你的 SQL 浓度 100%,框架不加任何中间层。
你学这一个框架,就等于学会了所有场景下的数据库访问。没有两套语法,没有切换成本,没有学习曲线。
总结
持久层框架只有一个评价标准:
你写的代码里,SQL 浓度有多少?
- SQL 浓度 100%:你在直接跟数据库打交道。你写的每一行代码都在解决业务问题。单表联表用同一套条件构造,没有任何切换成本。
- SQL 浓度 50%:你一半时间在写业务 SQL,另一半时间在学框架的规则。而且联表查询时,你之前学的那套 Wrapper 语法基本作废。
- SQL 浓度 0%:你完全在跟框架打交道。SQL 长什么样,你得靠猜。联表查询?想都别想。
你要先想清楚你在跟谁打交道。
你是在跟 MyBatis 打交道,还是在跟 SQL 打交道?
你是在跟 Hibernate 打交道,还是在跟数据库打交道?
你是在跟 LambdaWrapper 打交道,还是在跟你想要的数据打交道?
想清楚这个问题,你就知道该怎么选了。
如果有一天你发现,你写的大部分代码不是在"干活",而是在"伺候框架"------那你就该换框架了。
这就是持久层框架的唯一评价标准。没有之一。
写在最后:SQL 浓度,是最好的面试题
你去面试的时候,面试官问你:"你怎么评价 MyBatis 和 MyBatis-Plus?"
你就问他一句话:
"你们项目里的代码,SQL 浓度是多少?"
如果对方愣住了,说明他从来没从这个角度想过问题。
如果对方说"我们用了 MyBatis-Plus 的 Wrapper,SQL 都是框架生成的,不用写 SQL",你就知道他们的项目里 SQL 浓度接近 0%。联表查询大概率全靠 @Select 注解或者 XML 手写,Wrapper 只在单表场景下自娱自乐。
如果对方说"我们用的是 SimpleDAO,条件继承 BaseCondition,SQL 完全手写,单表和联表用同一套条件,SQL 浓度接近 100%",那你就知道,这帮人懂行。
这个标准之所以有效,是因为它不问"你用什么框架",它问的是"你的代码跟数据库之间隔了几层"。
隔得越少,离本质越近。
离本质越近,噪音越少。
噪音越少,代码越干净。
这就是唯一的标准。没有之一。