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
@Repositorypublic 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...