MyBatis中#{}?和?${}?的區(qū)別和動(dòng)態(tài)?SQL詳解
1. #{} 和 ${} 的區(qū)別
為了方便,接下來使用注解方式來演示:

#{} 的 SQL 語句中的參數(shù)是用過 ? 來起到類似于占位符的作用,而 ${} 是直接進(jìn)行參數(shù)替換,這種直接替換的即時(shí) SQL 就可能會出現(xiàn)一個(gè)問題

當(dāng)傳入一個(gè)字符串時(shí),就會發(fā)現(xiàn) SQL 語句出錯(cuò)了:


這里的 zhangsan并不是作為一個(gè)字符串使用的,應(yīng)該是加上引號的

加上之后就可以正常查詢了
這就可能會出現(xiàn) SQL 注入的問題
來看一下 SQL 注入的例子,假如傳入的參數(shù)是' or 1='1
@Select("select username, `password`, age, gender, phone from user_info where username= '${name}' ")
List<UserInfo> queryByName(String name);按道理說是沒有這個(gè)用戶的,但是卻把所有用戶的信息都查出來了

如果在某些登錄的界面輸入 SQL 注入代碼' or 1='1就可能登錄成功
使用 #{} 就沒有這個(gè)問題
除了以上的區(qū)別外,二者還有性能方面的區(qū)別
在上面提到過,#{} 是預(yù)編譯 SQL,${} 是即時(shí) SQL ,預(yù)編譯SQL編譯一次之后會將編譯后的 SQL 語句緩存起來,后面再執(zhí)行這條語句時(shí),不會再次編譯,省去了解析優(yōu)化等過程,以此來提高效率,所以當(dāng)需要頻繁地使用 SQL 語句時(shí),預(yù)編譯的性能優(yōu)化就體現(xiàn)出來了,而對于即時(shí) SQL ,如果只是在啟動(dòng)時(shí)或者很少變化的場景下使用${}來配置一些數(shù)據(jù)庫對象名稱等,它可以避免預(yù)編譯的過程,執(zhí)行起來相對直接

2. 排序
在上面看來,#{} 無論是在安全性還是效率上,都占據(jù)了優(yōu)勢,那么都是用 #{}可以嗎?
來看使用 #{}來實(shí)現(xiàn)排序功能:
@Select("select * from user_info order by id #{order}")
List<UserInfo> selectUserByOrder(String order);這里把排序的方式作為參數(shù),給用戶選擇是升序還是降序排序,測試方法中傳入一個(gè)字符串表示降序
@Test
void selectUserByOrder() {
userInfoMapper.selectUserByOrder("desc");
}
然后就會發(fā)現(xiàn)報(bào)錯(cuò)了,可以看到 "desc" 確實(shí)是當(dāng)做字符串傳進(jìn)去了,#{} 的方式會把字符串類型加上單引號,然后 SQL 語句就會變成這樣:
select * from user_info order by id 'order'
這樣肯定是不對的,那么這個(gè)時(shí)候就需要用到 ${} 了,直接進(jìn)行參數(shù)替換,但是使用 ${} 肯定就需要考慮 SQL 注入的問題,由于排序方式只有 asc 和 desc 兩種方式,可以采用枚舉類來進(jìn)行校驗(yàn),也可以通過判斷條件來實(shí)現(xiàn)校驗(yàn)

3. 模糊查詢
通過模糊查詢來查找名字中含有“zhang”的信息
@Select("select * from user_info where username like '%#{name}%'")
List<UserInfo> selectUserByLike(String name);@Test
void selectUserByLike() {
System.out.println(userInfoMapper.selectUserByLike("zhang"));
}
然后發(fā)現(xiàn)又報(bào)錯(cuò)了,因?yàn)槭褂玫氖?#{} ,所以就會替換為 '%'zhang'%',這樣是肯定不能運(yùn)行的,所以還是需要使用 ${} 進(jìn)行直接替換,但是這時(shí)怎么去解決 SQL 注入的問題呢,這樣就不能簡單的通過枚舉或者判斷來約束傳入的參數(shù)了,這時(shí)就可以通過使用拼接的方式
通過 CONCAT 函數(shù)來對 SQL 語句進(jìn)行拼接,這樣就可以使用 #{},
@Select("select * from user_info where username like CONCAT('%',#{name},'%')")
List<UserInfo> selectUserByLike(String name);4. 數(shù)據(jù)庫連接池
在傳統(tǒng)的數(shù)據(jù)庫訪問模式中,每當(dāng)應(yīng)用程序需要與數(shù)據(jù)庫進(jìn)行交互時(shí),它會創(chuàng)建一個(gè)新的數(shù)據(jù)庫連接,使用完畢后關(guān)閉連接,這樣頻繁地創(chuàng)建和銷毀數(shù)據(jù)庫連接會消耗大量的系統(tǒng)資源
數(shù)據(jù)庫連接池的出現(xiàn)就是為了解決這些問題。它在應(yīng)用程序啟動(dòng)時(shí)預(yù)先創(chuàng)建一定數(shù)量的數(shù)據(jù)庫連接,將這些連接存儲在一個(gè) “池” 中。當(dāng)應(yīng)用程序需要訪問數(shù)據(jù)庫時(shí),從池中獲取一個(gè)可用的連接,使用完畢后將連接歸還給池,而不是直接關(guān)閉連接,從而避免了頻繁創(chuàng)建和銷毀連接所帶來的性能開銷,這一點(diǎn)和線程池是類似的

