写点什么

【源码解析】MyBatis 动态 SQL

  • 2022 年 8 月 03 日
  • 本文字数:2747 字

    阅读完需:约 9 分钟

前言

📫 作者简介:小明 java 问道之路,专注于研究计算机底层,就职于金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的设计和架构📫 

🏆 Java 领域优质创作者、阿里云专家博主、华为云专家🏆

🔥 如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主哦


本文导读

你知道 Mybatis 动态 sql 是做什么,都有哪些?动态 sql 的执行原理吗?

一、动态 SQL 语句解析

这部分我们要知道 Mybatis 动态 sql 是做什么的?都有哪些动态 sql?动态 sql 的执行原理?

1、Mybatis 动态 sql 是做什么的?都有哪些动态 sql?

Mybatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能,Mybatis 提供了 9 种 动态 sql 标签 trim、where、set、foreach、if、choose、when、otherwise、bind。

2、动态 sql 的执行原理

        执行原理,MyBatis 会将 Mapper 映射文件中定义的 SQL 语句解析成 SqlSource 对象,其中的动态标签、SQL 语句文本等,会解析成对应类型的 SqlNode 对象包含了 “${}”占位符的动态 SQL 片段;动态 SQL 语句中使用到了 OGNL 表达式,读写 JavaBean 属性值、执行 JavaBean 方法这两个基础功能。

二、OGNL 表达式语言

        OGNL 表达式是相对完备的一门表达式语言,我们可以通过 “对象变量名称.方法名称(或属性名称)” 调用一个 JavaBean 对象的方法(或访问其属性),还可以通过 “@[类的完全限定名]@[静态方法(或静态字段)]” 调用一个 Java 类的静态方法(或访问静态字段)。

        MyBatis 为了提高 OGNL 表达式的工作效率,添加了一层 OgnlCache 来缓存表达式编译之后的结果(不是表达式的执行结果),OgnlCache 通过一个 ConcurrentHashMap<String, Object> 类型的集合(expressionCache 字段,静态字段)来记录 OGNL 表达式编译之后的结果。通过缓存拿到表达式编译的结果之后,OgnlCache 底层还会依赖上述示例中的 OGNL 工具类以及 OgnlContext 完成表达式的执行。

三、DynamicContext 源码解析

在 MyBatis 解析一条动态 SQL 语句的时候,整个流程非常长,其中涉及多层方法的调用、方法的递归、复杂的循环等,其中产生的中间结果需要有一个地方进行存储,那就是 DynamicContext 上下文对象

        DynamicContext 中有两个核心属性:一个是 sqlBuilder 字段(StringJoiner 类型),用来记录解析之后的 SQL 语句;另一个是 bindings 字段,用来记录上下文中的一些 KV 信息。                DynamicContext 定义了一个 ContextMap 内部类,ContextMap 用来记录运行时用户传入的、用来替换“#{}”占位符的实参。在 DynamicContext 构造方法中,会根据传入的实参类型决定如何创建对应的 ContextMap 对象,核心代码如下:

public DynamicContext(Configuration configuration, Object parameterObject) {    if (parameterObject != null && !(parameterObject instanceof Map)) {        // 对于非Map类型的实参,会创建对应的MetaObject对象,并封装成ContextMap对象        MetaObject metaObject = configuration.newMetaObject(parameterObject);
        boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());        bindings = new ContextMap(metaObject, existsTypeHandler);    } else {        // 对于Map类型的实参,这里会创建一个空的ContextMap对象        bindings = new ContextMap(null, false);    }    // 这里实参对应的Key是_parameter    bindings.put(PARAMETER_OBJECT_KEY, parameterObject);    bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());}
复制代码


读取这个 ContextMap 的地方主要是在 OGNL 表达式中,也就是在 DynamicContext 中定义了一个静态代码块,指定了 OGNL 表达式读写 ContextMap 集合的逻辑,这部分读取逻辑封装在 ContextAccessor 中。

public class DynamicContext {  public static final String PARAMETER_OBJECT_KEY = "_parameter";  static {    OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());  }
static class ContextAccessor implements PropertyAccessor { @Override public Object getProperty(Map context, Object target, Object name) throws OgnlException { Map map = (Map) target; // getProperty() 方法会将传入的 target 参数(实际上就是 ContextMap)转换为 Map // 并先尝试按照 Map 规则进行查找 Object result = map.get(name); if (map.containsKey(name) || result != null) { return result; } // 查找失败之后,会尝试获取“_parameter”对应的 parameterObject 对象 // 从 parameterObject 中获取指定的 Value 值。 Object parameterObject = map.get(PARAMETER_OBJECT_KEY); if (parameterObject instanceof Map) { return ((Map)parameterObject).get(name); } return null; }}
复制代码

四、SqlNode 源码解析

在 MyBatis 处理动态 SQL 语句的时候,会将动态 SQL 标签解析为 SqlNode 对象,多个 SqlNode 对象就是通过组合模式组成树形结构供上层使用的

public interface SqlNode {    // apply()方法会根据用户传入的实参,解析该SqlNode所表示的动态SQL内容并    // 将解析之后的SQL片段追加到DynamicContext.sqlBuilder字段中暂存。    // 当SQL语句中全部的动态SQL片段都解析完成之后,就可以从DynamicContext.sqlBuilder字段中    // 得到一条完整的、可用的SQL语句了    boolean apply(DynamicContext context);}
复制代码


TrimSqlNode 对应 MyBatis 动态 SQL 语句中的 <trim> 标签;ChooseSqlNode 在 MyBatis 的动态 SQL 语句中,我们可以使用 <choose><when> 和 <otherwise> 三个标签来实现类似的效果;ForeachSqlNode 对应 <foreach> 标签对一个集合进行迭代;VarDeclSqlNode 抽象了 <bind> 标签;<choose> 标签会被 MyBatis 解析成 ChooseSqlNode 对象<otherwise> 标签会被解析成 MixedSqlNode 对象。StaticTextSqlNode 用于表示非动态的 SQL 片段;TextSqlNode 实现抽象了包含 “${}”占位符的动态 SQL 片段;IfSqlNode 实现类对应了动态 SQL 语句中的 <if> 、<when> 标签

总结

本部分说明了动态 SQL 语句中使用的 OGNL 表达式语言,动态 SQL 语句中占位符的处理逻辑。分析了 DynamicContext 对象源码,其中维护了解析动态 SQL 语句上下文信息;随后我还分析了组合模式,因为它是 MyBatis 组合各动态 SQL 节点的设计思想。

发布于: 刚刚阅读数: 3
用户头像

物有本末,事有终始。知所先后,则近道矣 2020.03.20 加入

🏆CSDNJava领域优质创作者/阿里云专家博主/华为云专家 📫就职某大型金融互联网公司后端高级工程师 👍专注于研究计算机底层/Java/架构/设计模式/算法

评论

发布
暂无评论
【源码解析】MyBatis动态SQL_源码分析_小明Java问道之路_InfoQ写作社区