您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“mybatis中#{}和${}有哪些區別”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“mybatis中#{}和${}有哪些區別”這篇文章吧。
數據庫數據:
dao接口:
List<User> findByUsername(String username); List<User> findByUsername2(String username);
Mapper.xml:
<!-- 使用#{} --> <select id="findByUsername" parameterType="java.lang.String" resultType="com.lscl.entity.User"> select * from user where username like #{username} </select> <!-- 使用${},注意${}中的值必須要填value --> <select id="findByUsername2" parameterType="java.lang.String" resultType="com.lscl.entity.User"> select * from user where username like '%${value}%' </select>
執行測試代碼:
@Test public void findByUsername() throws Exception { InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); // true:自動提交 SqlSession session = factory.openSession(true); UserDao userDao = session.getMapper(UserDao.class); List<User> userList = userDao.findByUsername("%小%"); List<User> userList2 = userDao.findByUsername2("小"); System.out.println("userList: "); for (User user : userList) { System.out.println(user); } System.out.println("userList2: "); for (User user : userList2) { System.out.println(user); } session.close(); in.close(); }
查看執行結果:
發現都能夠查詢出來
${}會產生SQL注入,#{}不會產生SQL注入問題
我們做一個測試:
List<User> userList2 = userDao.findByUsername2(" aaa' or 1=1 -- "); System.out.println("userList2: "); for (User user : userList2) { System.out.println(user); }
查詢生成的SQL語句:
我們傳遞的參數是aaa' or 1=1 --
,導致查詢出來了全部的數據。
大家可以想象一下,如果我是要根據id刪除呢?
delete from user where id='${value}'
如果我傳遞的是:1' or 1=1; --
,結果會是什么樣,我想大家應該已經知道了。
我這里id是Integer類型,不好測試,就不帶大家測試了,大家有興趣可以自己私下測試。
如果上面使用的是#{}就不會出現SQL注入的問題了
#{}
匹配的是一個占位符,相當于JDBC中的一個?
,會對一些敏感的字符進行過濾,編譯過后會對傳遞的值加上雙引號,因此可以防止SQL注入問題。
${}
匹配的是真實傳遞的值,傳遞過后,會與sql語句進行字符串拼接。${}
會與其他sql進行字符串拼接,不能預防sql注入問題。
查看#{}
和${}
生成的SQL語句:
String abc=“123”;
#{abc}="123"
${value}=123;
網上關于這類問題非常多,總結出來就兩個原因:
1)#{}
底層采用的是PreparedStatement,會預編譯,因此不會產生SQL注入問題;
其實預編譯是MySQL自己本身的功能,和PreparedStatement沒關系;而且預編譯也不是咱們理解的那個預編譯,再者PreparedStatement底層默認根本沒有用到預編譯(要我們手動開啟)!詳細往下看
2)#{}
不會產生字符串拼接,${}
會產生字符串拼接,因此${}
會出現SQL注入問題;
這兩個答案都經不起深究,最終答案也只是停留在表面,也沒人知道具體是為什么。
我們翻開MySQL驅動的源碼一看究竟;
打開PreparedStatement
類的setString()
方法(MyBatis在#{}
傳遞參數時,是借助setString()
方法來完成,${}
則不是):
setString()方法全部源碼:
public void setString(int parameterIndex, String x) throws SQLException { synchronized(this.checkClosed().getConnectionMutex()) { if (x == null) { this.setNull(parameterIndex, 1); } else { this.checkClosed(); int stringLength = x.length(); StringBuilder buf; if (this.connection.isNoBackslashEscapesSet()) { boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength); Object parameterAsBytes; byte[] parameterAsBytes; if (!needsHexEscape) { parameterAsBytes = null; buf = new StringBuilder(x.length() + 2); buf.append('\''); buf.append(x); buf.append('\''); if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(buf.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(buf.toString()); } this.setInternal(parameterIndex, parameterAsBytes); } else { parameterAsBytes = null; if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(x); } this.setBytes(parameterIndex, parameterAsBytes); } return; } String parameterAsString = x; boolean needsQuoted = true; if (this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) { needsQuoted = false; buf = new StringBuilder((int)((double)x.length() * 1.1D)); buf.append('\''); for(int i = 0; i < stringLength; ++i) { //遍歷字符串,獲取到每個字符 char c = x.charAt(i); switch(c) { case '\u0000': buf.append('\\'); buf.append('0'); break; case '\n': buf.append('\\'); buf.append('n'); break; case '\r': buf.append('\\'); buf.append('r'); break; case '\u001a': buf.append('\\'); buf.append('Z'); break; case '"': if (this.usingAnsiMode) { buf.append('\\'); } buf.append('"'); break; case '\'': buf.append('\\'); buf.append('\''); break; case '\\': buf.append('\\'); buf.append('\\'); break; case '¥': case '?': if (this.charsetEncoder != null) { CharBuffer cbuf = CharBuffer.allocate(1); ByteBuffer bbuf = ByteBuffer.allocate(1); cbuf.put(c); cbuf.position(0); this.charsetEncoder.encode(cbuf, bbuf, true); if (bbuf.get(0) == 92) { buf.append('\\'); } } buf.append(c); break; default: buf.append(c); } } buf.append('\''); parameterAsString = buf.toString(); } buf = null; byte[] parameterAsBytes; if (!this.isLoadDataQuery) { if (needsQuoted) { parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } } else { parameterAsBytes = StringUtils.getBytes(parameterAsString); } this.setInternal(parameterIndex, parameterAsBytes); this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 12; } } }
我們執行#{}的查詢語句,打斷點觀察:
最終傳遞的參數如下:
最終傳遞的參數為:'aaa\' or 1=1 --
咱們在數據庫中執行如下SQL語句(肯定是查詢不到數據的):
select * from user where username like 'aaa\' or 1=1 -- '
如果把PreparedStatement
加的那根"/"
去掉呢?我們執行SQL試試:
select * from user where username like 'aaa' or 1=1 -- '
我們也可以通過MySQL的日志來觀察#{}和${}產生的SQL語句來分析問題:
1)開啟MySQL日志:
在MySQL配置文件中的[mysqld]下增加如下配置:
# 是否開啟mysql日志 0:關閉(默認值) 1:開啟 general-log=1 # mysql 日志的存放位置 general_log_file="D:/query.log"
2)重啟MySQL服務(要以管理員身份運行):
net stop mysqlnet start mysql
使用mybatis分別執行如下兩條SQL語句:
查看MySQL日志:
既然#{}
比${}
好那么多,那為什么還要有${}
這個東西存在呢?干脆都用#{}
不就萬事大吉嗎?
其實不是的,${}
也有用武之地,我們都知道${}
會產生字符串拼接,來生成一個新的字符串
例如現在要進行模糊查詢,查詢user表中姓張的所有員工的信息
sql語句為:select * from user where name like '張%'
此時如果傳入的參數是 “張”
如果使用${}
:select * from user where name like '${value}%'
生成的sql語句:select * from user where name like '張%'
如果使用#{}
:select * from user where name like #{value}"%"
生成的sql語句:select * from user where name like '張'"%"
如果傳入的參數是 “張%”
使用#{}:select * from user where name like #{value}
生成的sql語句:select * from user where name like '張%'
使用${}
:select * from user where name like '${value}'
生成的sql語句:select * from user where name like '張%'
通過上面的SQL語句我們能夠發現
#{}
是會加上雙引號,而${}
匹配的是真實的值。
還有一點就是如果使用${}
的話,里面必須要填value,即:${value}
,#{}
則隨意
場景舉例:
代碼測試:
執行之后,發現執行成功
我們可以切換一下,把${}改成#{},會出現SQL語法錯誤的異常
MyBatis的#{}
之所以能夠預防SQL注入是因為底層使用了PreparedStatement
類的setString()
方法來設置參數,此方法會獲取傳遞進來的參數的每個字符,然后進行循環對比,如果發現有敏感字符(如:單引號、雙引號等),則會在前面加上一個'/'
代表轉義此符號,讓其變為一個普通的字符串,不參與SQL語句的生成,達到防止SQL注入的效果。
其次${}
本身設計的初衷就是為了參與SQL語句的語法生成,自然而然會導致SQL注入的問題(不會考慮字符過濾問題)。
1)#{}
在使用時,會根據傳遞進來的值來選擇是否加上雙引號,因此我們傳遞參數的時候一般都是直接傳遞,不用加雙引號,${}
則不會,我們需要手動加
2)在傳遞一個參數時,我們說了#{}
中可以寫任意的值,${}
則必須使用value;即:${value}
3)#{}
針對SQL注入進行了字符過濾,${}
則只是作為普通傳值,并沒有考慮到這些問題
4)#{}
的應用場景是為給SQL語句的where字句傳遞條件值,${}
的應用場景是為了傳遞一些需要參與SQL語句語法生成的值。
以上是“mybatis中#{}和${}有哪些區別”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。