mybatis深入理解(一)之 # 与 $ 区别以及 sql 预编译
mybatis 中使用 sqlMap 进行 sql 查询时,经常需要动态传递参数,例如我们需要根据用户的姓名来筛选用户时,sql 如下:
select * from user where name = "ruhua";
上述 sql 中,我们希望 name 后的参数 "ruhua" 是动态可变的,即不同的时刻根据不同的姓名来查询用户。在 sqlMap 的 xml 文件中使用如下的 sql 可以实现动态传递参数 name:
select * from user where name = #{name};
或者
select * from user where name = ${name};
对于上述这种查询情况来说,使用 #{ } 和 ${ } 的结果是相同的,但是在某些情况下,我们只能使用二者其一。
'#' 与 '$'
区别
动态 SQL 是 mybatis 的强大特性之一,也是它优于其他 ORM 框架的一个重要原因。mybatis 在对 sql 语句进行预编译之前,会对 sql 进行动态解析,解析为一个 BoundSql 对象,也是在此处对动态 SQL 进行处理的。
在动态 SQL 解析阶段, #{ } 和 ${ } 会有不同的表现:
#{ } 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符。
例如,sqlMap 中如下的 sql 语句
select * from user where name = #{name};
解析为:
select * from user where name = ?;
一个 #{ } 被解析为一个参数占位符 ?
。
而,
${ } 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换
例如,sqlMap 中如下的 sql
select * from user where name = ${name};
当我们传递的参数为 "ruhua" 时,上述 sql 的解析为:
select * from user where name = "ruhua";
预编译之前的 SQL 语句已经不包含变量 name 了。
综上所得, ${ } 的变量的替换阶段是在动态 SQL 解析阶段,而 #{ }的变量的替换是在 DBMS 中。
用法 tips
1、能使用 #{ } 的地方就用 #{ }
首先这是为了性能考虑的,相同的预编译 sql 可以重复利用。
其次,${ } 在预编译之前已经被变量替换了,这会存在 sql 注入问题。例如,如下的 sql,
select * from ${tableName} where name = #{name}
假如,我们的参数 tableName 为 user; delete user; --
,那么 SQL 动态解析阶段之后,预编译之前的 sql 将变为
select * from user; delete user; -- where name = ?;
--
之后的语句将作为注释,不起作用,因此本来的一条查询语句偷偷的包含了一个删除表数据的 SQL!
2、表名作为变量时,必须使用 ${ }
这是因为,表名是字符串,使用 sql 占位符替换字符串时会带上单引号 ''
,这会导致 sql 语法错误,例如:
select * from #{tableName} where name = #{name};
预编译之后的sql 变为:
select * from ? where name = ?;
假设我们传入的参数为 tableName = "user" , name = "ruhua",那么在占位符进行变量替换后,sql 语句变为
select * from 'user' where name='ruhua';
上述 sql 语句是存在语法错误的,表名不能加单引号 ''
(注意,反引号 ``是可以的)。
sql预编译
定义
sql 预编译指的是数据库驱动在发送 sql 语句和参数给 DBMS 之前对 sql 语句进行编译,这样 DBMS 执行 sql 时,就不需要重新编译。
为什么需要预编译
JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译
预编译阶段可以优化 sql 的执行。
预编译之后的 sql 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的sql,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。预编译语句对象可以重复利用。
把一个 sql 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个sql,可以直接使用这个缓存的 PreparedState 对象。
mybatis 默认情况下,将对所有的 sql 进行预编译。
mysql预编译源码解析
mysql 的预编译源码在 com.mysql.jdbc.ConnectionImpl
类中,如下:
public synchronized java.sql.PreparedStatement prepareStatement(String sql,int resultSetType, int resultSetConcurrency) throws SQLException {checkClosed();//// FIXME: Create warnings if can't create results of the given// type or concurrency//PreparedStatement pStmt = null;boolean canServerPrepare = true;// 不同的数据库系统对sql进行语法转换String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql;// 判断是否可以进行服务器端预编译if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);}// 如果可以进行服务器端预编译if (this.useServerPreparedStmts && canServerPrepare) {// 是否缓存了PreparedStatement对象if (this.getCachePreparedStatements()) {synchronized (this.serverSideStatementCache) {// 从缓存中获取缓存的PreparedStatement对象pStmt = (com.mysql.jdbc.ServerPreparedStatement)this.serverSideStatementCache.remove(sql);if (pStmt != null) {// 缓存中存在对象时对原 sqlStatement 进行参数清空等((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false);pStmt.clearParameters();}if (pStmt == null) {try {// 如果缓存中不存在,则调用服务器端(数据库)进行预编译pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,this.database, resultSetType, resultSetConcurrency);if (sql.length() < getPreparedStatementCacheSqlLimit()) {((com.mysql.jdbc.ServerPreparedStatement)pStmt).isCached = true;}// 设置返回类型以及并发类型pStmt.setResultSetType(resultSetType);pStmt.setResultSetConcurrency(resultSetConcurrency);} catch (SQLException sqlEx) {// Punt, if necessaryif (getEmulateUnsupportedPstmts()) {pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);if (sql.length() < getPreparedStatementCacheSqlLimit()) {this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);}} else {throw sqlEx;}}}}} else {// 未启用缓存时,直接调用服务器端进行预编译try {pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,this.database, resultSetType, resultSetConcurrency);pStmt.setResultSetType(resultSetType);pStmt.setResultSetConcurrency(resultSetConcurrency);} catch (SQLException sqlEx) {// Punt, if necessaryif (getEmulateUnsupportedPstmts()) {pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);} else {throw sqlEx;}}}} else {// 不支持服务器端预编译时调用客户端预编译(不需要数据库 connection )pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);}return pStmt;}
流程图如下所示:
mybatis之sql动态解析以及预编译源码
mybatis sql 动态解析
mybatis 在调用 connection 进行 sql 预编译之前,会对sql语句进行动态解析,动态解析主要包含如下的功能:
占位符的处理
动态sql的处理
参数类型校验
mybatis强大的动态SQL功能的具体实现就在此。动态解析涉及的东西太多,以后再讨论。
总结
本文主要深入探究了 mybatis 对 #{ } 和 ${ }的不同处理方式,并了解了 sql 预编译。
from: https://segmentfault.com/a/1190000004617028
mybatis深入理解(一)之 # 与 $ 区别以及 sql 预编译相关推荐
- mybatis以及预编译如何防止SQL注入
SQL注入是一种代码注入技术,用于攻击数据驱动的应用,恶意的SQL语句被插入到执行的实体字段中(例如,为了转储数据库内容给攻击者).[摘自] SQL injection - Wikipedia SQL ...
- [html] 说下你对组件、模块、元素的理解,它们的区别在哪里?
[html] 说下你对组件.模块.元素的理解,它们的区别在哪里? 元素:元素是网页的一部分,在 XML 和 HTML 中,一个元素可以包含一个数据详情或者是一块文本或者是一张照片,亦或是什么也不包含. ...
- 02 理解==与Equals()的区别及用法 1214
02 理解==与Equals()的区别及用法 01 02 03
- MyBatis中的#和$之间的区别
MyBatis 中使用 parameterType 向 SQL 语句传参,parameterType支持的类型可以是基本类型int,String,HashMap和java自定义类型. 在SQL中引用这 ...
- mybatis中association 和collection 的区别
mybatis中association 和collection 的区别:https://zhidao.baidu.com/question/1240407172484106299.html 两个实体类 ...
- Java 深入理解深拷贝和浅拷贝区别
title: Java 深入理解深拷贝和浅拷贝区别 date: 2021-6-19 updated: 2021-6-19 tags: Java 深拷贝和浅拷贝 categories: 面试 Java ...
- 【MyBatis】resultMap和resultType的区别
mybatis中resultMap和resultType的区别 mybatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap.resultType是 ...
- MyBatis学习总结(13)——Mybatis查询之resultMap和resultType区别
MyBatis的每一个查询映射的返回类型都是ResultMap,只是当我们提供的返回类型属性是resultType的时候,MyBatis对自动的给我们把对应的值赋给resultType所指定对象的属性 ...
- Mybatis中resultMap和resultType的区别
MyBatis的每一个查询映射的返回类型都是ResultMap,只是当我们提供的返回类型属性是resultType的时候,MyBatis对自动的给我们把对应的值赋给resultType所指定对象的属性 ...
最新文章
- 2021-07-23 图像分割
- 7 成中国职场人厌班,我们为什么会陷入职业倦怠?
- 正则表达式中模式修正符作用详解(i、g、m、s、x、e)
- 《云计算揭秘企业实施云计算的核心问题》——导读
- 在别人那看到的很不错的ext.net的基本讲解
- 在Windows服务器上开启SNMP代理程序
- 关于struts,spring,hibernate的几个问题
- android 通知栏按钮,android 通知栏添加按钮点击效果
- cisco 动态路由协议RIP笔记
- 深入理解Spring Redis的使用 (五)、常见问题汇总
- java中关于length的真确理解~~~~有补充的请跟帖~~~
- circle loss代码实现_Python全栈之路-23-使用Python实现Logistic回归算法
- 可以学习的国外课件链接地址(自己收集)
- python 生成嵌套字典
- springboot报错Table 'wechat.hibernate_sequence' doesn't exist
- mybatis注册映射文件
- 实变函数自制笔记9:勒贝格积分的极限定理
- 关于Alipay支付宝接口(Java版)
- Visual Studio下载太慢的解决方法
- 什么是用户代理样式表
热门文章
- 用 Java 技术创建 RESTful Web 服务--转载
- Lesson 13.3 梯度不平稳性与Glorot条件
- 【风险管理】假如我是风控经理,会搭建怎样的风控团队
- 支持向量机:Outliers
- Gartner预测2015年的十大IT战略发展趋势
- 将pdf转换html_pdf文件怎么转换成html网页格式?用什么方法来转换?
- 深入理解分布式技术 - 分库分表后的扩容解决方案
- Algorithms_算法思想_递归分治
- CentOS-创建yum本地源
- Property or field 'username' cannot be found on null