https://segmentfault.com/a/1190000004617028
[TOC]
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 來抽象預編譯語句,使用預編譯
1. **預編譯階段可以優化 sql 的執行**。
預編譯之后的 sql 多數情況下可以直接執行,DBMS 不需要再次編譯,越復雜的sql,編譯的復雜度將越大,預編譯階段可以合并多次操作為一個操作。
2. **預編譯語句對象可以重復利用**。
把一個 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 預編譯。
- 數據庫
- CAP定理
- 關系模型
- 關系數據庫
- NoSQL
- ODBC
- JDBC
- ODBC、JDBC和四種驅動類型
- mysql
- 安裝與配置
- CentOS 7 安裝 MySQL
- 優化
- 比較全面的MySQL優化參考
- 1、硬件層相關優化
- 1.1、CPU相關
- 1.2、磁盤I/O相關
- 2、系統層相關優化
- 2.1、文件系統層優化
- 2.2、其他內核參數優化
- 3、MySQL層相關優化
- 3.1、關于版本選擇
- 3.2、關于最重要的參數選項調整建議
- 3.3、關于Schema設計規范及SQL使用建議
- 3.4、其他建議
- 后記
- Mysql設計與優化專題
- ER圖,數據建模與數據字典
- 數據中設計中的范式與反范式
- 字段類型與合理的選擇字段類型
- 表的垂直拆分和水平拆分
- 詳解慢查詢
- mysql的最佳索引攻略
- 高手詳解SQL性能優化十條經驗
- 優化SQL查詢:如何寫出高性能SQL語句
- MySQL索引原理及慢查詢優化
- 數據庫SQL優化大總結之 百萬級數據庫優化方案
- 數據庫性能優化之SQL語句優化1
- 【重磅干貨】看了此文,Oracle SQL優化文章不必再看!
- MySQL 對于千萬級的大表要怎么優化?
- MySQL 數據庫設計總結
- MYSQL性能優化的最佳20+條經驗
- 數據操作
- 數據語句操作類型
- DCL
- 修改Mysql數據庫名的5種方法
- DML
- 連接
- 連接2
- DDL
- 數據類型
- 字符集
- 表引擎
- 索引
- MySQL理解索引、添加索引的原則
- mysql建索引的幾大原則
- 淺談mysql的索引設計原則以及常見索引的區別
- 常用工具簡介
- QA
- MySQL主機127.0.0.1與localhost區別總結
- 視圖(view)
- 觸發器
- 自定義函數和存儲過程的使用
- 事務(transaction)
- 范式與反范式
- 常用函數
- MySQL 數據類型 詳解
- Mysql數據庫常用分庫和分表方式
- 隔離級別
- 五分鐘搞清楚MySQL事務隔離級別
- mysql隔離級別及事務傳播
- 事務隔離級別和臟讀的快速入門
- 數據庫引擎中的隔離級別
- 事務隔離級別
- Innodb中的事務隔離級別和鎖的關系
- MySQL 四種事務隔離級的說明
- Innodb鎖機制:Next-Key Lock 淺談
- SQL函數和存儲過程的區別
- mongo
- MongoDB設置訪問權限、設置用戶
- redis
- ORM
- mybatis
- $ vs #
- mybatis深入理解(一)之 # 與 $ 區別以及 sql 預編譯
- 電商設計
- B2C電子商務系統研發——概述篇
- B2C電子商務系統研發——商品數據模型設計
- B2C電子商務系統研發——商品模塊E-R圖建模
- B2C電子商務系統研發——商品SKU分析和設計(一)
- B2C電子商務系統研發——商品SKU分析和設計(二)
- 數據庫命名規范--通用