当sql带有参数时,Mybatis是如何处理的。使用的是User类。

//省略get set方法
public class User {private int id;private String name;private String phone;
}

例1 带有全局变量的sql

//UserMapper中的dao接口
List<User> getByglobal();
<select id="getByglobal" resultType="com.entity.User">select * from user where id = ${globalId}</select>
<!--mybatis.xml中的部分配置--><properties><property name="globalId" value="1"/></properties>

注意我是用的符号为$。在这个例子中globalId是在mybatis.xml文件中的property配置的。接口不传参数。

,我们知道每一个查询语句都会被包装成一个MappedStatement,这个对象用来存放我们的sql语句,返回类型,id等等。让我们回到之前的代码。

//XMLMapperBuilder.configurationElementprivate void configurationElement(XNode context) {try {//mapper的缓存信息,命名空间等会被临时保存到MapperBuilderAssistant中,最后把这些公用的信息在存到MappedStatement中String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));//该节点已经被废弃parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));//在解析增删改查节点时,每个节点都会生成一个mapperStatement对象并保存到配置文件类中.//mapperStatement保存这这个节点的全部信息,如id,fetchSize,timeoutbuildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}}

这部分代码应该不陌生,现在我们从context.evalNodes("select|insert|update|delete")开始。该方法开始解析增删改查的节点

