mybatis之脚本解析器
本篇主要来介绍一下 mybatis 的脚本解析功能,基于mybatis 3.4.6。
知识点
- 什么是脚本解析器
- 解析原理
什么是脚本解析器
第一眼看到脚本解析器这个说法,你肯定会一脸懵逼,这百度上都搜不到啊。没错,这是我个人定义的,为什么取名叫脚本解析器呢,因为我是根据代码中来取的,基于两点原因:
1、对应的包名叫:scripting
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
当然还有where
、foreach
等常用的标签。这些标签明显不是 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 文件进行解析的。
可以看到,它就是在解析 mapper 节点,跟进去看下就会更清晰
这里对 mapper 下的各个标签进行了解析,我们重点看 CURD 这一块,也就是buildStatementFromContext
可以看到遍历了每个节点进行解析,继续往下
可以看到这里对节点下的标签再做解析,这里面有很多细节的标签解析,比如使用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>)
,
这个类非常重要,所有脚本解析必然经过它来产生SqlSource
,它提供了xml和注解方式的解析,虽然LanguageDriver
有另外一个派生类RawLanguageDriver
,实际上这个派生类已经不用了。在XMLLanguageDriver
中会用到XMLScriptBuilder
来做实际的脚本解析。
这里可以看到会产生两种类型的SqlSource
,分别为DynamicSqlSource
和RawSqlSource
,这里说一下什么是DynamicSqlSource
,就是指脚本中带有${}这种占位符的或者带if
、where
等这些标签的,反正就是不能直接拿来用的 sql。重点看下这句
在这里会看到通过名称获取对应的处理器NodeHandler
,对于每一种标签,mybatis都定义了一种处理器
另外需要知道的一点:标签类的节点类型是 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
并加入到org.apache.ibatis.session.Configuration
的mappedStatements中,所以如果我们要用,也可以直接去 Configuration 中去获取。
脚本解析完了,但是我们的例子还没讲完,不是要结合
标签吗,那么哪里在使用呢?
之前我们说执行器的时候,说到在执行的时候要获取到BoundSql
,这又是什么玩意儿?别急,我们来看一下
可以看到这个是从 MappedStatement 中获取的,也就是我们上面解析完之后生成的那个东西。进入方法getBoundSql
看下
看到了吗,这里最终用的就是之前生成的SqlSource
来获取到的,这个SqlSource
在这里只会有两种:RawSqlSource
和DynamicSqlSource
。说白了就是静态文本和动态文本,静态文本就不说了,直接拿到可用的sql,而动态文本,则需要去解析
这里传入了实际的参数对象,所以动态文本缺少的东西都具备了,直接可以解析出结果,在rootSqlNode.apply(context);
这行就调用了实际的标签处理器进行处理,比如我们例子里的就是IfSqlNode
可以看到,这里用了ExpressionEvaluator
进行处理,直译就是表达式翻译器。在这里面用到了OgnlCache
进行处理,最终用的是Ognl进行处理
OgnlCache
只是为了提高性能,什么是 Ognl 呢?参考这篇文章。从
标签处理来看,为true就会加到对应的文本后面,当然如果有占位符的则会解析占位符。获取到BoundSql
之后,脚本解析器基本工作都完成了。
这里可以看出BoundSql
和SqlSource
的区别,可以理解为BoundSql
就是带了实际参数对象的SqlSource
,后面再由类型处理器去逐个映射。
总结
脚本处理器内容还是挺多的,学完这一篇我相信大家都会有不少收获,甚至我们可以自定义标签来做处理。另外我们在使用mybatis的时候会发现Ognl包是在mybatis里的,其实是maven的一种打包方式。
参考资料
Ognl:https://www.cnblogs.com/ends-...
maven将依赖包打进当前包:https://blog.csdn.net/yangguo...