MyBatis #{}和${} |與數(shù)據(jù)庫連接池使用詳解
前言
前面我們學(xué)習(xí)了 MyBatis 的基礎(chǔ)操作,其中參數(shù)的傳遞使用到了#{}
,而參數(shù)的傳遞不僅僅只有這一個(gè)可以使用,還有 ${}
可以使用,那么今天這篇文章我將為大家說說關(guān)于 #{} 和 ${},這個(gè)是 MyBatis 在面試中最常問的面試題,以及數(shù)據(jù)庫連接池相關(guān)的知識(shí)。
#{}和${}的使用
首先我們還是需要在 YAML 配置文件中配置數(shù)據(jù)庫的相關(guān)配置。
# 配置數(shù)據(jù)庫相關(guān)信息 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false username: root password: lmh041105666 # 打印mybatis日志 mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
UserInfoMapper文件中的代碼
@Mapper public interface UserInfoMapper { //這里是使用#{}作為參數(shù)的傳遞 @Select("select * from userinfo where id=#{id}") public UserInfo selectById(Integer id); }
單元測試中代碼
@Slf4j @SpringBootTest class UserInfoMapperTest { @Autowired private UserInfoMapper userInfoMapper; @Test void selectById() { log.info(userInfoMapper.selectById(3).toString()); } }
然后我們講這里的 #{}
改為 ${}
看看有什么效果。
@Select("select * from userinfo where id=${id}") public UserInfo selectById(Integer id);
這里可以看到 #{}
和 ${}
的一個(gè)區(qū)別:使用 #{}
進(jìn)行參數(shù)的傳遞的時(shí)候,不是直接將輸入的參數(shù)拼接到后面,而是先使用 ?
進(jìn)行占位,這種 SQL 我們稱之為“預(yù)編譯SQL”。而使用 ${}
進(jìn)行參數(shù)傳遞的時(shí)候,會(huì)將輸入的參數(shù)直接拼接到 SQL 的后面,這種 SQL 我們稱之為“即時(shí)SQL”。這兩個(gè) SQL 的區(qū)別我們后面再說。
上面我們傳入的參數(shù)都是 Integer
類型的參數(shù),那么如果我們傳入的參數(shù)類型是 String
的話,會(huì)發(fā)生什么呢?
@Select("select * from userinfo where username=#{username}") public UserInfo selectByName(String username);
@Test void selectByName() { log.info(userInfoMapper.selectByName("小美").toString()); }
可以看到,使用 #{}
進(jìn)行參數(shù)的傳遞的時(shí)候,會(huì)對(duì)傳遞的參數(shù)的類型進(jìn)行識(shí)別,我們傳入的類型是 String
類型的話,就會(huì)識(shí)別為 String
類型,我們?cè)賮砜纯词褂?${}
傳遞參數(shù),參數(shù)的類型為 String
類型的情況。
@Select("select * from userinfo where username=${username}") public UserInfo selectByName(String username);
通過觀察 MyBatis 打印出來的日志可以發(fā)現(xiàn),傳遞參數(shù)的時(shí)候,直接將我們傳遞的參數(shù)拼接到了 SQL 的末尾,而且沒有加上引號(hào),那么在 SQL 中沒有加引號(hào)的字符串會(huì)被認(rèn)為是表中的列,而我們當(dāng)前表中沒有 小美 這個(gè)列,所以就會(huì)報(bào)錯(cuò),那么我們?nèi)绾谓鉀Q這個(gè)問題呢?
因?yàn)槭褂?${}
進(jìn)行參數(shù)的傳遞的時(shí)候是直接將傳遞的參數(shù)拼接到 SQL 的對(duì)應(yīng)位置,所以我們可以手動(dòng)在 SQL 中加入引號(hào)。
@Select("select * from userinfo where username='${username}'") public UserInfo selectByName(String username);
通過上面的兩個(gè)例子,我們可以發(fā)現(xiàn):#{}
使用的是預(yù)編譯 SQL,通過 ?
占位的方式,提前對(duì) SQL 進(jìn)行編譯,然后把參數(shù)填充到 SQL 語句中,并且 #{}
會(huì)根據(jù)參數(shù)類型,自動(dòng)拼接 ‘’。而 ${}
則會(huì)直接進(jìn)行字符的替換,一起對(duì) SQL 進(jìn)行編譯,如果參數(shù)為字符串類型,需要我們手動(dòng)在 SQL 中加入引號(hào)。
#{}和${}的區(qū)別
當(dāng)別人問我們 #{}
和 ${}
的區(qū)別的時(shí)候,實(shí)際上是在問預(yù)編譯 SQL 和即時(shí) SQL 的區(qū)別。
當(dāng)一個(gè)客戶發(fā)送一條 SQL 給服務(wù)器之后,大致流程是這樣的:
- 解析語法和語義,校驗(yàn) SQL 語句是否正確
- 優(yōu)化 SQL 語句,制定執(zhí)行計(jì)劃
- 執(zhí)行并返回結(jié)果
一個(gè) SQL 如果執(zhí)行流程是這樣的話,那么這個(gè) SQL 就被成為 即時(shí)SQL。
但是呢,絕大多數(shù)情況下,某一條 SQL 語句可能會(huì)被反復(fù)調(diào)用執(zhí)行,或者每次執(zhí)行的時(shí)候只有個(gè)別的值不同(比如 selelct 的 where 子句值不同,update 的 set 子句不同,insert 的 values 值不同)。如果我們每次調(diào)用這種 SQL 都按照上面的語法解析、SQL 優(yōu)化、SQL 編譯等,那么效率就會(huì)降低了。
類似這種 SQL,我們就可以使用預(yù)編譯 SQL 的方法來提升效率。
預(yù)編譯SQL是一種常見的數(shù)據(jù)庫優(yōu)化技術(shù),其主要目的是提高數(shù)據(jù)庫查詢的效率和安全性。在預(yù)編譯SQL中,SQL語句和參數(shù)被分開處理。首先,SQL語句被編譯成一個(gè)中間形式,這個(gè)中間形式通常是與數(shù)據(jù)庫管理系統(tǒng)(DBMS)的內(nèi)部表示相對(duì)應(yīng)的。然后,參數(shù)值與編譯后的SQL語句進(jìn)行動(dòng)態(tài)綁定,最終生成可以直接執(zhí)行的SQL語句。后面再傳入?yún)?shù)執(zhí)行這條語句的時(shí)候(只是輸入的參數(shù)不同),省去了解析優(yōu)化的過程,一次來提高效率。
預(yù)編譯 SQL 的優(yōu)點(diǎn)不止是可以提高效率,還可以防止 SQL 注入。
什么是 SQL 注入?
SQL注入是一種常見的網(wǎng)絡(luò)攻擊方式,發(fā)生在Web應(yīng)用程序中。它利用了Web應(yīng)用程序?qū)τ脩糨斎霐?shù)據(jù)合法性沒有進(jìn)行充分驗(yàn)證或過濾不嚴(yán)的漏洞。攻擊者可以在Web應(yīng)用程序中事先定義好的查詢語句的結(jié)尾上添加額外的SQL語句,從而實(shí)現(xiàn)在管理員不知情的情況下執(zhí)行非法操作。
當(dāng)用戶在Web頁面上輸入數(shù)據(jù)時(shí),如果這些數(shù)據(jù)未經(jīng)過適當(dāng)?shù)尿?yàn)證和處理,攻擊者可以在其中插入或“注入”惡意的SQL代碼。當(dāng)這些數(shù)據(jù)被用來構(gòu)建SQL查詢時(shí),這些惡意的SQL代碼就會(huì)被執(zhí)行。攻擊者可以執(zhí)行非授權(quán)的任意查詢,從而進(jìn)一步得到相應(yīng)的數(shù)據(jù)信息。
例如,假設(shè)一個(gè)Web應(yīng)用程序的登錄頁面允許用戶輸入用戶名和密碼。如果應(yīng)用程序沒有對(duì)用戶輸入進(jìn)行適當(dāng)?shù)尿?yàn)證,攻擊者可以在用戶名或密碼字段中輸入如’ OR ‘1’='1之類的SQL代碼。當(dāng)應(yīng)用程序?qū)⑦@些數(shù)據(jù)用于構(gòu)建SQL查詢時(shí),這些代碼會(huì)使得查詢變成SELECT * FROM users WHERE username='' OR '1'='1' AND password='password'
,從而繞過正常的驗(yàn)證,獲得所有用戶的訪問權(quán)限。
因?yàn)?${}
傳遞參數(shù)的方式也就是即時(shí) SQL 不會(huì)對(duì)用戶的輸入進(jìn)行充分檢查,而 SQL 又是直接拼接而成的,在用戶輸入?yún)?shù)的時(shí)候,在參數(shù)中添加一些 SQL 關(guān)鍵字,達(dá)到改變 SQL 運(yùn)行結(jié)果的目的,從而實(shí)現(xiàn)惡意攻擊。
給大家舉個(gè)例子:
@Select("select * from userinfo where username='${username}'") public List<UserInfo> selectByName(String username);
@Test void selectByName() { log.info(userInfoMapper.selectByName("'or 1='1").toString()); }
通過這樣傳遞參數(shù)的話,那么將我們傳遞的參數(shù)直接拼接在 SQL 中,就會(huì)出現(xiàn)這樣的 SQL:select * from userinfo where username='' or 1='1'
,在這里 1 會(huì)被解釋為字符串 ‘1’,那么這時(shí) ‘1’='1’為真,那么這時(shí)就會(huì)將數(shù)據(jù)庫中該表中的所有信息都給顯示出來,這樣就會(huì)導(dǎo)致數(shù)據(jù)的泄露。
通過觀察代碼的執(zhí)行結(jié)果,我們也可以發(fā)現(xiàn),表中的所有信息都被顯示出來了。
而如果我們使用 #{}
來實(shí)現(xiàn) SQL 的注入,看看可以達(dá)到效果嗎?
@Select("select * from userinfo where username=#{username}") public List<UserInfo> selectByName(String username);
@Test void selectByName() { log.info(userInfoMapper.selectByName("'or 1='1").toString()); }
通過預(yù)編譯 SQL 占位的方式,我們輸入的參數(shù)會(huì)被認(rèn)為是表中的 username 列中的一個(gè)名稱,而不會(huì)被解析成為上面的 SQL,表中沒有一個(gè)叫“or 1='1"的username,所以查詢結(jié)果為0,也就是說,通過預(yù)編譯 SQL 的方式可以有效的解決 SQL 注入的情況。
所以為了防止 SQL 注入,我們應(yīng)盡量選擇 #{}
來使用,那么是否就代表著 ${}
就不被使用了了呢?不是這樣的,既然出現(xiàn)了,那么它就有存在的意義。
排序功能
有一些情況下,只能使用 ${}
,而不能使用 #{}
。其中很典型的情況就是涉及到排序的時(shí)候,根據(jù)我們輸入的參數(shù)決定是升序排列還是降序排列。
@Select("select * from userinfo order by id #{sort}") public List<UserInfo> selectBySort(String sort);
@Test void selectBySort() { log.info(userInfoMapper.selectBySort("desc").toString()); }
可以看到,本來我們進(jìn)行排序查詢的話,排序規(guī)則是不需要加上引號(hào)的,而在預(yù)編譯占位的情況下,因?yàn)閭鬟f來的參數(shù)是字符串類型,所以在拼接 SQL 的時(shí)候,該參數(shù)就被加上了引號(hào),所以就導(dǎo)致出現(xiàn)錯(cuò)誤,在這種情況下我們就得使用 ${}
來進(jìn)行 SQL 的直接拼接。
@Select("select * from userinfo order by id ${sort}") public List<UserInfo> selectBySort(String sort);
所以當(dāng)我們進(jìn)行傳遞排序規(guī)則的時(shí)候,需要使用到 ${}
。
模糊查詢
MySQL 中的模糊查詢使用 like 關(guān)鍵字,%
用來匹配任意任意數(shù)量的字符(包含0個(gè)),_
用來匹配單個(gè)字符,那么如果我們想用參數(shù)傳遞的方式來指定模糊查詢的規(guī)則的話,就不能使用 #{}
的。
@Select("select * from userinfo where username like '%#{like}%'") public List<UserInfo> selectByLike(String like);
@Test void selectByLike() { log.info(userInfoMapper.selectByLike("小").toString()); }
同樣也是,使用 #{}
進(jìn)行參數(shù)的傳遞的話,因?yàn)閭鬟f過來的參數(shù)的類型是字符串類型,所以在進(jìn)行 SQL 拼接的時(shí)候就會(huì)自動(dòng)加上引號(hào)也就是 ‘%‘小’%’,這樣的形成的 SQL,就是有問題的 SQL。
我們使用 ${}
的話就不會(huì)出現(xiàn)這種問題:
@Select("select * from userinfo where username like '%${like}%'") public List<UserInfo> selectByLike(String like);
但是在這種情況下還是會(huì)發(fā)生 SQL 注入的情況,所以我們不推薦使用 ${}
,而是使用 SQL 內(nèi)置的函數(shù) concat()
。
@Select("select * from userinfo where username like concat('%',#{like},'%')") public List<UserInfo> selectByLike(String like);
總結(jié) #{} 和 ${} 的區(qū)別
- 在使用 #{} 進(jìn)行參數(shù)的傳遞的時(shí)候,會(huì)根據(jù)參數(shù)的類型,在拼接 SQL 的時(shí)候做出調(diào)整,而 ${} 則是直接進(jìn)行 SQL 的拼接。
- 在排序,傳遞排序規(guī)則的時(shí)候不能使用 #{},因?yàn)?SQL 編譯的時(shí)候會(huì)根據(jù)傳遞的字符串類型,在 SQL 中加入引號(hào),所以這種情況下只能使用 ${}。
- 進(jìn)行模糊查詢的時(shí)候,雖然直接使用 #{} 會(huì)出現(xiàn)問題,使用 ${} 不會(huì)出現(xiàn)問題,但是我們不使用 ${},因?yàn)闀?huì)發(fā)生 SQL 注入,而是使用 SQL 內(nèi)置函數(shù) concat()
- 最重要的就是預(yù)編譯 SQL 和即時(shí) SQL 的區(qū)別:
- 預(yù)編譯 SQL 性能更高
- 預(yù)編譯 SQL 不存在 SQL 注入的問題
所以在實(shí)際開發(fā)的過程中,能使用 #{} 的情況就盡量使用 #{},如果要使用 ${},就一定要考慮到 SQL 注入的問題。
數(shù)據(jù)庫連接池
數(shù)據(jù)庫連接池是程序在啟動(dòng)時(shí)創(chuàng)建足夠多的數(shù)據(jù)庫連接,并將這些連接放入一個(gè)池子中。這個(gè)池子中的連接可以被程序動(dòng)態(tài)地申請(qǐng)、使用和釋放。其作用是分配、管理和釋放數(shù)據(jù)庫連接,使得應(yīng)用程序可以重復(fù)使用現(xiàn)有的同一個(gè)數(shù)據(jù)庫連接,而不是每次都重新建立連接,從而避免了資源的消耗,提高了程序執(zhí)行的效率。
當(dāng)沒有數(shù)據(jù)庫連接池的話,當(dāng)客戶端向服務(wù)器發(fā)送 SQL 請(qǐng)求的話,會(huì)先創(chuàng)建一個(gè) Connection 連接,然后通過這個(gè)連接訪問到服務(wù)器,當(dāng)請(qǐng)求完成之后,這個(gè)連接又會(huì)被銷毀,下次再請(qǐng)求的時(shí)候,還是需要先創(chuàng)立連接,然后再釋放連接,這樣是比較消耗資源的。
當(dāng)使用數(shù)據(jù)庫連接池的情況,當(dāng)程序啟動(dòng)的時(shí)候,就會(huì)在數(shù)據(jù)庫連接池中創(chuàng)建一定數(shù)量的 Connection 對(duì)象,當(dāng)客戶端需要訪問服務(wù)器的時(shí)候,可以直接使用數(shù)據(jù)庫連接池中的 Connection 對(duì)象,并且當(dāng)使用完成之后,會(huì)將 Connection 對(duì)象歸還給數(shù)據(jù)庫連接池,這樣就極大的節(jié)省了 CConnection 對(duì)象的創(chuàng)建和銷毀所需要的時(shí)間。
總的來說,使用數(shù)據(jù)庫連接池具有以下優(yōu)點(diǎn):
- 減少網(wǎng)絡(luò)開銷
- 資源重用
- 提升系統(tǒng)的性能
常見的數(shù)據(jù)庫連接池有:C3P0、DBCP、Druid、Hikari,當(dāng)前比較流行的是 Hikari和Druid。
而在我們的 SpringBoot 中,默認(rèn)使用的數(shù)據(jù)庫連接池是 Hikari。
當(dāng)我們啟動(dòng) MyBatis 的 SpringBoot 項(xiàng)目的時(shí)候,就會(huì)啟動(dòng) Hikari 數(shù)據(jù)庫連接池,建立 Connection 對(duì)象。
如果我們想要更換 SpringBoot 使用的數(shù)據(jù)庫連接池的話,只需要將要更換到的數(shù)據(jù)庫連接池的依賴添加到 pom.xml 文件即可。
這里假設(shè)我們更換 SpringBoot 的數(shù)據(jù)庫連接池為 Druid:
將下面這個(gè)坐標(biāo)添加到 pom.xml 文件中。并且刷新 pom.xml 文件,將新添加的依賴下載下來。
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency>
到此這篇關(guān)于MyBatis #{}和${} | 數(shù)據(jù)庫連接池的文章就介紹到這了,更多相關(guān)MyBatis #{}和${}內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MyBatis中#{}?和?${}?的區(qū)別和動(dòng)態(tài)?SQL詳解
- mybatis中${}和#{}的區(qū)別以及底層原理分析
- MyBatis中使用#{}和${}占位符傳遞參數(shù)的各種報(bào)錯(cuò)信息處理方案
- Mybatis關(guān)于動(dòng)態(tài)排序 #{} ${}問題
- mybatis中#{}和${}的區(qū)別詳解
- MyBatis中#{}和${}有哪些區(qū)別
- mybatis中${}和#{}取值的區(qū)別分析
- MyBatis中#{}占位符與${}拼接符的用法說明
- 詳解Mybatis中的 ${} 和 #{}區(qū)別與用法
- Mybatis之#{}與${}的區(qū)別使用詳解
- Mybatis中#{}與${}的區(qū)別詳解
- MyBatis中 #{} 和 ${} 的區(qū)別小結(jié)
相關(guān)文章
spring的xml文件打開沒有namespace等操作選項(xiàng)的解決方案
這篇文章主要介紹了spring的xml文件打開沒有namespace等操作選項(xiàng)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Java?Selenium實(shí)現(xiàn)修改打開頁面窗口大小
Selenium是一個(gè)強(qiáng)大的自動(dòng)化測試工具,支持多種編程語言和瀏覽器,本文將詳細(xì)介紹如何使用Java?Selenium來修改打開頁面窗口的大小,需要的可以參考下2025-01-01Java基于Base64實(shí)現(xiàn)編碼解碼圖片文件
這篇文章主要介紹了Java基于Base64實(shí)現(xiàn)編碼解碼圖片文件,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03java 中InputStream,String,File之間的相互轉(zhuǎn)化對(duì)比
這篇文章主要介紹了java 中InputStream,String,File之間的相互轉(zhuǎn)化對(duì)比的相關(guān)資料,需要的朋友可以參考下2017-04-04基于java file 文件操作operate file of java的應(yīng)用
本篇文章介紹了,基于java file 文件操作operate file of java的應(yīng)用。需要的朋友參考下2013-05-05利用java和sqlserver建立簡易圖書管理系統(tǒng)的完整步驟
圖書館管理系統(tǒng)是圖書館管理工作中不可缺少的部分,它對(duì)于圖書館的管理者和使用者都非常重要,下面這篇文章主要給大家介紹了關(guān)于利用java和sqlserver建立簡易圖書管理系統(tǒng)的完整步驟,需要的朋友可以參考下2022-06-06