mybatis深入理解(一)之 # 与 $ 区别以及 sql 预编译
mybatis 中使用 sqlMap 進行 sql 查詢時,經(jīng)常需要動態(tài)傳遞參數(shù),例如我們需要根據(jù)用戶的姓名來篩選用戶時,sql 如下:
select?*?from?user?where?name =?"ruhua";上述 sql 中,我們希望 name 后的參數(shù) "ruhua" 是動態(tài)可變的,即不同的時刻根據(jù)不同的姓名來查詢用戶。在 sqlMap 的 xml 文件中使用如下的 sql 可以實現(xiàn)動態(tài)傳遞參數(shù) name:
select * from user where name = #{name};或者
select * from user where name = ${name};對于上述這種查詢情況來說,使用 #{ } 和 ${ } 的結(jié)果是相同的,但是在某些情況下,我們只能使用二者其一。
'#' 與 '$'
區(qū)別
動態(tài) SQL?是 mybatis 的強大特性之一,也是它優(yōu)于其他 ORM 框架的一個重要原因。mybatis 在對 sql 語句進行預編譯之前,會對 sql 進行動態(tài)解析,解析為一個 BoundSql 對象,也是在此處對動態(tài) SQL 進行處理的。
在動態(tài) SQL 解析階段, #{ } 和 ${ } 會有不同的表現(xiàn):
#{ } 解析為一個 JDBC 預編譯語句(prepared statement)的參數(shù)標記符。
例如,sqlMap 中如下的 sql 語句
select?*?from?user?where?name = #{name};解析為:
select?*?from?user?where?name = ?;一個 #{ } 被解析為一個參數(shù)占位符???。
而,
${ } 僅僅為一個純碎的 string 替換,在動態(tài) SQL 解析階段將會進行變量替換
例如,sqlMap 中如下的 sql
select?*?from?user?where?name = ${name};當我們傳遞的參數(shù)為 "ruhua" 時,上述 sql 的解析為:
select?*?from?user?where?name =?"ruhua";預編譯之前的 SQL 語句已經(jīng)不包含變量 name 了。
綜上所得, ${ } 的變量的替換階段是在動態(tài) SQL 解析階段,而 #{ }的變量的替換是在 DBMS 中。
用法 tips
1、能使用 #{ } 的地方就用 #{ }
首先這是為了性能考慮的,相同的預編譯 sql 可以重復利用。
其次,${ } 在預編譯之前已經(jīng)被變量替換了,這會存在 sql 注入問題。例如,如下的 sql,
select?*?from?${tableName}?where?name =?#{name}假如,我們的參數(shù) tableName 為?user; delete user; --,那么 SQL 動態(tài)解析階段之后,預編譯之前的 sql 將變?yōu)?/p> select?*?from?user;?delete?user;?-- where name = ?;
--?之后的語句將作為注釋,不起作用,因此本來的一條查詢語句偷偷的包含了一個刪除表數(shù)據(jù)的 SQL!
2、表名作為變量時,必須使用 ${ }
這是因為,表名是字符串,使用 sql 占位符替換字符串時會帶上單引號?'',這會導致 sql 語法錯誤,例如:
select?*?from?#{tableName}?where?name = #{name};預編譯之后的sql 變?yōu)?#xff1a;
select?*?from???where?name = ?;假設我們傳入的參數(shù)為 tableName = "user" , name = "ruhua",那么在占位符進行變量替換后,sql 語句變?yōu)?/p> select?*?from?'user'?where?name='ruhua';
上述 sql 語句是存在語法錯誤的,表名不能加單引號?''(注意,反引號 ``是可以的)。
sql預編譯
定義
sql 預編譯指的是數(shù)據(jù)庫驅(qū)動在發(fā)送 sql 語句和參數(shù)給 DBMS 之前對 sql 語句進行編譯,這樣 DBMS 執(zhí)行 sql 時,就不需要重新編譯。
為什么需要預編譯
JDBC 中使用對象 PreparedStatement 來抽象預編譯語句,使用預編譯
預編譯階段可以優(yōu)化 sql 的執(zhí)行。
預編譯之后的 sql 多數(shù)情況下可以直接執(zhí)行,DBMS 不需要再次編譯,越復雜的sql,編譯的復雜度將越大,預編譯階段可以合并多次操作為一個操作。
預編譯語句對象可以重復利用。
把一個 sql 預編譯后產(chǎn)生的 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;// 不同的數(shù)據(jù)庫系統(tǒng)對sql進行語法轉(zhuǎn)換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 進行參數(shù)清空等((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false);pStmt.clearParameters();}if (pStmt == null) {try {// 如果緩存中不存在,則調(diào)用服務器端(數(shù)據(jù)庫)進行預編譯pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql,this.database, resultSetType, resultSetConcurrency);if (sql.length() < getPreparedStatementCacheSqlLimit()) {((com.mysql.jdbc.ServerPreparedStatement)pStmt).isCached = true;}// 設置返回類型以及并發(fā)類型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 {// 未啟用緩存時,直接調(diào)用服務器端進行預編譯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 {// 不支持服務器端預編譯時調(diào)用客戶端預編譯(不需要數(shù)據(jù)庫 connection )pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);}return pStmt;}流程圖如下所示:
mybatis之sql動態(tài)解析以及預編譯源碼
mybatis sql 動態(tài)解析
mybatis 在調(diào)用 connection 進行 sql 預編譯之前,會對sql語句進行動態(tài)解析,動態(tài)解析主要包含如下的功能:
-
占位符的處理
-
動態(tài)sql的處理
-
參數(shù)類型校驗
mybatis強大的動態(tài)SQL功能的具體實現(xiàn)就在此。動態(tài)解析涉及的東西太多,以后再討論。
總結(jié)
本文主要深入探究了 mybatis 對 #{ } 和 ${ }的不同處理方式,并了解了 sql 預編譯。
from:?https://segmentfault.com/a/1190000004617028
總結(jié)
以上是生活随笔為你收集整理的mybatis深入理解(一)之 # 与 $ 区别以及 sql 预编译的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 5.1 代码合并:Merge、Rebas
- 下一篇: 超时,重试,熔断,限流