在BaseExecutor中调用query方法时,会触发一个BoundSQL的东西
![c1fd4911c881e6832d7bb871b7e7a245.png](https://i-blog.csdnimg.cn/blog_migrate/22581674bc1a703d03fdc66b8cc3d466.png)
除此之外,在BaseStatementHandler中也调用过BoundSQL
![397b5baedaae48fe534f006f74096d52.png](https://i-blog.csdnimg.cn/blog_migrate/d2f571db780ee07fca6238af0b14a291.jpeg)
那么BoundSQL到底是什么东西?
![9bb29414c58b2c718d521d9bf60bad29.png](https://i-blog.csdnimg.cn/blog_migrate/c6b4ecaa1e2980d963c32189220224fe.jpeg)
sql:可执行的语句
parameterMapping:sql中的参数位置是"?",通过parameterMapping进行映射
parameterObject:就是传递过来的参数
additionParameters:对原始参数进行运算后得出的新的参数,例如将数组中的参数打散进行分离成Map形式与参数进行一一对应
metaParameters:操纵parameterObject属性
即,BoundSQL包含了我们所要执行的SQL的所有信息,有了BoundSQL就可以发起SQL调用了
动态SQL的定义:
![593e8ec025d17eb02983c89729a98c98.png](https://i-blog.csdnimg.cn/blog_migrate/584b1192bfc6af9235cec24b14bf92b8.png)
在每次执行SQL的时候发生的,即每执行一次SQL都会重新进行一次编译和解析
例如:此时有一个SQL脚本
![230b68d6c378220bc8360eae4385e0ed.png](https://i-blog.csdnimg.cn/blog_migrate/502fa86d6a9b0b47a81fd87c66e50e85.png)
select * from users:静态文本元素
<where><if>:脚本表达式
动态SQL就是去解析静态文本元素和脚本表达式最后形成select语句
在执行脚本解析的时候必须要带上所需的参数才可以生成最终的SQL语句
![e029000a81d9ea2fc2d6ccd5c4ad0e85.png](https://i-blog.csdnimg.cn/blog_migrate/11c7a77f18a9142695ab407f95c0df0a.jpeg)
回忆一下SQL脚本语言树
![72109cb9bc0ed09855817bc224b366ba.png](https://i-blog.csdnimg.cn/blog_migrate/9d6d2bb2b8d2f97b9db9995109e4c16a.jpeg)
在使用动态SQL的时候经常会看到一写表达式,例如:
![313d378868f72b5df8cf7ce8ca0fdb50.png](https://i-blog.csdnimg.cn/blog_migrate/5ba17ae3a48c17e78598b97d16ecea7f.png)
这种表达式就称之为OGNL表达式
OGNL表达式
![ceea404ec5a5c97ab21be717b76dc0e8.png](https://i-blog.csdnimg.cn/blog_migrate/062524ccbcda42ed841e0ed44842105e.jpeg)
- 访问属性
public class BoundSqlTest {
private static Configuration configuration;
private static SqlSessionFactory factory = null;
static {
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
factory = factoryBuilder.build(StatementHandlerTest.class.getResourceAsStream("/mybatis-config.xml"));
configuration = factory.getConfiguration();
configuration.setLazyLoadTriggerMethods(new HashSet<>());
}
@Test
public void ognlTest() {
//表达式执行器
ExpressionEvaluator evaluator = new ExpressionEvaluator();
Blog blog = Mock.newBlog();
//1.访问属性
boolean b = evaluator.evaluateBoolean("id != null && author.name != null", blog);
System.out.println(b);
}
}
![deaaad2b806cc5fb7ec9a94afa2bb7ab.png](https://i-blog.csdnimg.cn/blog_migrate/22f2630b8795e2c50dca3e8085a93b8c.png)
如果将author设置为null,则就会报错
@Test
public void ognlTest() {
//表达式执行器
ExpressionEvaluator evaluator = new ExpressionEvaluator();
Blog blog = Mock.newBlog();
blog.setAuthor(null);
//1.访问属性
boolean b = evaluator.evaluateBoolean("id != null && author.name != null", blog);
System.out.println(b);
}
![b6ae2eb2a69bbd7f74ceaa90ff71c9b9.png](https://i-blog.csdnimg.cn/blog_migrate/e00556e60d15a2c3775fb4701e5498f8.png)
- 调用方法
@Test
public void ognlTest() {
//表达式执行器
ExpressionEvaluator evaluator = new ExpressionEvaluator();
Blog blog = Mock.newBlog();
blog.setAuthor(null);
//2.调用方法
boolean b1 = evaluator.evaluateBoolean("comments!=null && comments.size()>0", blog);
System.out.println(b1);
}
![8a46b5071e1fb51325e0f5c274bb9b51.png](https://i-blog.csdnimg.cn/blog_migrate/5353d8c48e80c73d94b382b2545bc248.png)
可以使省略方法的括号
evaluator.evaluateBoolean("comments!=null && !comments.isEmpty", blog);
- 传递参数
evaluator.evaluateBoolean("comments!=null && comments.get(0).body!=null", blog);
evaluator.evaluateBoolean("comments!=null && comments[0].body!=null", blog);
- 遍历集合
Iterable<?> comments = evaluator.evaluateIterable("comments", blog);
for (Object comment : comments) {
System.out.println(comment);
}
![66d947580274b27a78aa0b14f36e483f.png](https://i-blog.csdnimg.cn/blog_migrate/6ce1690e4838e6660790462795e979c8.jpeg)
脚本的解析流程
由SqlSource(SQL数据源)编程BoundSql(SQL包)的过程
![521da30d9e9beade82f233db8f85ad82.png](https://i-blog.csdnimg.cn/blog_migrate/a9d7a8f1502fde36f4e33397f5d2f658.png)
前面说过,有了BoundSQL就有了执行SQL所需的全部写信息,所以BoundSQL需要sql语句、执行sql所需的参数、参数和参数值的映射(sql中的"?")
![43d10038f18a52e1bbfe16007528084b.png](https://i-blog.csdnimg.cn/blog_migrate/771224f79efb45fd508dacc6626d4d0c.png)
即,需要将SqlSource编程BoundSQL,转换的时机在每次发起SQL调用的时候都会执行一次
![a4c9e618b85e0898eadb85a9b365adb6.png](https://i-blog.csdnimg.cn/blog_migrate/609f3d5f1876827cf480684634135c28.jpeg)
最后,这个脚本(XML)会变成一个数据源sqlSource
![66cb9bb93c05f3b48bc07d83245b20d6.png](https://i-blog.csdnimg.cn/blog_migrate/12a7c0526ea34685a79aa0c13e8f63de.png)
Xml形成的SqlSource有几种表现形式:
- Dynamic SqlSource
每次执行的时候都会进行一次编译,即编译我们对应的脚本(XML)把其变成BoundSQL
但是有的SQL不需要每次都进行编译
![b6136bb8fd8dbc2cfb478a3752f70344.png](https://i-blog.csdnimg.cn/blog_migrate/36ed14a0075061961f08f9425920de14.png)
所以还会存在一个静态编译的数据源:
- Raw SqlSource
只会编译一次,在初始化数据源的时候其就已经编译好了,编译好之后每次就通过它进行执行
两个数据源在编译之后将把结果存储在Static SqlSource中,最终通过Static SqlSource才能生成BoundSql
此外还存在一种用于第三方驱动包的继承:
- Provider SqlSource
![878d4557aa2432a857cb2fb6c935e719.png](https://i-blog.csdnimg.cn/blog_migrate/1add982ceef6002afdbc2bdddab010c5.jpeg)
Q:为什么不直接通过Dynamic SqlSource或者Raw SqlSource直接生成BoundSQL?
先看一下StaticSqlSource是什么:
![2fb90389a19f42213a4289a2adb42f6a.png](https://i-blog.csdnimg.cn/blog_migrate/3ae909108bddb1bc745c8e9a7689800b.png)
是不是感觉和BoundSql没有什么区别?
![49dfc2803a539cc3a1311273e35255fb.png](https://i-blog.csdnimg.cn/blog_migrate/bf9e9d7ffe8ac6e3f998b1d17ca607ce.png)
而且其调用方法也是直接通过new的方式来进行BoundSql的创建
![c2b4dcf37fb3c834859e739b0e815ba3.png](https://i-blog.csdnimg.cn/blog_migrate/068b92fa3ef93d939fd45f42b520784b.png)
再来看一下RawSqlSource:
![f081b0892af80c7bb32996a6bbd48c9a.png](https://i-blog.csdnimg.cn/blog_migrate/3ed94ede722ec22f9381fc1e3e807bc3.jpeg)
DynamicSqlSource:
![07cc2ad8420f83f1e88c95617391e4a9.png](https://i-blog.csdnimg.cn/blog_migrate/e8d40d441f287498e6f579a9e36abb76.jpeg)
其对应的SqlSource也是StaticSqlSource
即,RawSqlSource和DynamicSqlSource都会生成StaticSqlSource
因为RawSqlSource和DynamicSqlSource分别对应静态编译和动态编译,StaticSqlSource的作用就是成为RawSqlSource编译后的载体,用来保存RawSqlSource所生成的StaticSqlSource,那么下次再调用RawSqlSource时就可以直接通过已经编译好的SQL语言生成BoundSQL,如果没有RawSqlSource的话也就不用存在StaticSqlSource
RawSqlSource中的解析作用:
![b8609d81d001e0254ef6ae9072e63c2f.png](https://i-blog.csdnimg.cn/blog_migrate/2098b996409ad6f2c1ed747bd3ae6b34.png)
就是将 #{} 变成“?”,将name的值变为parameterMapping映射类(parameterMapping1和parameterMapping2)
![631554656cb649afa815577ba9d11b1a.png](https://i-blog.csdnimg.cn/blog_migrate/4f7e1b73fff4dba5b5c6dd64f4988f2d.png)
- DynamicSqlSource
脚本的解析流程
动态解析器:解析脚本,解析后再生成数据源
![e5542f0f4b797b22a68dc9cfd0a60c0b.png](https://i-blog.csdnimg.cn/blog_migrate/d0149df46a95221ab6d53882f81c3804.jpeg)
SQLNode:XML中每个语句都是一个SQLNode
![49bc5cc9974bdece4f35ad853b681971.png](https://i-blog.csdnimg.cn/blog_migrate/c8551fbbd55edfe47db8b5f78ff295db.png)
即这些SQLNode就组成了一棵语法树,而DynamicSqlSource就是去执行SqlNode中的一个一个的结点
在解析过程中会有一个解析上下文DynamicContext,其包含了已经成功解析的参数,最后生成BoundSQL
每执行一次脚本,DynamicContext中的参数就会发生一次变更,就相当于一个拼装构成
脚本组成结构
![ef60839c73308de37e174575df3e71b7.png](https://i-blog.csdnimg.cn/blog_migrate/ad47840eb4ef8a16cd8dd0a005d78123.jpeg)
MixedSqlNode:包含多个子Node
StaticTextSqlNode:静态文本,就是纯粹的sql文本,没有任何的表达式
TextSqlNode:表达式文本,例如:select * from ${table_name}
使用了 解释器设计模式
注意:#不是表达式文本,会直接替换成?,而$是表达式文本
其执行过程就是将XML文档转换成对应的SqlNode结点,例:
![41438c6a09134b2bea18088d0c8d9089.png](https://i-blog.csdnimg.cn/blog_migrate/3f6dd7dc2e378d825113e5c39ed58249.jpeg)
执行过程:
![9b1a0cdad356cebc103f9e01b49cf72f.png](https://i-blog.csdnimg.cn/blog_migrate/db4cd099ede024bd992ba1343e2189a5.png)
下面通过代码的方式来证明:
<!--动态SQL处理-->
<select id="findUser" resultMap="result_user" flushCache="true" databaseId="mysql">
select * from users
<where>
<if test="id!=null">
and id=#{id}
</if>
<if test="name!=null">
<bind name="like_name" value="'%'+name+'%'"/>
and name like #{like_name}
</if>
<if test="age!=null">
and age=#{age}
</if>
</where>
</select>
首选模拟一下BoundSQL的生成过程:
@Test
public void ifTest(){
User user = new User();
user.setId(1);
DynamicContext context = new DynamicContext(configuration, user);
//静态结点逻辑
new StaticTextSqlNode("select * from users where 1=1").apply(context);
//if结点逻辑
IfSqlNode ifSqlNode = new IfSqlNode(new StaticTextSqlNode("and id!=#{id}"), "id!=null");
ifSqlNode.apply(context);
//生成 sql
System.out.println(context.getSql());
}
![a2cf15db0058e1b4ee6c25d8b00ca05c.png](https://i-blog.csdnimg.cn/blog_migrate/de6b7af47d1546552256669c266f3b05.png)
如果将user.setId(1)去掉就不会生成
![2eb534da966a0b6a94b325054e9d81df.png](https://i-blog.csdnimg.cn/blog_migrate/ed62c0b284d33078231f189189e8ba45.png)
或者也可以改写成:
@Test
public void ifTest(){
User user = new User();
user.setId(1);
DynamicContext context = new DynamicContext(configuration, user);
//静态结点逻辑
new StaticTextSqlNode("select * from users").apply(context);
//if结点逻辑
IfSqlNode ifSqlNode = new IfSqlNode(new StaticTextSqlNode("and id!=#{id}"), "id!=null");
WhereSqlNode where = new WhereSqlNode(configuration, ifSqlNode);
where.apply(context);
//生成 sql
System.out.println(context.getSql());
}
![7cd7064545b684284385fa334682b056.png](https://i-blog.csdnimg.cn/blog_migrate/2e0161ded0c6b92d3d365ce64fe7c54d.png)
where中使用TrimSqlNode将and等条件进行删除(如果多余的话)
- 添加where前缀
- 溢出执行关键字的前缀和后缀
![be189f62a375caf310fcee7dc4474258.png](https://i-blog.csdnimg.cn/blog_migrate/1b0c284fdc89df892fc85d4d25d2a2c6.jpeg)
在进行appendSql时,是将其存储在一个缓存区sqlBuffer中,并不是直接追加到contents中
![62e24630c746d5c570e062dc743f73cf.png](https://i-blog.csdnimg.cn/blog_migrate/a11aa02dea3cd90eb8732eb9a7319d2c.png)
当字节点全部执行完时,此时子节点需要拼装的全部Sql都已经在sqlBuffer中,最后在applay时将其一次性加入到DynamicContext中
![4301b7f8c77aef230a1715f8ba1b5f6f.png](https://i-blog.csdnimg.cn/blog_migrate/8cb629aae6db5c0bad086c9284832116.jpeg)
![7a805f2bca6bf399cd29c8e8df71b89f.png](https://i-blog.csdnimg.cn/blog_migrate/d0027f00a964ffde8d3956d1e8b964c3.jpeg)
添加前缀,替换掉不需要的值,再把SqlBuffer追加到真正的Context中
可以写一个例子自己进行测试:
@Test
public void ifTest() {
User user = new User();
user.setId(1);
user.setName("yymmdd");
DynamicContext context = new DynamicContext(configuration, user);
//静态结点逻辑
new StaticTextSqlNode("select * from users").apply(context);
//if结点逻辑
IfSqlNode ifSqlNode = new IfSqlNode(new StaticTextSqlNode("and id!=#{id}"), "id!=null");
IfSqlNode ifSqlNode1 = new IfSqlNode(new StaticTextSqlNode("or name=#{name}"), "name!=null");
MixedSqlNode mixedSqlNode = new MixedSqlNode(Arrays.asList(ifSqlNode, ifSqlNode1));
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
where.apply(context);
//生成 sql
System.out.println(context.getSql());
}
![7d23b0446dbee03776f8ef807f063aff.png](https://i-blog.csdnimg.cn/blog_migrate/27eeab08acfd9015eca7c9694d8dd9f7.png)
可以在 where.apply(context); 处断点进行查看
ForEachSqlNode
<select id="findByIds" resultMap="result_user" flushCache="true">
select * from users
where id in
<foreach collection="list" separator="," item="item" open="(" close=")">
#{item}
</foreach>
</select>
测试类:
@Test
public void foreachTest() {
Object list;
HashMap<Object, Object> parameter = new HashMap<>();
parameter.put("list", Arrays.asList(1, 2, 3, 4, 5));
factory.openSession().selectList("findByIds", parameter);
}
![7a15692f7ed09baed2dfa061f32ac8e4.png](https://i-blog.csdnimg.cn/blog_migrate/a2890b37c290fce9bde91fb0d211cf1e.png)
![dad30a21106eb25cee8aad04f716193f.png](https://i-blog.csdnimg.cn/blog_migrate/a9b78a68e6441a9fc26f6ceca8048cb0.jpeg)
![0ee0f8cf7215dbf44a0667805f40c4e9.png](https://i-blog.csdnimg.cn/blog_migrate/563c18fbd553219c3ba873c0f4d4d45c.jpeg)
![e5a7bd8c5566a8dc3c6de9d060f59f45.png](https://i-blog.csdnimg.cn/blog_migrate/acab223e0888b22c8ffccde4a4a5219e.jpeg)
![9e21ebe12c3a12db26b0753a533bdb16.png](https://i-blog.csdnimg.cn/blog_migrate/3372365ab41a24f58aa6b49d8f69c5e5.jpeg)
最终生成的SQL:
![00377755538bfc49f219faa359c43b47.png](https://i-blog.csdnimg.cn/blog_migrate/21ef91864a5c6cecfe7c4a5690026ef5.png)
在获取参数时,会检查是否在条件参数中,如果在则直接获取
![79fc500634257945e3b6edc845c4d97a.png](https://i-blog.csdnimg.cn/blog_migrate/fd97e5c774bcce7ba84a167d6366652f.jpeg)
脚本文件解析过程
![70a73f46ee29d737f0d6607d0d05af6b.png](https://i-blog.csdnimg.cn/blog_migrate/65936951927a89bca01950e82d64a124.png)
![525df4440adb09291a815a0f09f5b396.png](https://i-blog.csdnimg.cn/blog_migrate/e23da6bea2011933f017df45cce12c94.jpeg)
可在org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode断点查看
![a15d1b8cc264ad06f10fee21f7432558.png](https://i-blog.csdnimg.cn/blog_migrate/3524e0a6ee9f8b1fb35793a6841f6c71.jpeg)