JDBC中,主要使用两种语句,一种是支持参数化和预编译的PreparedStatement,支持原生sql,支持设置占位符,参数化输入的参数,防止sql注入攻击,在mybatis的mapper配置文件中,我们通过使用#和$告诉mybatis我们需要对参数进行怎样的设置。sql注入指的是利用现有应用程序将恶意的sql命令提交存在安全漏洞。例如在提交表单时加入or拼接语句使其永远成立。对比JDBC执行流程connection->statement->result,在mybatis中由SqlSession提供给用户操作的API,Excutor具体执行数据库的操作。

SqlSession接口主要的实现类有:

先看DefaultSqlSession:

selectOne的方法最终转化成了selectList方法

最终执行的是箭头指的selectList方法,该方法中含有变量:MappedSatement和Excutor,MappedSatement是加载mapper.xml时匹配的namespace+id

StrictMap是Configuration类中的一个静态内部类,继承了HashMap,看一下selectList方法调用的wrapCollection方法

这就是为什么我们在mapper.xml中foreach用list或array遍历,再看Excutor执行的query方法,Excutor接口实现类

BaseExcutor的query方法:

由上可知若设置清除缓存,首先会清除缓存,首先会根据CacheKey查找缓存,查找结果为空,则从数据库查

doQuery:

可见Executor委托给StatementHandler执行查询,在此之前有一个预编译的过程(prepareStatement方法),StatementHandler接口的实现类:CallableStatementHandler,PreparedStatementHandler,SimpleStatementHandler对应JDBC中的CallableStatement,PreparedStatement和Statement,分别的执行方法:

看之前提到的prepareStatement方法

handler会对statement参数化设置,PreparedStatementHandler中:

由parameterhandler执行参数设置,上面是简单分析的查询流程,回头说$和#,我们知道,使用$时statement执行的是拼接操作,#的时候statement用的是占位符 ?,这是mybatis解析的时候造成的,根据测试例子:

SqlSessionFactoryBuilder的build方法:

XMLConfigBuilder负责解析总配置文件,其中方法有:

返回值都为XNode节点类型,看mapperElement:

package扫描包,resource和class扫描指定类和mapper.xml,XMLMapperBuilder:

主要看这两个方法,根据节点建立statement:

由上可以看出XMLStatementBuilder解析statement,也就是mapper.xml中的一个个statement,快成功了。。。

parseStatementNode解析一条记录中的各个属性,例如resultType,parameterType,useCache等等。。。该方法代码过长,其主要在:

两处,一个是SqlSource,一个是addMappedStatement,解析的属性值都对应到MappedStatement对象中

在MappedStatement对象中除这些外,还有个属性SqlSource,可见该对象决定sql语句的解析

只有一个抽象方法getBoundSql,SqlSource是如何获取的呢,这就用到了上面prepareStatement方法中的LanguageDriver的createSqlSource方法,继续跟进:

解析sql语句之前会先解析selectKey和include节点,LanguageDriver的实现类有XMLLanguageDriver和RawLanguageDriver

可以知道createSqlSource方法只在XMLLanguageDriver实现

委托给了XMLScriptBuilder的parseScriptNode方法:

 1 public SqlSource parseScriptNode() {
 2     List<SqlNode> contents = parseDynamicTags(context);
 3     MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
 4     SqlSource sqlSource = null;
 5     if (isDynamic) {
 6       sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
 7     } else {
 8       sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
 9     }
10     return sqlSource;
11   }

根据isDynamic标志确定sqlSource类型,parseDynamicTags方法:

 1  private List<SqlNode> parseDynamicTags(XNode node) {
 2     List<SqlNode> contents = new ArrayList<SqlNode>();
 3     NodeList children = node.getNode().getChildNodes();
 4     for (int i = 0; i < children.getLength(); i++) {
 5       XNode child = node.newXNode(children.item(i));
 6       if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
 7         String data = child.getStringBody("");
 8         TextSqlNode textSqlNode = new TextSqlNode(data);
 9         if (textSqlNode.isDynamic()) {
10           contents.add(textSqlNode);
11           isDynamic = true;
12         } else {
13           contents.add(new StaticTextSqlNode(data));
14         }
15       } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
16         String nodeName = child.getNode().getNodeName();
17         NodeHandler handler = nodeHandlers.get(nodeName);
18         if (handler == null) {
19           throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
20         }
21         handler.handleNode(child, contents);
22         isDynamic = true;
23       }
24     }
25     return contents;
26   }

