XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法会调用生成DynamicSqlSourceRawSqlSource这两个帮助类,本文将对此作下简单的简析

应用场景

我们在编写mybatis的sql语句的时候,经常用到的是#{}的字符去替代其中的查询入参,偶尔也会在网上看到${}这样的字符使用。

经过笔者分析源码得知,其实前者调用的为RawSqlSource帮助类进行生成具体的sql,而后者则是通过DynamicSqlSource帮助类来实现的。

选用逻辑

我们还是回到XMLLanguageDriver解析类,不管是xml方式还是注解方式解析sql,都会用到TextSqlNode这个包装类,我们可以作下简单的分析。

这里就以注解方式的解析sql为例

public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
//此处兼容XML方式的解析,条件以<script>为头结点
if (script.startsWith("<script>")) { // issue #3
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
//①解析Configuration#variable变量
script = PropertyParser.parse(script, configuration.getVariables()); // issue #127
TextSqlNode textSqlNode = new TextSqlNode(script);
// ②根据TextSqlNode的内部属性isDynamic来进行解析帮助类的分配
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}

PropertyParser#parse() ①

废话不讲,直接上源码

public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
// 此处专门查找`${}`关键字符,并替换为相应的variable值
return parser.parse(string);
}

附属其中的VariableTokenHandler内部静态类-对指定字符进行处理,此处特指${}

	private Properties variables;

    public VariableTokenHandler(Properties variables) {
this.variables = variables;
} public String handleToken(String content) {
//如果variables属性中存在则直接替换,没有则返回原来的内容
if (variables != null && variables.containsKey(content)) {
return variables.getProperty(content);
}
return "${" + content + "}";
}
  1. VariableTokenHandler的替换原则为查找variables中是否含有${}内的key,没有则返回原来的格式

  2. 此处插一句,这里的variables是如何配置呢,spring方面主要通过创建SqlsessionFactoryBean时设置configurationProperties属性即可;

    也可通过ibatis主配置文件的properties节点进行配置

TextSqlNode#isDynamic() ②

我们直接看源码

public boolean isDynamic() {
// 只要找到${}这样的字符则直接设置其内部属性isDynamic=true
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
// 返回是否为${}方式解析
return checker.isDynamic();
}

TextSqlNode看来是用来校验其中的sql语句是否含有${}字符,有则便用DynamicSqlSource方式解析,反之则用RawSqlSource方式解析

DynamicSqlSource

笔者此处针对含${}的SQL语句方式进行简单的分析,而#{}的方式读者可自行研究。也可查阅此篇简单了解>>>Mybatis源码解析-BoundSql

DynamicSqlSource#getBoundSql()

我们关注下其是如何组装BoundSql对象的,源码奉上

public BoundSql getBoundSql(Object parameterObject) {
//sql语句解析,优先解析${}字符
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
//创建StaticSqlSource,其也会去解析#{}字符。
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
//
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}

此处我们只关注TextSqlNode#apply()方法,看下其是如何解析${}

TextSqlNode#apply()

解析${}字符串

	public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context));
//将解析得到的sql直接存到context对象中
context.appendSql(parser.parse(text));
return true;
} private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}

附属BindingTokenParser解析静态内部类部分源码

public String handleToken(String content) {
//此处拿取的相当于为方法调用的入参对象,比如dao接口query(List list),指代的便是list对象
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());
return (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
}
  1. 由上面得知,${}主要就是对dao接口的参数的内部属性的直接调用,其不会像#{}那样将入参拼装成?预表达式,而是直接生成表达式执行SQL语句,那么这里就会涉及到sql恶意注入的风险

  2. 此处插一句,ognl指的是Object-Graph Navigation Language,其为强大的表达式语言,使用过Struct2/Freemaker等的就会熟悉其中的语法

