摘要:在動(dòng)態(tài)解析階段,和會(huì)有不同的表現(xiàn)解析為一個(gè)預(yù)編譯語句的參數(shù)標(biāo)記符。其次,在預(yù)編譯之前已經(jīng)被變量替換了,這會(huì)存在注入問題。預(yù)編譯語句對(duì)象可以重復(fù)利用。默認(rèn)情況下,將對(duì)所有的進(jìn)行預(yù)編譯。總結(jié)本文主要深入探究了對(duì)和的不同處理方式,并了解了預(yù)編譯。
mybatis 中使用 sqlMap 進(jìn)行 sql 查詢時(shí),經(jīng)常需要?jiǎng)討B(tài)傳遞參數(shù),例如我們需要根據(jù)用戶的姓名來篩選用戶時(shí),sql 如下:
select * from user where name = "ruhua";
上述 sql 中,我們希望 name 后的參數(shù) "ruhua" 是動(dòng)態(tài)可變的,即不同的時(shí)刻根據(jù)不同的姓名來查詢用戶。在 sqlMap 的 xml 文件中使用如下的 sql 可以實(shí)現(xiàn)動(dòng)態(tài)傳遞參數(shù) name:
select * from user where name = #{name};
或者
select * from user where name = "${name}";
對(duì)于上述這種查詢情況來說,使用 #{ } 和 ${ } 的結(jié)果是相同的,但是在某些情況下,我們只能使用二者其一。
"#" 與 "$" 區(qū)別動(dòng)態(tài) SQL 是 mybatis 的強(qiáng)大特性之一,也是它優(yōu)于其他 ORM 框架的一個(gè)重要原因。mybatis 在對(duì) sql 語句進(jìn)行預(yù)編譯之前,會(huì)對(duì) sql 進(jìn)行動(dòng)態(tài)解析,解析為一個(gè) BoundSql 對(duì)象,也是在此處對(duì)動(dòng)態(tài) SQL 進(jìn)行處理的。
在動(dòng)態(tài) SQL 解析階段, #{ } 和 ${ } 會(huì)有不同的表現(xiàn):
#{ } 解析為一個(gè) JDBC 預(yù)編譯語句(prepared statement)的參數(shù)標(biāo)記符。
例如,sqlMap 中如下的 sql 語句
select * from user where name = #{name};
解析為:
select * from user where name = ?;
一個(gè) #{ } 被解析為一個(gè)參數(shù)占位符 ? 。
而,
${ } 僅僅為一個(gè)純碎的 string 替換,在動(dòng)態(tài) SQL 解析階段將會(huì)進(jìn)行變量替換
例如,sqlMap 中如下的 sql
select * from user where name = "${name}";
當(dāng)我們傳遞的參數(shù)為 "ruhua" 時(shí),上述 sql 的解析為:
select * from user where name = "ruhua";
預(yù)編譯之前的 SQL 語句已經(jīng)不包含變量 name 了。
綜上所得, ${ } 的變量的替換階段是在動(dòng)態(tài) SQL 解析階段,而 #{ }的變量的替換是在 DBMS 中。
用法 tips1、能使用 #{ } 的地方就用 #{ }
首先這是為了性能考慮的,相同的預(yù)編譯 sql 可以重復(fù)利用。
其次,${ } 在預(yù)編譯之前已經(jīng)被變量替換了,這會(huì)存在 sql 注入問題。例如,如下的 sql,
select * from ${tableName} where name = #{name}
假如,我們的參數(shù) tableName 為 user; delete user; --,那么 SQL 動(dòng)態(tài)解析階段之后,預(yù)編譯之前的 sql 將變?yōu)?/p>
select * from user; delete user; -- where name = ?;
-- 之后的語句將作為注釋,不起作用,因此本來的一條查詢語句偷偷的包含了一個(gè)刪除表數(shù)據(jù)的 SQL!
2、表名作為變量時(shí),必須使用 ${ }
這是因?yàn)椋砻亲址褂?sql 占位符替換字符串時(shí)會(huì)帶上單引號(hào) "",這會(huì)導(dǎo)致 sql 語法錯(cuò)誤,例如:
select * from #{tableName} where name = #{name};
預(yù)編譯之后的sql 變?yōu)椋?/p>
select * from ? where name = ?;
假設(shè)我們傳入的參數(shù)為 tableName = "user" , name = "ruhua",那么在占位符進(jìn)行變量替換后,sql 語句變?yōu)?/p>
select * from "user" where name="ruhua";
上述 sql 語句是存在語法錯(cuò)誤的,表名不能加單引號(hào) ""(注意,反引號(hào) ``是可以的)。
sql預(yù)編譯 定義為什么需要預(yù)編譯sql 預(yù)編譯指的是數(shù)據(jù)庫驅(qū)動(dòng)在發(fā)送 sql 語句和參數(shù)給 DBMS 之前對(duì) sql 語句進(jìn)行編譯,這樣 DBMS 執(zhí)行 sql 時(shí),就不需要重新編譯。
JDBC 中使用對(duì)象 PreparedStatement 來抽象預(yù)編譯語句,使用預(yù)編譯
預(yù)編譯階段可以優(yōu)化 sql 的執(zhí)行。
預(yù)編譯之后的 sql 多數(shù)情況下可以直接執(zhí)行,DBMS 不需要再次編譯,越復(fù)雜的sql,編譯的復(fù)雜度將越大,預(yù)編譯階段可以合并多次操作為一個(gè)操作。
預(yù)編譯語句對(duì)象可以重復(fù)利用。
把一個(gè) sql 預(yù)編譯后產(chǎn)生的 PreparedStatement 對(duì)象緩存下來,下次對(duì)于同一個(gè)sql,可以直接使用這個(gè)緩存的 PreparedState 對(duì)象。
mybatis 默認(rèn)情況下,將對(duì)所有的 sql 進(jìn)行預(yù)編譯。
mysql預(yù)編譯源碼解析mysql 的預(yù)編譯源碼在 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)對(duì)sql進(jìn)行語法轉(zhuǎn)換 String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql): sql; // 判斷是否可以進(jìn)行服務(wù)器端預(yù)編譯 if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) { canServerPrepare = canHandleAsServerPreparedStatement(nativeSql); } // 如果可以進(jìn)行服務(wù)器端預(yù)編譯 if (this.useServerPreparedStmts && canServerPrepare) { // 是否緩存了PreparedStatement對(duì)象 if (this.getCachePreparedStatements()) { synchronized (this.serverSideStatementCache) { // 從緩存中獲取緩存的PreparedStatement對(duì)象 pStmt = (com.mysql.jdbc.ServerPreparedStatement)this.serverSideStatementCache.remove(sql); if (pStmt != null) { // 緩存中存在對(duì)象時(shí)對(duì)原 sqlStatement 進(jìn)行參數(shù)清空等 ((com.mysql.jdbc.ServerPreparedStatement)pStmt).setClosed(false); pStmt.clearParameters(); } if (pStmt == null) { try { // 如果緩存中不存在,則調(diào)用服務(wù)器端(數(shù)據(jù)庫)進(jìn)行預(yù)編譯 pStmt = ServerPreparedStatement.getInstance(getLoadBalanceSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); if (sql.length() < getPreparedStatementCacheSqlLimit()) { ((com.mysql.jdbc.ServerPreparedStatement)pStmt).isCached = true; } // 設(shè)置返回類型以及并發(fā)類型 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 { // 未啟用緩存時(shí),直接調(diào)用服務(wù)器端進(jìn)行預(yù)編譯 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 { // 不支持服務(wù)器端預(yù)編譯時(shí)調(diào)用客戶端預(yù)編譯(不需要數(shù)據(jù)庫 connection ) pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); } return pStmt; }
流程圖如下所示:
mybatis之sql動(dòng)態(tài)解析以及預(yù)編譯源碼 mybatis sql 動(dòng)態(tài)解析mybatis 在調(diào)用 connection 進(jìn)行 sql 預(yù)編譯之前,會(huì)對(duì)sql語句進(jìn)行動(dòng)態(tài)解析,動(dòng)態(tài)解析主要包含如下的功能:
占位符的處理
動(dòng)態(tài)sql的處理
參數(shù)類型校驗(yàn)
mybatis強(qiáng)大的動(dòng)態(tài)SQL功能的具體實(shí)現(xiàn)就在此。動(dòng)態(tài)解析涉及的東西太多,以后再討論。
總結(jié)本文主要深入探究了 mybatis 對(duì) #{ } 和 ${ }的不同處理方式,并了解了 sql 預(yù)編譯。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/65626.html
摘要:解決方法使用數(shù)據(jù)庫連接池管理數(shù)據(jù)庫連接。向中設(shè)置參數(shù),對(duì)占位符號(hào)位置和設(shè)置參數(shù)值,硬編碼在代碼中,同樣也不利于系統(tǒng)的維護(hù)。從中遍歷結(jié)果集數(shù)據(jù)時(shí),存在硬編碼,將獲取表的字段進(jìn)行硬編碼,不利于系統(tǒng)維護(hù)。 Mybatis Mybatis 和 SpringMVC 通過訂單商品案例驅(qū)動(dòng) 官方中文地址:http://www.mybatis.org/mybati... 官方托管地址:https://...
摘要:并且如果使用那么必須要指明值使用簡單的數(shù)據(jù)類型不好使使用接口代理模式的注解也可以 屬于MyBatis的核心之一,這里面的坑比較多,大家多多看看吧 一 模糊查詢的三種方式介紹 我會(huì)使用resultMap處理結(jié)果集數(shù)據(jù) 1.死數(shù)據(jù)的模糊查詢 映射文件 SELECT * FROM...
摘要:原因就是傳入的和原有的單引號(hào),正好組成了,而后面恒等于,所以等于對(duì)這個(gè)庫執(zhí)行了查所有的操作。類比的執(zhí)行流程和原有的我們使用的方法就是。可以理解為就是用來解析定制的符號(hào)的語句。后續(xù)的流程,就和正常的流程一致了。 前言 在JDBC中,主要使用的是兩種語句,一種是支持參數(shù)化和預(yù)編譯的PrepareStatement,能夠支持原生的Sql,也支持設(shè)置占位符的方式,參數(shù)化輸入的參數(shù),防止Sql注...
摘要:我們對(duì)語句做適當(dāng)改變,就完成了注入,因?yàn)槠胀ǖ牟粫?huì)對(duì)做任何處理,該例中單引號(hào)后的生效,拉出了所有數(shù)據(jù)。查詢資料后,發(fā)現(xiàn)還要開啟一個(gè)參數(shù),讓端緩存,緩存是級(jí)別的。結(jié)論是個(gè)好東西。 背景 最近因?yàn)楣ぷ髡{(diào)整的關(guān)系,都在和數(shù)據(jù)庫打交道,增加了許多和JDBC親密接觸的機(jī)會(huì),其實(shí)我們用的是Mybatis啦。知其然,知其所以然,是我們工程師童鞋們應(yīng)該追求的事情,能夠幫助你更好的理解這個(gè)技術(shù),面對(duì)問題...
閱讀 2983·2021-11-23 09:51
閱讀 2997·2021-11-02 14:46
閱讀 864·2021-11-02 14:45
閱讀 2739·2021-09-23 11:57
閱讀 2493·2021-09-23 11:22
閱讀 1924·2019-08-29 16:29
閱讀 740·2019-08-29 16:16
閱讀 937·2019-08-26 13:44