返回值是一个list,过程:

遍历各个子节点

(1) 如果节点类型是文本或者CDATA,构造一个TextSqlNode或StaticTextSqlNode

(2) 如果节点类型是元素,说明该节点是个动态sql,然后会使用NodeHandler处理各个类型的子节点。这里的NodeHandler是XMLScriptBuilder的一个内部接口,其实现类包括TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等。看类名也就明白了这个Handler的作用,比如我们分析的trim节点,对应的是TrimHandler;if节点,对应的是IfHandler...,TextSqlNode的isDynamic方法:

1   public boolean isDynamic() {
2     DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
3     GenericTokenParser parser = createParser(checker);//建立GenericTokenParser
4     parser.parse(text);//GenericTokenParser解析text
5     return checker.isDynamic();
6   }

createParser方法:

1 private GenericTokenParser createParser(TokenHandler handler) { 2 return new GenericTokenParser("${", "}", handler); 3 }

构造方法:

1   public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
2     this.openToken = openToken;
3     this.closeToken = closeToken;
4     this.handler = handler;
5   }

根据是否Dynamic,TokenHandler的主要实现类有:DynamicCheckerTokenParser和ParameterMappingTokenHandler,VariableTokenHandler

1 private GenericTokenParser createParser(TokenHandler handler) { 2 return new GenericTokenParser("${", "}", handler); 3 } //$的处理方式

DynamicCheckerTokenParser实现的handleToken方法

1  public String handleToken(String content) {
2       this.isDynamic = true;
3       return null;
4     }

ParameterMappingTokenHandler实现的handleToken方法:

1  public String handleToken(String content) {//#的处理方式,返回占位符?
2       parameterMappings.add(buildParameterMapping(content));
3       return "?";
4     }

#的方式大概就是这样,再看$,$使用的是DynamicCheckerTokenParser,这时候再看返回的DynamicSqlSource,其实现SqlSource接口的getBoundSql方法:

 1 public BoundSql getBoundSql(Object parameterObject) {
 2     DynamicContext context = new DynamicContext(configuration, parameterObject);
 3     rootSqlNode.apply(context);//apply方法实际调用的是TextSqlNode的applay方法
 4     SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
 5     Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
 6     SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());//SqlSourceBuilder的parse方法解析
 7     BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
 8     for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
 9       boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
10     }
11     return boundSql;
12   }

TextSqlNode的apply方法:
1 public boolean apply(DynamicContext context) {
2     GenericTokenParser parser = createParser(new BindingTokenParser(context));
3     context.appendSql(parser.parse(text));
4     return true;
5   }

用的是BindingTokenParser的parse方法:

 1  public String parse(String text) {
 2     StringBuilder builder = new StringBuilder();
 3     if (text != null && text.length() > 0) {
 4       char[] src = text.toCharArray();
 5       int offset = 0;
 6       int start = text.indexOf(openToken, offset);
 7       while (start > -1) {
 8         if (start > 0 && src[start - 1] == '\\') {
 9           // the variable is escaped. remove the backslash.
10           builder.append(src, offset, start - 1).append(openToken);
11           offset = start + openToken.length();
12         } else {
13           int end = text.indexOf(closeToken, start);
14           if (end == -1) {
15             builder.append(src, offset, src.length - offset);
16             offset = src.length;
17           } else {
18             builder.append(src, offset, start - offset);
19             offset = start + openToken.length();
20             String content = new String(src, offset, end - offset);
21             builder.append(handler.handleToken(content));//又回到了handleToken方法,此时的handler为BindingTokenParser
22             offset = end + closeToken.length();
23           }
24         }
25         start = text.indexOf(openToken, offset);
26       }
27       if (offset < src.length) {
28         builder.append(src, offset, src.length - offset);
29       }
30     }
31     return builder.toString();
32   }

BindingTokenParser的handleToken:

 1 public String handleToken(String content) {
 2       Object parameter = context.getBindings().get("_parameter");
 3       if (parameter == null) {
 4         context.getBindings().put("value", null);
 5       } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
 6         context.getBindings().put("value", parameter);//从此处可以看出mapper.xml中$或#中可以用value
 7       }
 8       Object value = OgnlCache.getValue(content, context.getBindings());//此处用了ognl处理
 9       return (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
10     }

ognl不太清除,先分析到这


