【科学翻墙上网地址:vpn.av1o.com】
在BaseExecutor中调用query方法时,会触发一个BoundSQL的东西
除此之外,在BaseStatementHandler中也调用过BoundSQL
那么BoundSQL到底是什么东西?
sql:可执行的语句parameterMapping:sql中的参数位置是"?",通过parameterMapping进行映射parameterObject:就是传递过来的参数additionParameters:对原始参数进行运算后得出的新的参数,例如将数组中的参数打散进行分离成Map形式与参数进行一一对应
metaParameters:操纵parameterObject属性即,BoundSQL包含了我们所要执行的SQL的所有信息,有了BoundSQL就可以发起SQL调用了动态SQL的定义:
在每次执行SQL的时候发生的,即每执行一次SQL都会重新进行一次编译和解析例如:此时有一个SQL脚本
select * from users:静态文本元素:脚本表达式动态SQL就是去解析静态文本元素和脚本表达式最后形成select语句在执行脚本解析的时候必须要带上所需的参数才可以生成最终的SQL语句
回忆一下SQL脚本语言树
在使用动态SQL的时候经常会看到一写表达式,例如:
这种表达式就称之为OGNL表达式OGNL表达式
访问属性publicclassBoundSqlTest{privatestaticConfigurationconfiguration;privatestaticSqlSessionFactoryfactory
=null;static{SqlSessionFactoryBuilderfactoryBuilder=newSqlSessionFactoryBuilder();factory=factoryBuilder
.build(StatementHandlerTest.class.getResourceAsStream("/mybatis-config.xml"));configuration=factory.getConfiguration
();configuration.setLazyLoadTriggerMethods(newHashSet<>());}@TestpublicvoidognlTest(){//表达式执行器 ExpressionEvaluator
evaluator=newExpressionEvaluator();Blogblog=Mock.newBlog();//1.访问属性 booleanb=evaluator.evaluateBoolean
("id != null && author.name != null",blog);System.out.println(b);}}
如果将author设置为null,则就会报错@TestpublicvoidognlTest(){//表达式执行器 ExpressionEvaluatorevaluator=newExpressionEvaluator
();Blogblog=Mock.newBlog();blog.setAuthor(null);//1.访问属性 booleanb=evaluator.evaluateBoolean("id != null && author.name != null"
,blog);System.out.println(b);}
调用方法@TestpublicvoidognlTest(){//表达式执行器 ExpressionEvaluatorevaluator=newExpressionEvaluator();Blogblog
=Mock.newBlog();blog.setAuthor(null);//2.调用方法 booleanb1=evaluator.evaluateBoolean("comments!=null && comments.size()>0"
,blog);System.out.println(b1);}
可以使省略方法的括号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);遍历集合Iterablecomments=evaluator.evaluateIterable("comments",blog);for(Objectcomment:comments
){System.out.println(comment);}
脚本的解析流程由SqlSource(SQL数据源)编程BoundSql(SQL包)的过程
前面说过,有了BoundSQL就有了执行SQL所需的全部写信息,所以BoundSQL需要sql语句、执行sql所需的参数、参数和参数值的映射(sql中的"?")
即,需要将SqlSource编程BoundSQL,转换的时机在每次发起SQL调用的时候都会执行一次
最后,这个脚本(XML)会变成一个数据源sqlSource
Xml形成的SqlSource有几种表现形式:Dynamic SqlSource每次执行的时候都会进行一次编译,即编译我们对应的脚本(XML)把其变成BoundSQL但是有的SQL不需要每次都进行编译
所以还会存在一个静态编译的数据源:Raw SqlSource只会编译一次,在初始化数据源的时候其就已经编译好了,编译好之后每次就通过它进行执行两个数据源在编译之后将把结果存储在Static SqlSource中,最终通过Static SqlSource才能生成BoundSql
此外还存在一种用于第三方驱动包的继承:Provider SqlSource
Q:为什么不直接通过Dynamic SqlSource或者Raw SqlSource直接生成BoundSQL?先看一下StaticSqlSource是什么:
是不是感觉和BoundSql没有什么区别?
而且其调用方法也是直接通过new的方式来进行BoundSql的创建
再来看一下RawSqlSource:
DynamicSqlSource:
其对应的SqlSource也是StaticSqlSource即,RawSqlSource和DynamicSqlSource都会生成StaticSqlSource因为RawSqlSource和DynamicSqlSource分别对应静态编译和动态编译,StaticSqlSource的作用就是成为RawSqlSource编译后的载体,用来保存RawSqlSource所生成的StaticSqlSource,那么下次再调用RawSqlSource时就可以直接通过已经编译好的SQL语言生成BoundSQL,如果没有RawSqlSource的话也就不用存在StaticSqlSource
【telegram指定翻墙地址:www.av1o.com】
RawSqlSource中的解析作用:
就是将 #{} 变成“?”,将name的值变为parameterMapping映射类(parameterMapping1和parameterMapping2)
DynamicSqlSource脚本的解析流程动态解析器:解析脚本,解析后再生成数据源
SQLNode:XML中每个语句都是一个SQLNode
即这些SQLNode就组成了一棵语法树,而DynamicSqlSource就是去执行SqlNode中的一个一个的结点在解析过程中会有一个解析上下文DynamicContext,其包含了已经成功解析的参数,最后生成BoundSQL
每执行一次脚本,DynamicContext中的参数就会发生一次变更,就相当于一个拼装构成脚本组成结构
MixedSqlNode:包含多个子NodeStaticTextSqlNode:静态文本,就是纯粹的sql文本,没有任何的表达式TextSqlNode:表达式文本,例如:select * from ${table_name}
使用了解释器设计模式注意:#不是表达式文本,会直接替换成?,而$是表达式文本其执行过程就是将XML文档转换成对应的SqlNode结点,例:
执行过程:
下面通过代码的方式来证明:
> select * from users and id=#{id}
"name!=null"> and name like #{like_name}
and age=#{age} 首选模拟一下BoundSQL的生成过程:@Test
publicvoidifTest(){Useruser=newUser();user.setId(1);DynamicContextcontext=newDynamicContext(configuration
,user);//静态结点逻辑 newStaticTextSqlNode("select * from users where 1=1").apply(context);//if结点逻辑 IfSqlNode
ifSqlNode=newIfSqlNode(newStaticTextSqlNode("and id!=#{id}"),"id!=null");ifSqlNode.apply(context);//生成 sql
System.out.println(context.getSql());}
如果将user.setId(1)去掉就不会生成
或者也可以改写成:@TestpublicvoidifTest(){Useruser=newUser();user.setId(1);DynamicContextcontext=newDynamicContext
(configuration,user);//静态结点逻辑 newStaticTextSqlNode("select * from users").apply(context);//if结点逻辑 IfSqlNode
ifSqlNode=newIfSqlNode(newStaticTextSqlNode("and id!=#{id}"),"id!=null");WhereSqlNodewhere=newWhereSqlNode
(configuration,ifSqlNode);where.apply(context);//生成 sql System.out.println(context.getSql());}
where中使用TrimSqlNode将and等条件进行删除(如果多余的话)添加where前缀溢出执行关键字的前缀和后缀
在进行appendSql时,是将其存储在一个缓存区sqlBuffer中,并不是直接追加到contents中
当字节点全部执行完时,此时子节点需要拼装的全部Sql都已经在sqlBuffer中,最后在applay时将其一次性加入到DynamicContext中
添加前缀,替换掉不需要的值,再把SqlBuffer追加到真正的Context中可以写一个例子自己进行测试:@TestpublicvoidifTest(){Useruser=newUser();user.
setId(1);user.setName("yymmdd");DynamicContextcontext=newDynamicContext(configuration,user);//静态结点逻辑
newStaticTextSqlNode("select * from users").apply(context);//if结点逻辑 IfSqlNodeifSqlNode=newIfSqlNode(new
StaticTextSqlNode("and id!=#{id}"),"id!=null");IfSqlNodeifSqlNode1=newIfSqlNode(newStaticTextSqlNode(
"or name=#{name}"),"name!=null");MixedSqlNodemixedSqlNode=newMixedSqlNode(Arrays.asList(ifSqlNode,ifSqlNode1
));WhereSqlNodewhere=newWhereSqlNode(configuration,mixedSqlNode);where.apply(context);//生成 sql System
.out.println(context.getSql());}
可以在 where.apply(context); 处断点进行查看ForEachSqlNode
"true"> select * from users where id in
open="("close=")"> #{item} 测试类:@TestpublicvoidforeachTest(){Objectlist
;HashMapparameter=newHashMap<>();parameter.put("list",Arrays.asList(1,2,3,4,5));factory
.openSession().selectList("findByIds",parameter);}
最终生成的SQL:
在获取参数时,会检查是否在条件参数中,如果在则直接获取
脚本文件解析过程
可在org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode断点查看
【telegram指定翻墙地址:www.av1o.com】