MyBatis 动态 SQL 的生成原理主要基于其灵活的 XML 配置解析机制和动态标签的递归处理逻辑,通过以下核心步骤实现 SQL 的动态拼接:
1. XML 配置解析
当 MyBatis 初始化时,会通过 XPath 解析器加载 Mapper XML 文件,提取 <select>
、<insert>
等标签中的 SQL 语句。对于动态 SQL 标签(如 <if>
、<choose>
、<foreach>
),MyBatis 会将其转换为特定的 SqlNode 对象,构建一个动态 SQL 节点树。
2. 动态标签的 SqlNode 实现
每个动态标签对应一个 SqlNode
接口的实现类,例如:
<if>
→IfSqlNode
<choose>
→ChooseSqlNode
<foreach>
→ForEachSqlNode
<where>
→WhereSqlNode
这些 SqlNode
对象在解析时会被组织成树形结构,用于后续的动态拼接。
3. OGNL(Object-Graph Navigation Language)表达式求值
动态标签中的 test
属性(如 <if test="name != null">
)使用 OGNL(对象图导航语言) 表达式进行条件判断。MyBatis 在运行时:
- 从参数对象中提取属性值。
- 通过
OgnlCache
缓存解析表达式,提高性能。 - 根据表达式结果(
true
/false
)决定是否包含该 SQL 片段。
4. SQL 动态拼接过程
当执行 Mapper 方法时,MyBatis 会:
- 生成
SqlSource
对象:根据 XML 配置构建DynamicSqlSource
,封装动态 SQL 节点树。 - 获取
BoundSql
对象:- 遍历
SqlNode
树,递归处理每个节点。 - 对
<if>
节点:判断条件,决定是否拼接子节点 SQL。 - 对
<foreach>
节点:遍历集合,展开成IN
子句或批量插入语句。 - 对
<where>
节点:自动处理WHERE
关键字和条件前的AND
/OR
。
- 遍历
- 参数替换:将
#{}
占位符替换为?
,并生成ParameterMapping
对象,记录参数位置和类型。
5. 缓存机制
MyBatis 对解析后的 SqlSource
和 BoundSql
进行缓存,避免重复解析 XML。首次执行时生成并缓存,后续直接复用,提升性能。
示例:动态 SQL 生成流程
假设有以下 XML 配置:
<select id="findUser" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">AND name = #{name}</if>
<if test="age != null">AND age = #{age}</if>
</where>
</select>
生成过程:
- 解析
<where>
标签为WhereSqlNode
,内部包含两个IfSqlNode
。 - 执行时,若
name
和age
均非空:WhereSqlNode
自动添加WHERE
关键字。- 两个
<if>
条件成立,拼接AND name = ? AND age = ?
。 - 最终 SQL:
SELECT * FROM user WHERE name = ? AND age = ?
- 若
age
为空,则拼接为:SELECT * FROM user WHERE name = ?
关键设计思想
- 组合模式:通过
SqlNode
树形结构实现动态 SQL 的灵活组合。 - 责任链模式:每个
SqlNode
依次处理 SQL 片段,决定是否包含或跳过。 - 表达式语言:利用 OGNL 实现简洁的条件判断。
性能优化
- 预编译:动态 SQL 最终生成带占位符的 SQL,由 JDBC 预编译执行,防止 SQL 注入。
- 缓存:减少 XML 解析和表达式求值次数。
通过以上机制,MyBatis 实现了高效、灵活的动态 SQL 生成,适应复杂多变的查询场景。