//XNode.evalNodespublic List<XNode> evalNodes(String expression) {return xpathParser.evalNodes(node, expression);}
public List<XNode> evalNodes(Object root, String expression) {List<XNode> xnodes = new ArrayList<>();NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);for (int i = 0; i < nodes.getLength(); i++) {//这里创建的新的节点并添加到结合中,解析也是在创建节点的时候开始的。xnodes.add(new XNode(this, nodes.item(i), variables));}return xnodes;}

XNode构造方法,在创建新节点的时候,会在构造器中先进性解析,也就是调用parseBody方法.

public XNode(XPathParser xpathParser, Node node, Properties variables) {this.xpathParser = xpathParser;this.node = node;this.name = node.getNodeName();this.variables = variables;//获取节点的属性//例如这个select就有id和resultType两个属性this.attributes = parseAttributes(node);//解析节点里面的内容,也就是sql了this.body = parseBody(node);}

variables传入配置中的全局变量

//XNode.parseBodyprivate String parseBody(Node node) {//获取当前节点的信息//例子中这里返回空String data = getBodyData(node);if (data == null) {//获取孩子节点的信息NodeList children = node.getChildNodes();for (int i = 0; i < children.getLength(); i++) {Node child = children.item(i);//获取当前节点的信息data = getBodyData(child);if (data != null) {break;}}}return data;}

为什么select节点getBodyData会返回空呢,从它的方法体中可以看出,首先它会判断节点的类型,select这个节点是ELEMENT_NODE类型,不属于它要求的文本类型或者部分节点类型。那么就直接返回空了。而当select的孩子节点,也就是sql语句select * from user where id = ${globalId}这个节点调用getBodyData方法时,sql语句是文本类型的,满足条件,才会使用解析器开始解析。

//XNode.getBodyDataprivate String getBodyData(Node child) {//判断节点的类型if (child.getNodeType() == Node.CDATA_SECTION_NODE|| child.getNodeType() == Node.TEXT_NODE) {String data = ((CharacterData) child).getData();data = PropertyParser.parse(data, variables);return data;}return null;}
//PropertyParser.parsepublic static String parse(String string, Properties variables) {//先创建一个处理器VariableTokenHandler handler = new VariableTokenHandler(variables);//创建解析器GenericTokenParser parser = new GenericTokenParser("${", "}", handler);//进行解析return parser.parse(string);}

这里出现了很多陌生的类。首先是GenericTokenParser通用类型的解析器,他能根据传入的参数做出相应。如果参数满足条件,就会调用handler处理器来处理参数。每个handler都要实现handleToken方法,该方法就是用来处理参数的。

例如这里传入的是以${作为开头,}作为结尾。如果传入的字符串包含一个或者多个这样的格式,就会调用VariableTokenHandler.handleToken,该方法会试图从全局中找到该变量,并修改成具体的值。

VariableTokenHandler.handleToken 传入String变量globalId,将其替换成1并返回。

public String handleToken(String content) {//variables里面存放全局的变量,为空直接returnif (variables != null) {String key = content;//是否存在默认值,默认是falseif (enableDefaultValue) {final int separatorIndex = content.indexOf(defaultValueSeparator);String defaultValue = null;if (separatorIndex >= 0) {key = content.substring(0, separatorIndex);defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());}if (defaultValue != null) {return variables.getProperty(key, defaultValue);}}//variables是用来存放全局变量的容器。//这里会从全局变量中找到我们定义的globalId,然后将对应的值返回,这样我们的sql就拼接完成了if (variables.containsKey(key)) {return variables.getProperty(key);}}return "${" + content + "}";}}

解析器代码,根据传入的标记开始解析,这里传入开始标记${和结束标记$}。在这之后还会用来解析#{}。代码比较长,最好打个断点进去看。

//GenericTokenParser.parse
public String parse(String text) {if (text == null || text.isEmpty()) {return "";}//查找开始标记,如果不存在返回-1 ,存在返回偏移量int start = text.indexOf(openToken);if (start == -1) {return text;}char[] src = text.toCharArray();int offset = 0;final StringBuilder builder = new StringBuilder();//这个变量用来存放中间的字符,如${id}中的idStringBuilder expression = null;//如果存在开始标志while (start > -1) {//这里将从offset开始,一直到start的字符先放入builder中//例如select * from user where id = if (start > 0 && src[start - 1] == '\\') {// this open token is escaped. remove the backslash and continue.builder.append(src, offset, start - offset - 1).append(openToken);offset = start + openToken.length();} else {// found open token. let's search close token.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);//取到中间字符globalIdwhile (end > -1) {if (end > offset && src[end - 1] == '\\') {// this close token is escaped. remove the backslash and continue.expression.append(src, offset, end - offset - 1).append(closeToken);offset = end + closeToken.length();end = text.indexOf(closeToken, offset);} else {expression.append(src, offset, end - offset);break;}}if (end == -1) {// close token was not found.builder.append(src, start, src.length - start);offset = src.length;} else {//这里根据不同的处理器会有不同的操作,刚才传入的是VariableTokenHandlerbuilder.append(handler.handleToken(expression.toString()));offset = end + closeToken.length();}}start = text.indexOf(openToken, offset);}if (offset < src.length) {builder.append(src, offset, src.length - offset);}return builder.toString();}

到这里全局变量就解析完成了,那么如果在全局变量中没有找到对应的值该怎么办呢?例如我这里使用的sql是select * from user where id = ${id},而不是${globalId},那么根据VariableTokenHandler处理器,它会原封不动的进行返回,等待后文的解析。

顺便一提,这一部分的解析实在解析我们的配置文件的时候就发生了,方法入口为context.evalNodes("select|insert|update|delete"),在解析配置的时候,其他节点也大量使用了context.evalNodes()方法去解,所以只要当配置mybatis.xml文件中的properties节点解析完成之后,里面的变量就是能全局使用了,这也是为什么properties节点要放在第一个解析。

又由于这个通用解析器只解析${XXX}格式的变量,所以全局的变量不能写成#{xxx}.

需要更多教程,微信扫码即可

Mybatis源码解析:sql参数处理(1)相关推荐

  1. Mybatis源码解析-sql执行

    一.传统的jdbc操作步骤 获取驱动 获取jdbc连接 创建参数化预编译的sql 绑定参数 发送sql到数据库执行 将将获取到的结果集返回应用 关闭连接 传统的jdbc代码: package com. ...

  2. MyBatis 源码分析 - SQL 的执行过程

    本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析.运 ...

  3. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  4. mybatis源码解析(一)

    Mybatis 源码解析 (一) 一. ORM框架的作用 实际开发系统时,我们可通过JDBC完成多种数据库操作.这里以传统JDBC编程过程中的查询操作为例进行说明,其主要步骤如下: (1)注册数据库驱 ...

  5. Mybatis源码解析《二》

    导语 在前一篇文章Mybatis源码解析<一>中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文 ...

  6. mybatis源码解析一 xml解析(解析器)

    最近闲来无事,看着一些源码类的书籍,只是光看,好像并不能给自己很好的益处,无法记下来,所以就有了这个Mybatis源码解析系列的博客.网上也有大量的源码解析,在此记录有两个原因,一是为了加深自己的印象 ...

  7. Mybatis源码解析(一):环境搭建

    Mybatis源码系列文章 手写源码(了解源码整体流程及重要组件) Mybatis源码解析(一):环境搭建 Mybatis源码解析(二):全局配置文件的解析 Mybatis源码解析(三):映射配置文件 ...

  8. mybatis源码解析1_sessionFactory

    注 一下内容都是根据尚硅谷2018年mybatis源码解析做的课程笔记,视频链接: https://www.bilibili.com/video/BV1mW411M737?p=74&share ...

  9. 对标阿里P8的MyBatis源码解析文档,面试/涨薪两不误,已献出膝盖

    移动互联网的特点是大数据.高并发,对服务器往往要求分布式.高性能.高灵活等,而传统模式的Java数据库编程框架已经不再适用了. 在这样的背景下,一个Java的持久框架MyBatis走入了我们的世界,它 ...

  10. [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构

    [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 文章目录 [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 0x00 摘要 0x01使用 1.1 ...

最新文章

  1. java int interger_java面试题之int和Integer的区别
  2. Codeforces 1254C/1255F Point Ordering (交互题)
  3. 2017年10月21日 数据库基础三大范式
  4. Cocoa/iPhone App/静态库 嵌入资源文件 rtb v0.1发布
  5. JEP 277“增强弃用”非常好。 但这是一个更好的选择
  6. mapreduce介绍_MapReduce:简单介绍
  7. iphone版 天行skyline_Skyline QT
  8. 最优化学习笔记(二)——二分法
  9. springboot 2.4.4java.sql.SQLException Access denied for user ‘root‘@‘localhost‘ (using password YES)
  10. android浏览系统分区,Android系统分区
  11. 4.寻找两个正序数组的中位数
  12. 绑定校园卡服务器没有响应,单击校园卡管理系统常见问题汇总.doc
  13. cad添加自己线性_如何自定义自己需要的CAD线型?
  14. python numpy的shape函数
  15. 企业终端安全应该包括哪几部分?
  16. 一种文件捆绑型病毒研究
  17. 深入浅出keepalived+nginx实现网关主备高可用
  18. 用什么软件可以修改PDF文件,软件的操作方法
  19. 全球与中国Adobe渠道合作伙伴市场现状及未来发展趋势
  20. matlab relieff函数,数据挖掘 ReliefF和K-means算法的应用

热门文章

  1. 比尔盖茨正式退出微软董事会:将继续担任技术顾问
  2. 降级无门!苹果关闭iOS 13.2.2验证通道
  3. 坑哭了!老牌教育机构也崩盘,疑失联跑路,学员却还得继续还贷
  4. 竟然不是马云!第一个使用支付宝的人找到了 :受赠终身钻石会员
  5. 苹果“自研”心不死 仍考虑收购英特尔基带业务
  6. 使用Python分析网易云歌曲评论信息,我发现了这些有趣的规律
  7. 如何使用libxml2库[转]
  8. 中通科技移动自动化测试的革新与探索
  9. Python产生随机数(转)
  10. Python机器学习:贝叶斯文本分类器