Myabtis-动态sql解析流程

目录

1.DynamicContext 上下文

2.sqlNode接口及其主要实现类

​​​​​​​2.1. StaticTextSqlNode 和 MixedSqlNode

2.2TextSqlNode

3. IfSqlNode

4.TrimSqlNode


​​​​​​​

1.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());

}

2.sqlNode接口及其主要实现类

  • 组合模式(有时候也被称为“部分-整体”模式)是将同一类型的多个对象组合成一个树形结构。在使用这个树形结构的时候,我们可以像处理一个对象那样进行处理,而不用关心其复杂的树形结构
  •  SqlNode 对象就是通过组合模式组成树形结构供上层使用
public interface SqlNode {

    // apply()方法会根据用户传入的实参,解析该SqlNode所表示的动态SQL内容并

    // 将解析之后的SQL片段追加到DynamicContext.sqlBuilder字段中暂存。

    // 当SQL语句中全部的动态SQL片段都解析完成之后,就可以从DynamicContext.sqlBuilder字段中

    // 得到一条完整的、可用的SQL语句了

    boolean apply(DynamicContext context);

}

​​​​​​​2.1. StaticTextSqlNode 和 MixedSqlNode

  • StaticTextSqlNode 用于表示非动态的 SQL 片段,其中维护了一个 text 字段(String 类型),用于记录非动态 SQL 片段的文本内容,其 apply() 方法会直接将 text 字段值追加到 DynamicContext.sqlBuilder 的最末尾。
  • MixedSqlNode 在整个 SqlNode 树中充当了树枝节点,其中维护了一个 List<SqlNode> 集合用于记录 MixedSqlNode 下所有的子 SqlNode 对象。MixedSqlNode 对于 apply() 方法的实现也相对比较简单,核心逻辑就是遍历 List<SqlNode> 集合中全部的子 SqlNode 对象并调用 apply() 方法,由子 SqlNode 对象完成真正的动态 SQL 处理逻辑。

2.2TextSqlNode

  • TextSqlNode 实现抽象了包含 “${}”占位符的动态 SQL 片段。TextSqlNode 通过一个 text 字段(String 类型)记录了包含“${}”占位符的 SQL 文本内容,在 apply() 方法实现中会结合用户给定的实参解析“${}”占位符
  • 使用 GenericTokenParser 识别“${}”占位符,在识别到占位符之后,会通过 BindingTokenParser 将“${}”占位符替换为用户传入的实参。BindingTokenParser 继承了TokenHandler 接口,在其 handleToken() 方法实现中,会根据 DynamicContext.bindings 这个 ContextMap 中的 KV 数据替换 SQL 语句中的“${}”占位符
public boolean apply(DynamicContext context) {

    // 创建GenericTokenParser解析器,这里指定的占位符的起止符号分别是"${"和"}"

    GenericTokenParser parser = createParser(

            new BindingTokenParser(context, injectionFilter));

    // 将解析之后的SQL片段追加到DynamicContext暂存

    context.appendSql(parser.parse(text));

    return true;

}
public String handleToken(String content) {

    // 获取用户提供的实参数据

    Object parameter = context.getBindings().get("_parameter");

    if (parameter == null) { // 通过value占位符,也可以查找到parameter对象

        context.getBindings().put("value", null);

    } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {

        context.getBindings().put("value", parameter);

    }

    // 通过Ognl解析"${}"占位符中的表达式,解析失败的话会返回空字符串

    Object value = OgnlCache.getValue(content, context.getBindings());

    String srtValue = value == null ? "" : String.valueOf(value); 

    checkInjection(srtValue); // 对解析后的值进行过滤

    return srtValue; // 通过过滤的值才能正常返回

}

3. IfSqlNode

  • IfSqlNode 实现类对应了动态 SQL 语句中的 标签,在 MyBatis 的 <if> 标签中使用可以通过 test 属性指定一个表达式,当表达式成立时,<if> 标签内的 SQL 片段才会出现在完整的 SQL 语句中。
  • 在 IfSqlNode 中,通过 test 字段(String 类型)记录了 <if> 标签中的 test 表达式,通过 contents 字段(SqlNode 类型)维护了 <if> 标签下的子 SqlNode 对象。在 IfSqlNode 的 apply() 方法实现中,会依赖 ExpressionEvaluator 工具类解析 test 表达式,只有 test 表达式为 true,才会调用子 SqlNode 对象(即 contents 字段)的 apply() 方法。需要说明的是:这里使用到的 ExpressionEvaluator 工具类底层也是依赖 OGNL 表达式实现 test 表达式解析的。

4.TrimSqlNode

在使用 <trim> 标签的时候,我们可以指定 prefix 和 suffix 属性添加前缀和后缀,也可以指定 prefixesToOverrides 和 suffixesToOverrides 属性来删除多个前缀和后缀(使用“|”分割不同字符串)。在 TrimSqlNode 中维护了同名的四个字段值,即 prefix 字段、suffix 字段(这两个是 String 类型)以及 prefixesToOverride 字段、suffixesToOverride 字段(这两个是 List<String> 类型)。

public boolean apply(DynamicContext context) {

    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);

    // 首先执行子SqlNode对象的apply()方法完成对应动态SQL片段的解析

    boolean result = contents.apply(filteredDynamicContext);

    // 使用FilteredDynamicContext.applyAll()方法完成前后缀的处理操作

    filteredDynamicContext.applyAll();

    return result;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值