常見的數(shù)據(jù)庫連接池有:C3P0 , DBCP , Druid , Hikari
Spring Boot 默認(rèn)使用的是 Hikari

如果想更換為 Druid 的話,導(dǎo)入相關(guān)的依賴即可
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.21</version> </dependency>
然后再啟動(dòng)程序之后就更換為了 Druid

也可以去 官方文檔 進(jìn)行查看
5. 動(dòng)態(tài) SQL
我們在填一些表單的時(shí)候應(yīng)該會見到下面這種,有的是必填項(xiàng),有的是選填項(xiàng),對于選填項(xiàng)來說,如果沒有填,肯定是需要賦一個(gè)默認(rèn)值的,比如 null,那么就需要?jiǎng)討B(tài) SQL 來實(shí)現(xiàn)這樣的功能

5.1. <if>
可以通過 if 標(biāo)簽來實(shí)現(xiàn)一下:
@Mapper
public interface UserInfoXmlMapper {
Integer insertUserByCondition(UserInfo userInfo);
}再來看 XML 中的 SQL 語句
<insert id="insertUserByCondition">
insert into user_info(username,'password',age,
<if test="gender != null">
gender
</if>
)
values (#{username},#{password},#{age},
<if test="gender != null">
#{gender}
</if>
)
</insert>if 標(biāo)簽中的參數(shù)和 java 對象中的屬性參數(shù)是對應(yīng)的

@Test
void insertUserByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("java");
userInfo.setPassword("java");
userInfo.setAge(19);
//userInfo.setGender(1);
Integer integer = userInfoXmlMapper.insertUserByCondition(userInfo);
}如果不傳入性別的話來看一下結(jié)果:

由于性別沒有傳入,所以說 SQL 語句中是只有前三個(gè)參數(shù)的,所以第三個(gè)參數(shù)那里就多了一個(gè)逗號,導(dǎo)致最終的 SQL 的語法錯(cuò)誤
那么就可以想一個(gè)辦法,如果把逗號直接加前面,是不是就可以解決了

這樣看似是可以解決的,但是如果說 username, age 都設(shè)為了非必填的,例如 username 沒有傳入?yún)?shù),但是 age 傳入了參數(shù),這樣前面就多了一個(gè)逗號,這時(shí) SQL 語句就又會出錯(cuò)了,把逗號都加到右邊,也是會出現(xiàn)問題的
這時(shí)就需要用到下面的標(biāo)簽了
5.2. <trim>
主要用于去除 SQL 語句中多余的關(guān)鍵字或者字符,同時(shí)也可以添加自定義的前綴和后綴
?prefix:用于為包含在trim標(biāo)簽內(nèi)部的 SQL 語句塊添加一個(gè)前綴
?suffix:表示整個(gè)語句塊,以 suffix 的值作為后綴.
?prefixOverrides:為trim標(biāo)簽內(nèi)的 SQL 語句塊添加一個(gè)后綴.
?suffixOverrides:表示整個(gè)語句塊要去除掉的后綴.
<insert id="insertUserByCondition">
insert into user_info
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
username ,
</if>
<if test="password!=null">
`password`,
</if>
<if test="age!=null">
age,
</if>
<if test="gender!=null">
gender
</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
#{username},
</if>
<if test="password!=null">
#{password},
</if>
<if test="age!=null">
#{age},
</if>
<if test="gender!=null">
#{gender}
</if>
</trim>
</insert>這就表示在 SQL 語句前面加上一個(gè) '(' ,后面加上 ')' ,如果最后是以逗號結(jié)尾的就把逗號刪了,以此來實(shí)現(xiàn) SQL 語句拼接的效果
5.3. <where>
來看一下條件查詢

這里的 and 和上面的逗號是一樣的性質(zhì),放在右邊或者左邊都不合適,還是可以使用 trim 標(biāo)簽來解決

但是這時(shí)其實(shí)還有一個(gè)問題,如果說 age 和 deleteFlag 都沒有傳入的話,最后的 SQL 語句 where 后面就沒有了,這時(shí)又會報(bào)錯(cuò)了

這種情況 trim 就解決不了了,其中一種解決方式是在 where 后面加上 1=1,那么 and 就需要加在前面了:

