第六篇:MyBatis 动态 SQL 为什么能如此灵活?
MyBatis 动态 SQL 为什么能如此灵活?
如果你写过 JDBC,应该都有过这样的经历,领导突然来了一个需求:支持按姓名查询
于是你写了一条 SQL:
select * from user where name = ?
过两天需求又变了,支持按年龄查询。
于是又写:
select * from user where age = ?
再过几天,支持姓名和年龄同时查询。
于是继续写:
select * from user
where name = ?
and age = ?
刚开始觉得没什么,但随着查询条件越来越多。
问题就来了,如果有 5 个查询条件,你要写多少种 SQL?
后来我们开始使用 MyBatis。
神奇的事情出现了。
一个 SQL 竟然就解决了所有问题。
<select id="queryUser">
select * from user
<where>
<if test="name != null">
and name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
</where>
</select>
传不同参数,执行不同 SQL,仿佛 SQL 会自己变化一样。
那么问题来了,MyBatis 到底是怎么做到的?
很多人以为 MyBatis 在拼字符串
第一次接触动态 SQL 时,很多人都会觉得:这不就是字符串拼接吗?
比如:
String sql = "select * from user";
if(name != null){
sql += " and name = ?";
}
确实能实现,但问题也很明显,SQL 越复杂,代码越难维护,而且各种 if、else 拼在一起,很容易出错,所以 MyBatis 采用了另外一种设计。
MyBatis 保存的不是 SQL
很多人以为,MyBatis 启动时,会把下面这段 SQL 保存起来
<if test="name != null">
and name = #{name}
</if>
实际上不是
MyBatis 保存的是:
SQL 的生成规则。
换句话说,它保存的不是结果而是过程。
SQL 在 MyBatis 眼里是一棵树
例如下面这段动态 SQL。
<where>
<if test="name != null">
and name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
</where>
在 MyBatis 看来,它不是一段字符串,而是一棵树。
大概类似这样:
where
├── if(name)
└── if(age)
执行时MyBatis 会从上往下遍历,看看哪些节点满足条件,满足就拼进去,不满足就跳过,最后生成真正执行的 SQL
SQL 是什么时候生成的?
答案是:执行时
例如:
userMapper.query(dto);
调用发生以后,MyBatis 才开始判断:
name != null ?
如果成立。
拼接:
and name = ?
继续判断:
age != null ?
如果成立。
再拼接:
and age = ?
最终得到:
select * from user
where name = ?
and age = ?
所以动态 SQL 的本质并不是修改 SQL。
而是:
根据参数动态生成 SQL。
test 为什么能执行?
很多人第一次看到:
<if test="name != null">
都会产生一个疑问,这里明明是字符串,为什么能判断真假?
原因是 MyBatis 引入了一套表达式引擎。
叫做:
OGNL
执行时。
MyBatis 会把参数对象放进去。
然后计算:
name != null
最终得到 true 或 false。
从而决定当前节点是否生效。
为什么 foreach 这么好用?
开发中最常见的其实不是 if。
而是:
<foreach>
例如:
<select id="selectBatch">
select * from user
where id in
<foreach collection="ids"
item="id"
open="("
separator=","
close=")">
#{id}
</foreach>
</select>
传入:
[1,2,3]
最终生成:
select * from user
where id in (?,?,?)
传入:
[1,2,3,4,5]
最终生成:
select * from user
where id in (?,?,?,?,?)
这也是动态 SQL 的能力。
根据参数动态生成最终 SQL。
看一眼源码
动态 SQL 的核心入口:
DynamicSqlSource
执行查询时。
最终会调用:
getBoundSql()
这里会遍历整棵 SQL 树,判断哪些节点需要参与生成,最后得到真正执行的 SQL,然后交给 Executor 去执行。
总结
很多人以为:
<if>
<where>
<foreach>
只是 MyBatis 提供的几个标签,实际上它们背后是一套非常经典的设计。
整个过程本质上是:
动态SQL
↓
解析成SQL树
↓
运行时判断条件
↓
生成最终SQL
↓
执行SQL
所以:
MyBatis 动态 SQL 的核心并不是字符串拼接,而是把 SQL 抽象成一棵可执行的节点树,在运行期间根据参数动态生成最终 SQL。
这也是为什么十几年过去了,MyBatis 的动态 SQL 依然没能轻易取代
上一篇:
《查询结果为什么能自动变成 Java 对象?》
下一篇:
《MyBatis 插件为什么这么强大?》
你第一次接触 MyBatis 动态 SQL 的时候,有没有觉得 <if> 和 <foreach> 特别神奇?
更多推荐



所有评论(0)