Mybatis的sql组装详解
上一篇分析了SqlSession执行sql的过程,其中并没有分析sql是从哪里来的,今天就来仔细分析下。
Sql来源
从上一篇的最后一步执行sql那里倒推sql的来源,源码主要过程如下图:
可以看到最后是通过BoundSql直接获取的sql,然后往前倒推最后发现是通过MappedStatement的getBoundSql方法返回的。MappedStatement在之前分析mapper的时候知道一个执行sql对应一个MappedStatement对象,它封装有mybatis中需要执行一条sql的所有信息,所以从这里获取也是理所应当的。
MappedStatement的getBoundSql方法
那么就来看下MappedStatement的getBoundSql方法吧,源码如下图:
首先是右边MappedStatement的getBoundSql方法,这次改了下sql传递了两个参数,但是对程序基本没什么影响。可以看到BoundSql是通过SqlSource创建的,通过debug知道是DynamicSqlSource对象。
左边是DynamicSqlSource的getBoundSql方法,可以先看后面的创建BoundSql对象,是通过SqlSource初始化的,而创建SqlSource对象需要的第一个参数是通过context.getSql()得到的。
而在getBoundSql方法第一步是初始化了context,从上图的debug可以看到目前context的信息,bindings属性已经通过初始化把参数设置进去,而sqlBuilder还是一个空字符串。关键代码就在当前打断点的“rootSqlNode.apply(context);”这行代码。
从上图可以看到rootSqlNode是一个MixedSqlNode对象,MixedSqlNode对象的apply方法是遍历属性contents的所有元素并执行它们的apply方法,可以看到contents只有一个TextSqlNode对象的元素。所以最终来到TextSqlNode对象。
TextSqlNode对象的text属性保存的就是还没有进行处理的sql。
TextSqlNode处理sql
TextSqlNode处理sql的源码如下图:
右侧是TextSqlNode的apply方法,参数context就是上一步需要的context,他里面包含有请求参数,从上一张源码图可以知道它的属性sqlBuilder此时还是一个空字符串,而apply就是去拼接sqlBuilder。
apply方法首先创建了一个GenericTokenParser对象,GenericTokenParser对象有三个属性openToken、closeToken、handler。其中openToken、closeToken分别对应字符串”${”、”}”,而handler的属性context对应传递进来的context,injectionFilter属性是TextSqlNode的injectionFilter。apply第二行代码“context.appendSql(parser.parse(text));”可以看出来parser.parse(text)就是生成sql的代码。其中text是TextSqlNode中没有处理的原始sql。
所以sql生成在GenericTokenParser的parse方法,parse方法的关键源码如上图左侧。主要步骤分析如下:
1、从sql找到第一个“${”位置start,获取到元素sql(text)的字符数组src;
2、把src从开始到start处的字符拼接到结果builder上,也就是把“${”的内容拼接到结果上;
3、从start+2的位置开始找“}”的位置end,从src数组里取出“${”、“}”之间的字符组成字符串并根据字符串从hander中取出字符串对应的参数值。并把值拼接到结果builder上。
最后还有一点代码没有截取到“start = text.indexOf(openToken,offset);
}while (start > -1);”也就是第4步。
4、继续找到下一个“${”并拼接。最终组成完整的sql。
实际上GenericTokenParser的parse方法还是比较简单的,就是把传递进来的参数text中openToken、closeToken中间的内容替换成对应的参数。
总结
从之前的分析我们知道一个MappedStatement对应一个sql,那么如何从MappedStatement获取组装好的sql呢,实际上是MappedStatement的属性sqlSource。
而sqlSource实际上是依靠rootSqlNode,在之前我们分析过解析mapper文件是把sql生成了嵌套的各种SqlNode子类。今天就看到他们的使用了。不过今天的算是比较简单的,后面来一个稍微复杂一点的看看sql的解析过程。
Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!