比較推薦的寫法就是使用 <where> 標(biāo)簽
<select id="selectUserByCondition" resultType="com.example.mybatisdemo.model.UserInfo">
select * from user_info
<where>
<if test="age!=null">
and age=#{age}
</if>
<if test="deleteFlag!=null">
and delete_flag = #{deleteFlag}
</if>
</where>
</select><where> 標(biāo)簽如果后面都沒有值的話,SQL 語句中的 where 也不會添加,并且如果只有一個(gè)值的話,前面的 and 也會被去掉,也不用 trim 標(biāo)簽了,不過去掉的是前面的 and,寫后面是不會去掉的
5.4. <set>
動(dòng)態(tài)更新操作也是,當(dāng)后面有值的時(shí)候就更新,沒有值的時(shí)候就不更新,<set> 標(biāo)簽的作用和 where 類似,也是后面有值的話就生成 set 關(guān)鍵字并且去除右邊的逗號,但是后面設(shè)置的內(nèi)容也不能全部是空,此時(shí)就算沒有生成 set 標(biāo)簽,但是前面還有一個(gè) update 關(guān)鍵字,最后的 SQL 語句還是有問題
<update id="updateByCondition">
update user_info
<set>
<if test="username!=null">
username = #{username},
</if>
<if test="password!=null">
password = #{password},
</if>
<if test="gender!=null">
gender = #{gender}
</if>
</set>
<where>
id = #{id}
</where>
</update>5.5. <foreach>
foreach 用于在 SQL 語句中遍歷集合,動(dòng)態(tài)地構(gòu)建包含多個(gè)參數(shù)的 SQL 語句,比如IN子句、批量插入語句等
- collection:綁定方法參數(shù)中的集合,如 List,Set,Map 或數(shù)組對象。
- item:遍歷時(shí)的每一個(gè)對象。
- open:語句塊開頭的字符串。
- close:語句塊結(jié)束的字符串。
- separator:每次遍歷之間間隔的字符串。
<delete id="batchDelete">
delete from user_info where id in
<foreach collection="ids" separator="," item="id" open="(" close=")">
#{id}
</foreach>
</delete>
5.6. <include>
<include>標(biāo)簽主要用于代碼復(fù)用。它可以將一個(gè) SQL 片段(通常是在<sql>標(biāo)簽中定義的)包含到另一個(gè) SQL 語句中,使得 SQL 語句的編寫更加模塊化,減少重復(fù)代碼

例如上面的重復(fù)語句就可以提取出來
<sql id="insertCol"> insert into user_info(username, password, age, gender) </sql>
然后就可以通過 include 標(biāo)簽來引用了

6. 注解方式的動(dòng)態(tài) SQL
注解方式就是把原來 XML 中的 SQL 語句部分寫到注解的 <script> 標(biāo)簽下,可以看出,由于注解中是字符串拼接的方式,這種方法是非常容易出錯(cuò)的,而且排查錯(cuò)誤也是有些困難的

主頁
到此這篇關(guān)于MyBatis中#{} 和 ${} 的區(qū)別和動(dòng)態(tài) SQL的文章就介紹到這了,更多相關(guān)MyBatis #{} 和 ${} 區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Guava Cache本地緩存在Spring Boot應(yīng)用中的實(shí)踐
Guava Cache是一個(gè)全內(nèi)存的本地緩存實(shí)現(xiàn),本文將講述如何將 Guava Cache緩存應(yīng)用到 Spring Boot應(yīng)用中。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
java對接支付寶支付項(xiàng)目的實(shí)戰(zhàn)記錄
最近公司有一個(gè)需求是接入第三方支付(微信&支付寶),我接到了支付寶支付,所以下面這篇文章主要給大家介紹了關(guān)于java對接支付寶支付項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2022-06-06
java命令調(diào)用虛擬機(jī)方法總結(jié)
在本篇文章里我們給大家整理了關(guān)于java中的java命令如何調(diào)用虛擬機(jī)的方法和具體步驟,需要的朋友們跟著操作下。2019-05-05
SpringBoot 整合Tess4J庫實(shí)現(xiàn)圖片文字識別案例詳解
Tess4J是一個(gè)基于Tesseract OCR引擎的Java接口,可以用來識別圖像中的文本,說白了,就是封裝了它的API,讓Java可以直接調(diào)用,今天給大家分享一個(gè)SpringBoot整合Tess4j庫實(shí)現(xiàn)圖片文字識別的小案例2023-10-10
springboot接入netty實(shí)現(xiàn)在線統(tǒng)計(jì)人數(shù)
本文主要介紹了springboot接入netty實(shí)現(xiàn)在線統(tǒng)計(jì)人數(shù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03
spring boot實(shí)現(xiàn)過濾器和攔截器demo
本篇文章主要介紹了spring boot實(shí)現(xiàn)過濾器和攔截器demo ,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
解決啟用 Spring-Cloud-OpenFeign 配置可刷新項(xiàng)目無法啟動(dòng)的問題
這篇文章主要介紹了解決啟用 Spring-Cloud-OpenFeign 配置可刷新項(xiàng)目無法啟動(dòng)的問題,本文重點(diǎn)給大家介紹Spring-Cloud-OpenFeign的原理及問題解決方法,需要的朋友可以參考下2021-10-10