转载于:https://www.cnblogs.com/miserable-faith/p/7658550.html

mybatis $和#源代码分析相关推荐

  1. Android系统默认Home应用程序(Launcher)的启动过程源代码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还需要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

  2. 《LINUX3.0内核源代码分析》第一章:内存寻址

    https://blog.csdn.net/ekenlinbing/article/details/7613334 摘要:本章主要介绍了LINUX3.0内存寻址方面的内容,重点对follow_page ...

  3. Scrapy源代码分析-经常使用的爬虫类-CrawlSpider(三)

    CrawlSpider classscrapy.contrib.spiders.CrawlSpider 爬取一般站点经常使用的spider.其定义了一些规则(rule)来提供跟进link的方便的机制. ...

  4. Android 中View的绘制机制源代码分析 三

    到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...

  5. Android应用程序进程启动过程的源代码分析(1)

    Android应用程序框架层创建的应用程序进程具有两个特点,一是进程的入口函数是ActivityThread.main,二是进程天然支持Binder进程间通信机制:这两个特点都是在进程的初始化过程中实 ...

  6. AFNetworking 源代码分析

    关于其他 AFNetworking 源代码分析的其他文章: AFNetworking 概述(一) AFNetworking 的核心 AFURLSessionManager(二) 处理请求和响应 AFU ...

  7. Hadoop源代码分析 - MapReduce(转载)

    1. Hadoop源代码分析(MapReduce概论) http://caibinbupt.javaeye.com/blog/336467

  8. RTMPdump(libRTMP) 源代码分析 3: AMF编码

    2019独角兽企业重金招聘Python工程师标准>>> 注:此前写了一些列的分析RTMPdump(libRTMP)源代码的文章,在此列一个列表: RTMPdump 源代码分析 1: ...

  9. Android系统默认Home应用程序(Launcher)的启动过程源代码分析(3)

    Step 13.  ActivityStack.startActivityLocked 这个函数定义在frameworks/base/services/java/com/android/server/ ...

  10. 区块链教程Fabric1.0源代码分析scc(系统链码)

    区块链教程Fabric1.0源代码分析scc(系统链码),2018年下半年,区块链行业正逐渐褪去发展之初的浮躁.回归理性,表面上看相关人才需求与身价似乎正在回落.但事实上,正是初期泡沫的渐退,让人们更 ...

最新文章

  1. MXNet 安装 Windows
  2. Bit-Z开展BTC交易赛 赢200倍交易奖励
  3. php跨服务器访问不了,php跨服务器访问方法小结
  4. 页面缓存,数据源缓存
  5. Linux进阶之进程与线程
  6. Struts中ActionActionForm
  7. 《DSP using MATLAB》示例Example4.2
  8. 获取referer中的请求参数_http请求的referer属性
  9. 曝光!人工智能行业薪酬到底有多高?
  10. 学会这一方法,轻松实现Excel批量转PDF,快来码住
  11. 用计算机表白的数字,数字表白密码 表白密码大全
  12. 锐取电视墙服务器型号,锐取录播系统 CL4000系列多媒体录播一体机--投影时代产品专区...
  13. fidder无法抓取浏览器Https,提示“证书错误”
  14. 电脑硬盘空间如何免费扩容2TB+
  15. C++面试之Linux操作系统
  16. php不使用第三变量互换,总结PHP不用第三个变量交换两个变量的值的几种方法
  17. 仿vivo控制中心下载_仿vivo控制中心下载_手机控制中心app
  18. statsby: 不用循环语句的循环
  19. 《开源软件架构》--nginx配置与内部(三)
  20. MySQL命令行操作

热门文章

  1. 【CodeForces - 1102C 】Doors Breaking and Repairing (思维,简单博弈)
  2. 【NOIP2013积木大赛,NOIP2018铺设道路】积木大赛(思维,贪心)
  3. 大学计算机二级培训蹭课,本人在复旦大学蹭课总结的一点小小的经验
  4. 考研生物和计算机结合的专业,2020考研:生物医学工程,考研是考原专业还是跨专业考计算机好?...
  5. matlab cd参数,MATLAB变量参数列表​
  6. python os模块方法_python os模块方法总结
  7. 使用Log4j为项目配置日志输出应用详细总结及示例演示.
  8. leetcode597. 好友申请 I :总体通过率(SQL)
  9. Python(23)-面向对象2-继承,多态
  10. vue process.env获取不到_从文档开始,重学vue(下)源码级别