mybatis之脚本解析器

mybatis之脚本解析器

本篇主要来介绍一下 mybatis 的脚本解析功能,基于mybatis 3.4.6。

知识点

  • 什么是脚本解析器
  • 解析原理

什么是脚本解析器

第一眼看到脚本解析器这个说法,你肯定会一脸懵逼,这百度上都搜不到啊。没错,这是我个人定义的,为什么取名叫脚本解析器呢,因为我是根据代码中来取的,基于两点原因:

1、对应的包名叫:scripting
mybatis之脚本解析器_第1张图片
2、主要解析的是 xml 等文本内容,在我看来相当于脚本(sql语句其实也是一种脚本)

介绍完名称由来之后,再来介绍一下它到底是什么。

我们平时在使用 Mybatis 的时候,一般只要两步:

1、定义一个接口,一般是xxxMapper,定义了一些CURD接口;

2、定义上一步 Mapper 接口对应的配置文件,一般是 xxxMapper.xml,然后在配置文件中定义各个CURD接口对应的 sql 就可以了;

在配置文件中,我们一般都会用到动态 sql,比如

  
    AND title like #{title}
  

再比如

  
    
      AND title like #{title}
    
    
      AND author_name like #{author.name}
    
    
      AND featured = 1
    
  

当然还有whereforeach等常用的标签。这些标签明显不是 sql 本身的语法能解析的,为什么我们却能这么用呢?说到这里想必大家已经知道了,这就是脚本解析器发挥的作用。

解析原理

在了解脚本解析器是什么后,我们基于一个例子来看下它是怎么对脚本做解析的。先定义一个Mapper

@Repository
public interface UserInfoMapper {
    List select(String userName, String nickName);
}

再定义一个对应 UserInfoMapper.xml 文件





    
    

例子定义完毕,这里我们能看到主要用到了 if 标签,接着来说明一下 mybatis 是如何对上面的例子进行解析并生成最终 sql 的。

首先我们找到这个类org.apache.ibatis.builder.xml.XMLMapperBuilder,看名字估计也猜到了,它是对我们上面定义的 UserInfoMapper.xml 文件进行解析的。

mybatis之脚本解析器_第2张图片

可以看到,它就是在解析 mapper 节点,跟进去看下就会更清晰

mybatis之脚本解析器_第3张图片

这里对 mapper 下的各个标签进行了解析,我们重点看 CURD 这一块,也就是buildStatementFromContext

mybatis之脚本解析器_第4张图片

可以看到遍历了每个节点进行解析,继续往下

mybatis之脚本解析器_第5张图片

可以看到这里对节点下的标签再做解析,这里面有很多细节的标签解析,比如使用org.apache.ibatis.builder.xml.XMLIncludeTransformer#applyIncludes(org.w3c.dom.Node, java.util.Properties, boolean)include 标签做解析,比如对selectKey标签做解析,这部分不详细介绍,接着看org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class)

mybatis之脚本解析器_第6张图片

这个类非常重要,所有脚本解析必然经过它来产生SqlSource,它提供了xml和注解方式的解析,虽然LanguageDriver有另外一个派生类RawLanguageDriver,实际上这个派生类已经不用了。在XMLLanguageDriver中会用到XMLScriptBuilder来做实际的脚本解析。

mybatis之脚本解析器_第7张图片

这里可以看到会产生两种类型的SqlSource,分别为DynamicSqlSourceRawSqlSource,这里说一下什么是DynamicSqlSource,就是指脚本中带有${}这种占位符的或者带ifwhere等这些标签的,反正就是不能直接拿来用的 sql。重点看下这句

mybatis之脚本解析器_第8张图片

在这里会看到通过名称获取对应的处理器NodeHandler,对于每一种标签,mybatis都定义了一种处理器

mybatis之脚本解析器_第9张图片

另外需要知道的一点:标签类的节点类型是 Node.ELEMENT_NODE,文本类的是 Node.TEXT_NODE 或者 Node.CDATA_SECTION_NODE。什么意思呢?比如标签类的这种就认为是节点元素,所以叫Node.ELEMENT_NODE,而中间的内容,比如:select * from user_info where user_name = #{userName},这种就认为是文本类型的。这里执行完之后,就生产了一个 sqlNode,最终被包装成一个 SqlSource 返回,也就是说每一条 sql 标签(select|insert|update|delete)都会有自己的SqlSource。在org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode解析完之后,最后会生成一个MappedStatement

image.png

并加入到org.apache.ibatis.session.Configuration的mappedStatements中,所以如果我们要用,也可以直接去 Configuration 中去获取。

脚本解析完了,但是我们的例子还没讲完,不是要结合标签吗,那么哪里在使用呢?

之前我们说执行器的时候,说到在执行的时候要获取到BoundSql,这又是什么玩意儿?别急,我们来看一下

mybatis之脚本解析器_第10张图片

可以看到这个是从 MappedStatement 中获取的,也就是我们上面解析完之后生成的那个东西。进入方法getBoundSql看下

mybatis之脚本解析器_第11张图片

看到了吗,这里最终用的就是之前生成的SqlSource来获取到的,这个SqlSource在这里只会有两种:RawSqlSourceDynamicSqlSource。说白了就是静态文本和动态文本,静态文本就不说了,直接拿到可用的sql,而动态文本,则需要去解析

mybatis之脚本解析器_第12张图片

这里传入了实际的参数对象,所以动态文本缺少的东西都具备了,直接可以解析出结果,在rootSqlNode.apply(context);这行就调用了实际的标签处理器进行处理,比如我们例子里的就是IfSqlNode

mybatis之脚本解析器_第13张图片

可以看到,这里用了ExpressionEvaluator进行处理,直译就是表达式翻译器。在这里面用到了OgnlCache进行处理,最终用的是Ognl进行处理

mybatis之脚本解析器_第14张图片

OgnlCache只是为了提高性能,什么是 Ognl 呢?参考这篇文章。从标签处理来看,为true就会加到对应的文本后面,当然如果有占位符的则会解析占位符。获取到BoundSql之后,脚本解析器基本工作都完成了。

这里可以看出BoundSqlSqlSource的区别,可以理解为BoundSql就是带了实际参数对象的SqlSource,后面再由类型处理器去逐个映射。

总结

脚本处理器内容还是挺多的,学完这一篇我相信大家都会有不少收获,甚至我们可以自定义标签来做处理。另外我们在使用mybatis的时候会发现Ognl包是在mybatis里的,其实是maven的一种打包方式。

参考资料

Ognl:https://www.cnblogs.com/ends-...

maven将依赖包打进当前包:https://blog.csdn.net/yangguo...

你可能感兴趣的