小结

  1. DynamicSqlSource解析含有${}的sql语句,而RawSqlSource解析含有#{}的sql语句

  2. DynamicSqlSource涵盖的操作比RawSqlSource多了一步,便是优先处理${}字符,其本身也会调用去解析#{}字符

  3. ${}语句的解析是最终转换为Statement直接执行,其中的参数赋值是直接赋值,不做字符串引号拼装;而#{}语句的解析是最终转换为PrepareStatement预表达式来进行sql执行,安全性很高

    举个例子:sql查询语句为select * from user where name='admin' and pwd=${pwd}

  • 如果pwd参数传入aaa or 1=1,则上述拼装后的结果为select * from user where name='admin' and pwd=aaa or 1=1。这个表达式会恒为真,直接会绕过验证,风险贼高
  • 如果上述采用#{pwd},则传入aaa or 1=1,则最后的生成语句为select * from user='admin' and pwd='aaa or 1=1'。这个表达式验证通不过,有较高的安全性,防止sql恶意注入
  1. 优推#{}方式操作入参,不建议使用${}方式

Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别的更多相关文章

  1. Mybatis源码解析(二) —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  2. Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析

    在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...

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

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

  4. mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)

    目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...

  5. mybatis源码-解析配置文件(四)之配置文件Mapper解析

    在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的 ...

  6. mybatis源码-解析配置文件(三)之配置文件Configuration解析

    目录 1. 简介 1.1 系列内容 1.2 适合对象 1.3 本文内容 2. 配置文件 2.1 mysql.properties 2.2 mybatis-config.xml 3. Configura ...

  7. Mybatis源码解析,一步一步从浅入深(一):创建准备工程

    Spring SpringMVC Mybatis(简称ssm)是一个很流行的java web框架,而Mybatis作为ORM 持久层框架,因其灵活简单,深受青睐.而且现在的招聘职位中都要求应试者熟悉M ...

  8. Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码

    在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...

  9. Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)

    在上一篇文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码 ,中我们看到 代码:XMLConfigBuilder parser = new XMLConfigBuilder(read ...

随机推荐

  1. Java获取精确到秒的时间戳(转)

    1.时间戳简介: 时间戳的定义:通常是一个字符序列,唯一地标识某一刻的时间.数字时间戳技术是数字签名技术一种变种的应用.是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01 ...

  2. EasyUI Datagrid 鼠标悬停显示单元格内容 复制代码

    EasyUI Datagrid 鼠标悬停显示单元格内容 ,halign:, align: 0 « 上一篇:LINQ to Entities 中的查询» 下一篇:去掉字符串中的非数字字符 posted ...

  3. Shrio授权验证详解

    所谓授权,就是控制你是否能访问某个资源,比如说,你可以方位page文件夹下的jsp页面,但是不可以访问page文件夹下的admin文件夹下的jsp页面. 在授权中,有三个核心元素:权限,角色,用户. ...

  4. 极极极极极简的的增删查改(CRUD)解决方案

    去年这个时候写过一篇全自动数据表格的文章http://www.cnblogs.com/liuyh/p/5747331.html.文章对自己写的一个js组件做了个概述,很多人把它当作了一款功能相似的纯前 ...

  5. Mysql数据库索引

    今天,我们来讲讲Mysql数据库的索引的一些东西,想必大家都知道索引能干吗?必然是查找数据表的时候,查找的速度快啊,尤其是那些几百万行的数据库,不建立索引,你是想考验用户的耐心吗?Mysql有多种存储 ...

  6. python 之 计数器(counter)

    Counter是对字典类型的补充,用于追踪值的出现次数. ps:具备字典的所有功能 + 自己的功能 c = Counter('abcdeabcdabcaba') print c 输出:Counter( ...

  7. tp下的memcached运用

    来源:http://blog.csdn.net/fudaoji/article/details/50722839   侵删 一.环境: lnmp开发服务器, memcached2.2.0,thinkp ...

  8. Gvim安装nerd_tree插件

    1.先去官网下载nerd_tree插件 http://www.vim.org/scripts/script.php?script_id=1658 2.解压缩将nerd_tree目录下的doc目录和pl ...

  9. oracle锁表问题解决方法

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp52 Oracle数据库操作中,我们有时会用到锁表查询以及解锁和kill进程 ...

  10. C++学习日记(二)————初始字符串类型

    使用频率高,但操作复杂的数据有哪些? 做下总结: int; double;float;char;bool这些类型用的比较频繁,但并不复杂.但对于字符串来说(char数组)用的频繁但操作又复杂,只能用一 ...