MyBatis中#{}?和?${}?的區(qū)別和動(dòng)態(tài)?SQL詳解
1. #{} 和 ${} 的區(qū)別
為了方便,接下來使用注解方式來演示:
#{} 的 SQL 語句中的參數(shù)是用過 ? 來起到類似于占位符的作用,而 ${} 是直接進(jìn)行參數(shù)替換,這種直接替換的即時(shí) SQL 就可能會(huì)出現(xiàn)一個(gè)問題
當(dāng)傳入一個(gè)字符串時(shí),就會(huì)發(fā)現(xiàn) SQL 語句出錯(cuò)了:
這里的 zhangsan并不是作為一個(gè)字符串使用的,應(yīng)該是加上引號(hào)的
加上之后就可以正常查詢了
這就可能會(huì)出現(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編譯一次之后會(huì)將編譯后的 SQL 語句緩存起來,后面再執(zhí)行這條語句時(shí),不會(huì)再次編譯,省去了解析優(yōu)化等過程,以此來提高效率,所以當(dāng)需要頻繁地使用 SQL 語句時(shí),預(yù)編譯的性能優(yōu)化就體現(xiàn)出來了,而對(duì)于即時(shí) SQL ,如果只是在啟動(dòng)時(shí)或者很少變化的場(chǎng)景下使用${}
來配置一些數(shù)據(jù)庫對(duì)象名稱等,它可以避免預(yù)編譯的過程,執(zhí)行起來相對(duì)直接
2. 排序
在上面看來,#{}
無論是在安全性還是效率上,都占據(jù)了優(yōu)勢(shì),那么都是用 #{}
可以嗎?
來看使用 #{}
來實(shí)現(xiàn)排序功能:
@Select("select * from user_info order by id #{order}") List<UserInfo> selectUserByOrder(String order);
這里把排序的方式作為參數(shù),給用戶選擇是升序還是降序排序,測(cè)試方法中傳入一個(gè)字符串表示降序
@Test void selectUserByOrder() { userInfoMapper.selectUserByOrder("desc"); }
然后就會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了,可以看到 "desc" 確實(shí)是當(dāng)做字符串傳進(jìn)去了,#{} 的方式會(huì)把字符串類型加上單引號(hào),然后 SQL 語句就會(huì)變成這樣:
select * from user_info order by id 'order'
這樣肯定是不對(duì)的,那么這個(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)槭褂玫氖?#{} ,所以就會(huì)替換為 '%'zhang'%',這樣是肯定不能運(yùn)行的,所以還是需要使用 ${} 進(jìn)行直接替換,但是這時(shí)怎么去解決 SQL 注入的問題呢,這樣就不能簡單的通過枚舉或者判斷來約束傳入的參數(shù)了,這時(shí)就可以通過使用拼接的方式
通過 CONCAT 函數(shù)來對(duì) 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í),它會(huì)創(chuàng)建一個(gè)新的數(shù)據(jù)庫連接,使用完畢后關(guān)閉連接,這樣頻繁地創(chuàng)建和銷毀數(shù)據(jù)庫連接會(huì)消耗大量的系統(tǒng)資源
數(shù)據(jù)庫連接池的出現(xiàn)就是為了解決這些問題。它在應(yīng)用程序啟動(dòng)時(shí)預(yù)先創(chuàng)建一定數(shù)量的數(shù)據(jù)庫連接,將這些連接存儲(chǔ)在一個(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
我們?cè)谔钜恍┍韱蔚臅r(shí)候應(yīng)該會(huì)見到下面這種,有的是必填項(xiàng),有的是選填項(xiàng),對(duì)于選填項(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 對(duì)象中的屬性參數(shù)是對(duì)應(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è)逗號(hào),導(dǎo)致最終的 SQL 的語法錯(cuò)誤
那么就可以想一個(gè)辦法,如果把逗號(hào)直接加前面,是不是就可以解決了
這樣看似是可以解決的,但是如果說 username, age 都設(shè)為了非必填的,例如 username 沒有傳入?yún)?shù),但是 age 傳入了參數(shù),這樣前面就多了一個(gè)逗號(hào),這時(shí) SQL 語句就又會(huì)出錯(cuò)了,把逗號(hào)都加到右邊,也是會(huì)出現(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è) '(' ,后面加上 ')' ,如果最后是以逗號(hào)結(jié)尾的就把逗號(hào)刪了,以此來實(shí)現(xiàn) SQL 語句拼接的效果
5.3. <where>
來看一下條件查詢
這里的 and 和上面的逗號(hào)是一樣的性質(zhì),放在右邊或者左邊都不合適,還是可以使用 trim 標(biāo)簽來解決
但是這時(shí)其實(shí)還有一個(gè)問題,如果說 age 和 deleteFlag 都沒有傳入的話,最后的 SQL 語句 where 后面就沒有了,這時(shí)又會(huì)報(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 也不會(huì)添加,并且如果只有一個(gè)值的話,前面的 and 也會(huì)被去掉,也不用 trim 標(biāo)簽了,不過去掉的是前面的 and,寫后面是不會(huì)去掉的
5.4. <set>
動(dòng)態(tài)更新操作也是,當(dāng)后面有值的時(shí)候就更新,沒有值的時(shí)候就不更新,<set> 標(biāo)簽的作用和 where 類似,也是后面有值的話就生成 set 關(guān)鍵字并且去除右邊的逗號(hào),但是后面設(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ù)組對(duì)象。
- item:遍歷時(shí)的每一個(gè)對(duì)象。
- 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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- mybatis中${}和#{}的區(qū)別以及底層原理分析
- MyBatis #{}和${} |與數(shù)據(jù)庫連接池使用詳解
- 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 Security使用Lambda DSL配置流程詳解
Spring Security 5.2 對(duì) Lambda DSL 語法的增強(qiáng),允許使用lambda配置HttpSecurity、ServerHttpSecurity,重要提醒,之前的配置方法仍然有效。lambda的添加旨在提供更大的靈活性,但是用法是可選的。讓我們看一下HttpSecurity的lambda配置與以前的配置樣式相比2023-02-02Spring 中使用反射創(chuàng)建 Bean 實(shí)例的幾種方式
文章介紹了在Spring框架中如何使用反射來創(chuàng)建Bean實(shí)例,包括使用Class.newInstance()、Constructor.newInstance()、工廠方法以及Spring的BeanUtils工具類,文章還強(qiáng)調(diào)了反射操作的注意事項(xiàng),如異常處理、性能、安全性以及類型安全,感興趣的朋友一起看看吧2025-03-03SpringMvc向request域中設(shè)置數(shù)據(jù)的方法
這篇文章主要介紹了SpringMvc向request域中設(shè)置數(shù)據(jù)的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09Spring Boot application.yml配置文件示例詳解
本文詳細(xì)介紹了SpringBootapplication.yml配置文件的使用和配置項(xiàng),通過學(xué)習(xí)本文,您應(yīng)該已經(jīng)掌握了如何使用application.yml文件來配置SpringBoot應(yīng)用程序的不同組件,如數(shù)據(jù)源、數(shù)據(jù)庫、緩存、郵件服務(wù)等,感興趣的朋友一起看看吧2025-02-02Java中漢字轉(zhuǎn)拼音pinyin4j用法實(shí)例分析
這篇文章主要介紹了Java中漢字轉(zhuǎn)拼音pinyin4j用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了pinyin4j庫的具體使用技巧,需要的朋友可以參考下2015-12-12SpringBoot實(shí)現(xiàn)抽獎(jiǎng)算法的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何通過SpringBoot實(shí)現(xiàn)抽獎(jiǎng)算法,文中的示例代碼簡潔易懂,具有一定的參考價(jià)值,感興趣的小伙伴可以了解一下2023-06-06解決引用slf4j中Logger.info只打印出文字沒有數(shù)據(jù)的問題
這篇文章主要介紹了解決引用slf4j中Logger.info只打印出文字沒有數(shù)據(jù)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12