当前位置: 首页 > news >正文

手机电脑网站建设驻马店手机网站制作

手机电脑网站建设,驻马店手机网站制作,无代码建站,梵客装饰公司官网文章目录 前言8.5 动态SQL解析过程8.5.1 SQL配置转换为SqlSource对象8.5.2 SqlSource转换为静态SQL语句 8.6 #{}和${}的区别8.7 小结 前言 在【MyBatis3源码深度解析(二十)动态SQL实现原理(一)动态SQL的核心组件】中研究了MyBatis动态SQL相关的组件#xff0c;如SqlSource用于… 文章目录 前言8.5 动态SQL解析过程8.5.1 SQL配置转换为SqlSource对象8.5.2 SqlSource转换为静态SQL语句 8.6 #{}和${}的区别8.7 小结 前言 在【MyBatis3源码深度解析(二十)动态SQL实现原理(一)动态SQL的核心组件】中研究了MyBatis动态SQL相关的组件如SqlSource用于描述通过XML文件或Java注解配置的SQL信息SqlNode用于描述动态SQL中的if、where等标签信息LanguageDriver用于对SQL配置进行解析将SQL配置转换为SqlSource对象。 研究了MyBatis动态SQL相关的组件下面来研究一下动态SQL的解析过程。本文使用如下案例进行调试 !--UserMapper.xml-- select idselectByCons parameterTypeUser resultTypeUserselect * from user where id ${id}if testname ! null and name ! and name #{name}/ifif testage ! nulland age #{age}/if /select// ...... UserMapper userMapper sqlSession.getMapper(UserMapper.class); User user new User(); user.setId(1); user.setAge(18); userMapper.selectByCons(user);8.5 动态SQL解析过程 8.5.1 SQL配置转换为SqlSource对象 LanguageDriver用于对SQL配置进行解析它其中一个实现类XMLLanguageDriver的createSqlSource()方法就用于解析XML配置文件中的SQL配置。 源码1org.apache.ibatis.scripting.xmltags.XMLLanguageDriverOverride public SqlSource createSqlSource(Configuration configuration, XNode script, Class? parameterType) {XMLScriptBuilder builder new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode(); }由 源码1 可知在XMLLanguageDriver的createSqlSource()方法中XML配置文件中的SQL配置的解析实际上是委托给XMLScriptBuilder类来完成的调用XMLScriptBuilder类的parseScriptNode()方法来完成解析工作。 该方法的参数值script参数即select标签对应的XNode对象 源码2org.apache.ibatis.scripting.xmltags.XMLScriptBuilderprivate final XNode context; private boolean isDynamic; private final Class? parameterType;public SqlSource parseScriptNode() {// 将SQL信息对应的XNode对象转换为SqlNode对象MixedSqlNode rootSqlNode parseDynamicTags(context);SqlSource sqlSource;if (isDynamic) {// 如果是动态SQL则返回DynamicSqlSource实例sqlSource new DynamicSqlSource(configuration, rootSqlNode);} else {// 如果不是动态SQL则返回RawSqlSource实例sqlSource new RawSqlSource(configuration, rootSqlNode, parameterType);}return sqlSource; }由 源码2 可知在XMLScriptBuilder类的parseScriptNode()方法中首先调用parseDynamicTags()方法将SQL信息对应的XNode对象转换为SqlNode对象然后判定是否为动态SQL如果是则返回DynamicSqlSource实例否则返回RawSqlSource实例。 源码3org.apache.ibatis.scripting.xmltags.XMLScriptBuilderprotected MixedSqlNode parseDynamicTags(XNode node) {ListSqlNode contents new ArrayList();// 获取子节点NodeList children node.getNode().getChildNodes();// 遍历子节点for (int i 0; i children.getLength(); i) {XNode child node.newXNode(children.item(i));// 如果子节点为文本节点即非if、where等标签// 则使用TextSqlNode描述子节点if (child.getNode().getNodeType() Node.CDATA_SECTION_NODE || child.getNode().getNodeType() Node.TEXT_NODE) {String data child.getStringBody();TextSqlNode textSqlNode new TextSqlNode(data);if (textSqlNode.isDynamic()) {// 如果是动态SQL则将isDynamic属性设置为truecontents.add(textSqlNode);isDynamic true;} else {// 非动态SQL只用StaticTextSqlNode描述子节点contents.add(new StaticTextSqlNode(data));}} else if (child.getNode().getNodeType() Node.ELEMENT_NODE) {// 如果子节点为元素节点即if、where等标签// 则使用对应的NodeHandler进行处理String nodeName child.getNode().getNodeName();NodeHandler handler nodeHandlerMap.get(nodeName);if (handler null) {throw new BuilderException(Unknown element nodeName in SQL statement.);}// 调用NodeHandler的handleNode()方法进行处理handler.handleNode(child, contents);// 元素节点一定是动态SQLisDynamic true;}}return new MixedSqlNode(contents); }由 源码3 可知在XMLScriptBuilder类的parseDynamicTags()方法中会对SQL配置对应的XNode对象的所有子节点进行遍历。 如果子节点为文本节点即非标签元素则使用TextSqlNode描述子节点然后继续判断该SQL文本是否是动态SQL若是则将isDynamic属性设置为true源码2 中会根据该属性返回不同类型的SqlSource对象若不是则转为使用StaticTextSqlNode描述子节点。 TextSqlNode对象的isDynamic()方法用于判断SQL文本是否是动态SQL 源码4org.apache.ibatis.scripting.xmltags.TextSqlNodepublic boolean isDynamic() {DynamicCheckerTokenParser checker new DynamicCheckerTokenParser();GenericTokenParser parser createParser(checker);parser.parse(text);return checker.isDynamic(); }private GenericTokenParser createParser(TokenHandler handler) {return new GenericTokenParser(${, }, handler); }由 源码4 可知判断SQL文本是否是动态SQL需要借助GenericTokenParser类来完成。在createParser()方法中创建了一个GenericTokenParser对象并传递了${和}两个参数。 由此可以猜想判断SQL文本是否是动态SQL的依据是SQL文本中是否包含${和}符号。通过后文分析会发现这里猜想是对的 借助Debug工具可以查看到案例中TextSqlNode对象封装了select * from user where id ${id}并且它是动态SQL 回到 源码3 如果子节点为元素节点即、等标签节点则使用对应的NodeHandler进行处理。 在XMLScriptBuilder类内部定义了一个私有的接口NodeHandler并为每种动态SQL标签提供了一个NodeHandler接口的实现类用于处理对应的动态SQL标签转换为对应的SqlNode对象。 源码5org.apache.ibatis.scripting.xmltags.XMLScriptBuilderprivate interface NodeHandler {void handleNode(XNode nodeToHandle, ListSqlNode targetContents); }由 源码5 可知NodeHandler接口只有一个handleNode()方法它接收两个参数nodeToHandle参数是动态SQL标签对应的XNode对象targetContents参数是存放SqlNode对象的List集合。该方法对XML标签进行解析后把生成的SqlNode对象存放到该List集合中。 借助Debug可以列出NodeHandler接口的8个实现类 由图可知每个实现类用于处理对应的动态SQL标签例如IfHandler用于处理动态SQL配置中的if标签将if标签的内容转换为IfSqlNode对象。 源码6org.apache.ibatis.scripting.xmltags.XMLScriptBuilderprivate class IfHandler implements NodeHandler {public IfHandler() {// Prevent Synthetic Access}Overridepublic void handleNode(XNode nodeToHandle, ListSqlNode targetContents) {// 继续调用parseDynamicTags()方法处理if标签下的子节点MixedSqlNode mixedSqlNode parseDynamicTags(nodeToHandle);// 获取if标签的test属性String test nodeToHandle.getStringAttribute(test);// 创建IfSqlNode对象并添加到List集合中IfSqlNode ifSqlNode new IfSqlNode(mixedSqlNode, test);targetContents.add(ifSqlNode);} }由 源码6 可知在IfHandler的handleNode()方法中会继续调用XMLScriptBuilder类的parseDynamicTags()方法完成if标签的子节点的解析将子节点转换为MixedSqlNode对象然后获取if标签的test属性接着创建IfSqlNode对象并添加到List集合中。 这里使用了“递归”来完成动态SQL标签的解析。parseDynamicTags()方法会获取当前节点的所有子节点如果子节点为标签节点则继续调用对应的NodeHandler进行处理这就“递归”地完成了所有动态SQL标签的解析。 需要注意的是在XMLScriptBuilder类的构造方法中会调用initNodeHandlerMap()方法将所有的NodeHandler实例注册到Map集合中 源码7org.apache.ibatis.scripting.xmltags.XMLScriptBuilderpublic XMLScriptBuilder(Configuration configuration, XNode context, Class? parameterType) {super(configuration);this.context context;this.parameterType parameterType;initNodeHandlerMap(); }private void initNodeHandlerMap() {nodeHandlerMap.put(trim, new TrimHandler());nodeHandlerMap.put(where, new WhereHandler());nodeHandlerMap.put(set, new SetHandler());nodeHandlerMap.put(foreach, new ForEachHandler());nodeHandlerMap.put(if, new IfHandler());nodeHandlerMap.put(choose, new ChooseHandler());nodeHandlerMap.put(when, new IfHandler());nodeHandlerMap.put(otherwise, new OtherwiseHandler());nodeHandlerMap.put(bind, new BindHandler()); }由 源码7 可知提前注册好NodeHandler实例在需要解析动态SQL标签时只需要根据标签名获取对应的NodeHandler对象进行处理即可而不需要每次都创建对应的NodeHandler实例这是享元思想的应用。 经过以上逻辑SQL配置已转换为对应的SqlSource对象。 案例中的SqlSource对象内容如下 由图可知由于是动态SQL因此该SqlSource对象的类型是DynamicSqlSource。SQL配置被解析后转换为5个SqlNode对象包括1个TextSqlNode对象和2个IfSqlNode对象剩余2个StaticSqlNode对象是换行符和空格。 8.5.2 SqlSource转换为静态SQL语句 SQL配置转换为SqlSource对象后存放在MappedStatement对象的sqlSource属性中。 在【MyBatis3源码深度解析(十六)SqlSession的创建与执行(三)Mapper方法的调用过程】中提到SELECT类型的Mapper方法的调用过程中会调用BaseExecutor类的query()方法内部再转调MappedStatement对象的getBoundSql()方法获取一个BoundSql对象。 源码8org.apache.ibatis.mapping.MappedStatementprivate SqlSource sqlSource;public BoundSql getBoundSql(Object parameterObject) {// 调用SqlSource对象的getBoundSql()方法获取BoundSql对象BoundSql boundSql sqlSource.getBoundSql(parameterObject);// ......return boundSql; }由 源码8 可知在MappedStatement对象的getBoundSql()方法中会调用SqlSource对象的getBoundSql()方法获取一个BoundSql对象而BoundSql对象内部封装了SQL语句及参数信息这就完成了SqlSource对象到静态SQL语句的转换。 源码9org.apache.ibatis.scripting.xmltags.DynamicSqlSourcepublic class DynamicSqlSource implements SqlSource {private final Configuration configuration;private final SqlNode rootSqlNode;public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {this.configuration configuration;this.rootSqlNode rootSqlNode;}Overridepublic BoundSql getBoundSql(Object parameterObject) {// 通过参数对象构建动态SQL上下文对象DynamicContext context new DynamicContext(configuration, parameterObject);// 调用SqlNode对象的apply()方法rootSqlNode.apply(context);// 创建SqlSourceBuilder对象SqlSourceBuilder sqlSourceParser new SqlSourceBuilder(configuration);Class? parameterType parameterObject null ? Object.class : parameterObject.getClass();// 调用SqlSourceBuilder对象的parse()方法对SQL内容做进一步的处理返回一个StaticSqlSource对象SqlSource sqlSource sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());// 调用StaticSqlSource对象的getBoundSql()方法获取BoundSql对象BoundSql boundSql sqlSource.getBoundSql(parameterObject);// 将bind标签绑定的参数添加到BoundSql对象中context.getBindings().forEach(boundSql::setAdditionalParameter);return boundSql;} }由 源码9 可知在DynamicSqlSource的getBoundSql()方法中会调用SqlNode对象的apply()方法对动态SQL进行解析解析过程详见【MyBatis3源码深度解析(二十)动态SQL实现原理(一)动态SQL的核心组件 8.4 SqlNode组件】。 动态SQL解析完成后接着调用SqlSourceBuilder对象的parse()方法对SQL内容做进一步的处理返回一个StaticSqlSource对象StaticSqlSource对象用于描述动态SQL解析后的静态SQL信息。 最后调用StaticSqlSource对象的getBoundSql()方法获取BoundSql对象并返回。 源码10org.apache.ibatis.builder.SqlSourceBuilderpublic SqlSource parse(String originalSql, Class? parameterType, MapString, Object additionalParameters) {// 创建一个参数映射处理器对SQL中的#{}参数占位符进行解析ParameterMappingTokenHandler handler new ParameterMappingTokenHandler(configuration, parameterType,additionalParameters);// 创建一个GenericTokenParser对象对SQL中的#{}参数占位符进行解析GenericTokenParser parser new GenericTokenParser(#{, }, handler);String sql;if (configuration.isShrinkWhitespacesInSql()) {sql parser.parse(removeExtraWhitespaces(originalSql));} else {sql parser.parse(originalSql);}// 返回StaticSqlSource对象return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); }由 源码10 可知在SqlSourceBuilder对象的parse()方法中创建了两个处理器ParameterMappingTokenHandler对象是参数映射处理器负责将SQL语句中的#{}参数占位符进行转换GenericTokenParser对象负责对SQL中的#{}参数占位符进行解析。 借助Debug工具可以获取此时originalSql参数的值为select * from user where id 1 and age #{age}去掉了一些不必要的空格和换行符。 注意到这一步会发现${id}已经被替换成具体的参数值是怎么替换的放到下文再解析。 下面就来分析一下GenericTokenParser对象的parse()方法的原理 源码11org.apache.ibatis.parsing.GenericTokenParserpublic class GenericTokenParser {private final String openToken;private final String closeToken;private final TokenHandler handler;public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {// 被初始化为 #{this.openToken openToken;// 被初始化为 }this.closeToken closeToken;this.handler handler;}public String parse(String text) {if (text null || text.isEmpty()) {return ;}// 获取第1个#{符号在SQL文本中的位置索引int start text.indexOf(openToken);if (start -1) {// 位置索引为-1说明不存在#{符号return text;}// 将SQL文本转换为字符数组char[] src text.toCharArray();// 用于记录已处理字符的偏移量int offset 0;// 记录已确定的SQL文本final StringBuilder builder new StringBuilder();// 记录#{}符号内的内容StringBuilder expression null;do {if (start 0 src[start - 1] \\) {// 如果#{符号前面是\\符号说明这个符号后的内容已经被注释掉了// 则记录\\符号前面的内容并记录此时的偏移量builder.append(src, offset, start - offset - 1).append(openToken);offset start openToken.length();} else {// 找到了#{符号if (expression null) {expression new StringBuilder();} else {expression.setLength(0);}// #{符号前面的内容已经是确定的builder.append(src, offset, start - offset);// 偏移量移动到#{符号后offset start openToken.length();// 寻找结束字符}的位置索引int end text.indexOf(closeToken, offset);while (end -1) {// 找到了结束字符}的位置索引if ((end offset) || (src[end - 1] ! \\)) {// 结束字符}的位置索引比当前偏移量大且结束字符之前没有\\符号// 说明确实是真的结束字符// 将#{}符号内的内容记录到expression中expression.append(src, offset, end - offset);break;}// 结束字符之前有\\符号说明已经注释掉了则手动加一个结束字符expression.append(src, offset, end - offset - 1).append(closeToken);// 记录偏移量和结束字符的位置offset end closeToken.length();end text.indexOf(closeToken, offset);}if (end -1) {// 没有找到结束字符则把后面的字符全加入到builderbuilder.append(src, start, src.length - start);offset src.length;} else {// 有结束字符则调用TokenHandler的handleToken()方法处理#{}符号内的内容builder.append(handler.handleToken(expression.toString()));offset end closeToken.length();}}// 将开始索引移动到新的位置start text.indexOf(openToken, offset);} while (start -1);if (offset src.length) {builder.append(src, offset, src.length - offset);}return builder.toString();} }由 源码11 可知在GenericTokenParser对象的parse()方法中主要是对SQL语句中的所有#{}参数占位符进行解析获取参数占位符内的内容并调用ParameterMappingTokenHandler对象的handleToken()方法对参数占位符内容进行替换。 那#{}参数占位符被替换成了什么呢 源码12org.apache.ibatis.builder.SqlSourceBuilderprivate static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {Overridepublic String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));return ?;} }由 源码12 可知#{}参数占位符最终被替换成了一个“?”。 借助Debug工具确实发现originalSql参数的值由原来的select * from user where id 1 and age #{age}转换为select * from user where id 1 and age ?。 可想而知#{}参数占位符被替换成了一个“?”之后就可以调用PreparedStatement对象的setXXX()方法为参数占位符设置值了。 另外在ParameterMappingTokenHandler对象的handleToken()方法中还调用了buildParameterMapping()方法对占位符内容进行解析即对javaType、jdbcType、property等参数进行解析。 至此动态SQL的解析完成最终获得的SQL语句是包含?号的后续会通过调用PreparedStatement对象的setXXX()方法为?号设置值。 8.6 #{}和${}的区别 在案例中#{age}最终被转换为?而${id}直接被替换为具体的参数值。这两者的解析有什么区别呢 前面已经详细研究了#{}参数占位符的解析下面重点看看${}参数占位符的解析。 经过【8.5.1 SQL配置转换为SqlSource】的逻辑SQL配置已转换为对应的SqlSource对象。 在该对象中包括1个TextSqlNode对象该对象描述的SQL文本是select * from user where id ${id} 可见对${}参数占位符的解析在TextSqlNode对象的apply()方法中完成。 源码13org.apache.ibatis.scripting.xmltags.TextSqlNodeOverride public boolean apply(DynamicContext context) {GenericTokenParser parser createParser(new BindingTokenParser(context, injectionFilter));context.appendSql(parser.parse(text));return true; }private GenericTokenParser createParser(TokenHandler handler) {return new GenericTokenParser(${, }, handler); }由 源码13 可知在TextSqlNode对象的apply()方法中会转调GenericTokenParser对象的parse()方法但GenericTokenParser对象的openToken属性值为${closeToken属性值为}TokenHandler为BindingTokenParser对象。 而parse()方法的逻辑与 源码11 的分析是一致的不同的是openToken、closeToken属性值不一样最终会调用BindingTokenParser对象的handleToken()方法对${}参数占位符进行处理。这里也足以证明判断SQL文本是否是动态SQL的依据是SQL文本中是否包含${和}符号 源码14org.apache.ibatis.scripting.xmltags.TextSqlNodeprivate static class BindingTokenParser implements TokenHandler {Overridepublic String handleToken(String content) {// 获取内置参数该参数保存了所有参数信息Object parameter context.getBindings().get(_parameter);if (parameter null) {context.getBindings().put(value, null);} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {context.getBindings().put(value, parameter);}// 通过OGNL表达式获取参数值Object value OgnlCache.getValue(content, context.getBindings());String srtValue value null ? : String.valueOf(value);checkInjection(srtValue);// 返回参数值return srtValue;} }由 源码14 可知与ParameterMappingTokenHandler对象的handleToken()方法返回一个?号不同BindingTokenParser对象的handleToken()方法返回了具体的参数值。这也就解释了${}参数占位符会直接被替换为具体的参数值。 总结一下使用#{}参数占位符时占位符内容会被替换成?然后通过PreparedStatement对象的setXXX()方法为参数占位符设置值而${}参数占位符内容会被直接替换为具体的参数值。 使用#{}参数占位符能够有效避免SQL注入问题在实际开发中应优先考虑当其无法满足要求时才考虑使用${}参数占位符。 8.7 小结 第八章到此就梳理完毕了本章的主题是动态SQL实现原理。回顾一下本章的梳理的内容 (二十)SqlSource、BoundSql、LanguageDriver、SqlNode组件 (二十一)动态SQL解析过程、#{}和${}的区别 更多内容请查阅分类专栏MyBatis3源码深度解析 第九章主要学习MyBatis插件原理及应用。主要内容包括 MyBatis插件实现原理自定义一个分页插件自定义慢SQL统计插件。
http://www.pierceye.com/news/683487/

