共计 3591 个字符,预计需要花费 9 分钟才能阅读完成。
mybatis 之脚本解析器
本篇次要来介绍一下 mybatis 的脚本解析性能,基于 mybatis 3.4.6。
知识点
- 什么是脚本解析器
- 解析原理
什么是脚本解析器
第一眼看到脚本解析器这个说法,你必定会一脸懵逼,这百度上都搜不到啊。没错,这是我集体定义的,为什么取名叫脚本解析器呢,因为我是依据代码中来取的,基于两点起因:
1、对应的包名叫:scripting
2、次要解析的是 xml 等文本内容,在我看来相当于脚本(sql 语句其实也是一种脚本)
介绍完名称由来之后,再来介绍一下它到底是什么。
咱们平时在应用 Mybatis 的时候,个别只有两步:
1、定义一个接口,个别是 xxxMapper,定义了一些 CURD 接口;
2、定义上一步 Mapper 接口对应的配置文件,个别是 xxxMapper.xml,而后在配置文件中定义各个 CURD 接口对应的 sql 就能够了;
在配置文件中,咱们个别都会用到动静 sql,比方
<if test="title != null">
AND title like #{title}
</if>
再比方
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
当然还有 where
、foreach
等罕用的标签。这些标签显著不是 sql 自身的语法能解析的,为什么咱们却能这么用呢?说到这里想必大家曾经晓得了,这就是脚本解析器施展的作用。
解析原理
在理解脚本解析器是什么后,咱们基于一个例子来看下它是怎么对脚本做解析的。先定义一个 Mapper
@Repository
public interface UserInfoMapper {List<UserInfo> select(String userName, String nickName);
}
再定义一个对应 UserInfoMapper.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatisanalyze.mapper.UserInfoMapper">
<select id="select" resultType="com.example.mybatisanalyze.po.UserInfo">
select * from user_info where user_name = #{userName}
<if test="nickName !=''">
AND nick_name = #{nickName}
</if>
</select>
</mapper>
例子定义结束,这里咱们能看到次要用到了 if <test = "">
标签,接着来阐明一下 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。什么意思呢?比方标签类的 <if>
这种就认为是节点元素,所以叫 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 中去获取。
脚本解析完了,然而咱们的例子还没讲完,不是要联合 <if>
标签吗,那么哪里在应用呢?
之前咱们说执行器的时候,说到在执行的时候要获取到BoundSql
,这又是什么玩意儿?别急,咱们来看一下
能够看到这个是从 MappedStatement 中获取的,也就是咱们下面解析完之后生成的那个货色。进入办法 getBoundSql
看下
看到了吗,这里最终用的就是之前生成的 SqlSource
来获取到的,这个 SqlSource
在这里只会有两种:RawSqlSource
和DynamicSqlSource
。说白了就是动态文本和动静文本,动态文本就不说了,间接拿到可用的 sql,而动静文本,则须要去解析
这里传入了理论的参数对象,所以动静文本短少的货色都具备了,间接能够解析出后果,在 rootSqlNode.apply(context);
这行就调用了理论的标签处理器进行解决,比方咱们例子里的就是IfSqlNode
能够看到,这里用了 ExpressionEvaluator
进行解决,直译就是表达式翻译器。在这外面用到了 OgnlCache
进行解决,最终用的是 Ognl 进行解决
OgnlCache
只是为了进步性能,什么是 Ognl 呢?参考这篇文章。从 <if>
标签解决来看,为 true 就会加到对应的文本前面,当然如果有占位符的则会解析占位符。获取到 BoundSql
之后,脚本解析器根本工作都实现了。
这里能够看出 BoundSql
和SqlSource
的区别,能够了解为 BoundSql
就是带了理论参数对象的SqlSource
,前面再由类型处理器去一一映射。
总结
脚本处理器内容还是挺多的,学完这一篇我置信大家都会有不少播种,甚至咱们能够自定义标签来做解决。另外咱们在应用 mybatis 的时候会发现 Ognl 包是在 mybatis 里的,其实是 maven 的一种打包形式。
参考资料
Ognl:https://www.cnblogs.com/ends-…
maven 将依赖包打进以后包:https://blog.csdn.net/yangguo…