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 necessary
if (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 necessary
if (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 预编译。
mybatis之 # 与 $ 区别以及 sql 预编译的更多相关文章
- mybatis深入理解之 # 与 $ 区别以及 sql 预编译
mybatis 中使用 sqlMap 进行 sql 查询时,经常需要动态传递参数,例如我们需要根据用户的姓名来筛选用户时,sql 如下: select * from user where name = ...
- mybatis深入理解(一)之 # 与 $ 区别以及 sql 预编译
mybatis 中使用 sqlMap 进行 sql 查询时,经常需要动态传递参数,例如我们需要根据用户的姓名来筛选用户时,sql 如下: select * from user where name = ...
- 从Mybatis中#和$的区别到SQL预编译
#和$的区别 Mybatis中参数传递可以通过#和$设置.它们的区别是什么呢? # Mybatis在解析SQL语句时,sql语句中的参数会被预编译为占位符问号? $ Mybatis在解析SQL语句时, ...
- sql 预编译 in
sql : "select * from json where id in (:paramName)"; 在使用Hibernate时,sql in的预编译语句为query.setP ...
- 关于SQL预编译问题。
标准都是sql.add('insert a (b,c,d)values(:a,:b,:c)');params.parambyname('a').asstring:='';...
- sql预编译&动态语句静态语句
https://www.cnblogs.com/micrari/p/7112781.html https://www.cnblogs.com/MarsDing/p/9871703.html https ...
- 浅谈 MySQL的预编译
之前的一篇 Mybatis中 #{}和${}的区别 中涉及到通过 SQL预编译和 #{} 传值 的方式防止SQL注入. 由此引发了想了解预编译的想法.那么什么是预编译那? 一.三个阶段: 词法和语义解 ...
- mybatis以及预编译如何防止SQL注入
SQL注入是一种代码注入技术,用于攻击数据驱动的应用,恶意的SQL语句被插入到执行的实体字段中(例如,为了转储数据库内容给攻击者).[摘自] SQL injection - Wikipedia SQL ...
- SQL注入和Mybatis预编译防止SQL注入
什么是SQL注入?? 所谓SQL注入,就是通过把SQL命令插入到Web表单提交或页面请求url的查询字符串,最终达到欺骗服务器执行恶意的SQL命令.具体来说,它是利用现有应用程序,将(恶意)的SQL命 ...
随机推荐
- SpringMVC 上传文件(文件非必填)MultipartHttpServletRequest
原文:https://blog.csdn.net/dorothy1224/article/details/79136676 上传文件(文件非必填)MultipartHttpServletRequest ...
- C++ public private protect 继承关系(链接)
基础链接 总结: public继承基类成员访问权限没有变化; protected继承基类public和protected权限变为protected,基类private不变. private继承基类p ...
- Rstdio快捷键总结
Rstdio常用快捷键总结 在R语言里面输入了一个不完整的指令以后 左下方的console pane一直有加号+,不管输入什么都是加号,只用推出重新打开Rstudio才行,后来google了下,按左上 ...
- bzoj 2055 80人环游世界
有源汇上下界最小费用可行流. 将每个国家拆点. 源点向一个新建节点连一条上界为总人数下界为0费用为0的边. 新建节点向每个国家的入点连一条上界为正无穷下界为0费用为0的边. 每个国家的入点向出点连一条 ...
- nginx访问日志出现大量的500状态信息,用postman返回 Internal Server Error,Too Many Attempts.错误的解决办法
用postman的post方法访问某个URL时,出现以下错误: { "status": "1", "message": " Int ...
- Java基础-日期格式化DateFormat类简介
Java基础-日期格式化DateFormat类简介 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.DateFormat类概述 DateFormat 是日期/时间格式化子类的抽象 ...
- 视音频数据处理入门:FLV封装格式解析
===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...
- 1130 N的阶乘的长度 V2(斯特林近似)
1130 N的阶乘的长度 V2(斯特林近似) 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 输入N求N的阶乘的10进制表示的长度.例如6! = 720, ...
- 数据结构编程实验——chapter10-应用经典二叉树编程
二叉树不仅结构简单.节省内存,更重要是是这种结构有利于对数据的二分处理.之前我们提过,在二叉树的基础上能够派生很多经典的数据结构,也是下面我们将进行讨论的知识点: (1) 提高数据查找效率的二叉排 ...
- List(JDK1.7)(3)
Vector 实现了一个存储对象的增长数组.如同一个数组,它可以使用一个整型下标来访问元素.但是,vector的大小是可以根据需要增长或收缩的. 每个vector通过维护capacity和capaci ...