相关文章:

  • 汽车网站建设页面网站建设营销公司
  • 可以写代码的网站有哪些问题微信公众号的推广
  • 网站建设项目怎么写新网站一般多久收录
  • 什么网站可以免费发广告合肥做网站一般多少钱
  • 企业网站优化的方式大安市网站
  • 镇江专业网站建设制作wordpress调查插件
  • 桂林网站制作多少钱最好的网站开发公司
  • 广州网站开发公司排名广州从化建设网站官网
  • 网站备案在杭州注册公司需要什么条件
  • 购买域名做销售网站可以吗河北邢台刚刚发布的紧急通知
  • 安溪建设局网站政务网站建设信息
  • 如何做公司自己的网站首页网站建设的钱计入什么科目
  • 网站建设公司行业免费下载网站模版
  • 海外做淘宝网站网站有关于我们的好处
  • 给别人做网站挣钱吗怎么建设推广网站
  • 南宁市网站开发深圳制作app
  • 临海大经建设集团网站雄安做网站
  • 网站设计多少钱通桥小学的网站建设
  • 上海制作网站的公司做彩票网站违法吗
  • ps软件手机版下载百度seo工具
  • 新乡网站关键词优化建设局网站信息管理制度
  • 海南论坛网站建设网站建设怎么放到云空间
  • 中山 灯饰 骏域网站建设专家网站建设需求范文
  • 东光网站建设淘宝店铺装修苏州网站建设服务
  • 平台型网站建设预算表哈尔滨网站制作多少钱
  • 手机网站焦点图装修房子的流程和顺序
  • 做网站一个月赚多少和一起做网店类似的网站
  • 网站的栏目有什么名字中文网站建设技术解决方案
  • wordpress首页非常慢网站优化推广seo
  • 成都建设银行网站北京建设工程招标网