欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MyBatis中#{}?和?${}?的區(qū)別和動(dòng)態(tài)?SQL詳解

 更新時(shí)間:2024年11月23日 10:10:03   作者:2的n次方_  
這篇文章主要介紹了MyBatis中#{}和${}的區(qū)別,包括參數(shù)傳遞、安全性、性能等方面,然后詳細(xì)介紹了如何使用#{}和${}進(jìn)行排序、模糊查詢、動(dòng)態(tài)SQL、數(shù)據(jù)庫(kù)連接池等操作,最后,總結(jié)了注解方式的動(dòng)態(tài)SQL,感興趣的朋友跟隨小編一起看看吧

1. #{} 和 ${} 的區(qū)別

為了方便,接下來(lái)使用注解方式來(lái)演示:

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

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

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

加上之后就可以正常查詢了

這就可能會(huì)出現(xiàn) SQL 注入的問(wèn)題

來(lái)看一下 SQL 注入的例子,假如傳入的參數(shù)是' or 1='1

@Select("select username, `password`, age, gender, phone from user_info where username= '${name}' ")
List<UserInfo> queryByName(String name);

按道理說(shuō)是沒有這個(gè)用戶的,但是卻把所有用戶的信息都查出來(lái)了

如果在某些登錄的界面輸入 SQL 注入代碼' or 1='1就可能登錄成功

使用 #{} 就沒有這個(gè)問(wèn)題

除了以上的區(qū)別外,二者還有性能方面的區(qū)別

在上面提到過(guò),#{} 是預(yù)編譯 SQL,${} 是即時(shí) SQL ,預(yù)編譯SQL編譯一次之后會(huì)將編譯后的 SQL 語(yǔ)句緩存起來(lái),后面再執(zhí)行這條語(yǔ)句時(shí),不會(huì)再次編譯,省去了解析優(yōu)化等過(guò)程,以此來(lái)提高效率,所以當(dāng)需要頻繁地使用 SQL 語(yǔ)句時(shí),預(yù)編譯的性能優(yōu)化就體現(xiàn)出來(lái)了,而對(duì)于即時(shí) SQL ,如果只是在啟動(dòng)時(shí)或者很少變化的場(chǎng)景下使用${}來(lái)配置一些數(shù)據(jù)庫(kù)對(duì)象名稱等,它可以避免預(yù)編譯的過(guò)程,執(zhí)行起來(lái)相對(duì)直接

2. 排序

在上面看來(lái),#{} 無(wú)論是在安全性還是效率上,都占據(jù)了優(yōu)勢(shì),那么都是用 #{}可以嗎?

來(lái)看使用 #{}來(lái)實(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 語(yǔ)句就會(huì)變成這樣:

select * from user_info order by id 'order'

這樣肯定是不對(duì)的,那么這個(gè)時(shí)候就需要用到 ${} 了,直接進(jìn)行參數(shù)替換,但是使用 ${} 肯定就需要考慮 SQL 注入的問(wèn)題,由于排序方式只有 asc 和 desc 兩種方式,可以采用枚舉類來(lái)進(jìn)行校驗(yàn),也可以通過(guò)判斷條件來(lái)實(shí)現(xiàn)校驗(yàn)

3. 模糊查詢

通過(guò)模糊查詢來(lái)查找名字中含有“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 注入的問(wèn)題呢,這樣就不能簡(jiǎn)單的通過(guò)枚舉或者判斷來(lái)約束傳入的參數(shù)了,這時(shí)就可以通過(guò)使用拼接的方式

通過(guò) CONCAT 函數(shù)來(lái)對(duì) SQL 語(yǔ)句進(jìn)行拼接,這樣就可以使用 #{},

@Select("select * from user_info where username like CONCAT('%',#{name},'%')")
List<UserInfo> selectUserByLike(String name);

4. 數(shù)據(jù)庫(kù)連接池

在傳統(tǒng)的數(shù)據(jù)庫(kù)訪問(wèn)模式中,每當(dāng)應(yīng)用程序需要與數(shù)據(jù)庫(kù)進(jìn)行交互時(shí),它會(huì)創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)連接,使用完畢后關(guān)閉連接,這樣頻繁地創(chuàng)建和銷毀數(shù)據(jù)庫(kù)連接會(huì)消耗大量的系統(tǒng)資源

數(shù)據(jù)庫(kù)連接池的出現(xiàn)就是為了解決這些問(wèn)題。它在應(yīng)用程序啟動(dòng)時(shí)預(yù)先創(chuàng)建一定數(shù)量的數(shù)據(jù)庫(kù)連接,將這些連接存儲(chǔ)在一個(gè) “池” 中。當(dāng)應(yīng)用程序需要訪問(wèn)數(shù)據(jù)庫(kù)時(shí),從池中獲取一個(gè)可用的連接,使用完畢后將連接歸還給池,而不是直接關(guān)閉連接,從而避免了頻繁創(chuàng)建和銷毀連接所帶來(lái)的性能開銷,這一點(diǎn)和線程池是類似的

常見的數(shù)據(jù)庫(kù)連接池有: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)來(lái)說(shuō),如果沒有填,肯定是需要賦一個(gè)默認(rèn)值的,比如 null,那么就需要?jiǎng)討B(tài) SQL 來(lái)實(shí)現(xiàn)這樣的功能

5.1. <if>

可以通過(guò) if 標(biāo)簽來(lái)實(shí)現(xiàn)一下:

@Mapper
public interface UserInfoXmlMapper {
    Integer insertUserByCondition(UserInfo userInfo);
}

再來(lái)看 XML 中的 SQL 語(yǔ)句

<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);
}

如果不傳入性別的話來(lái)看一下結(jié)果:

由于性別沒有傳入,所以說(shuō) SQL 語(yǔ)句中是只有前三個(gè)參數(shù)的,所以第三個(gè)參數(shù)那里就多了一個(gè)逗號(hào),導(dǎo)致最終的 SQL 的語(yǔ)法錯(cuò)誤

那么就可以想一個(gè)辦法,如果把逗號(hào)直接加前面,是不是就可以解決了

這樣看似是可以解決的,但是如果說(shuō) username, age 都設(shè)為了非必填的,例如 username 沒有傳入?yún)?shù),但是 age 傳入了參數(shù),這樣前面就多了一個(gè)逗號(hào),這時(shí) SQL 語(yǔ)句就又會(huì)出錯(cuò)了,把逗號(hào)都加到右邊,也是會(huì)出現(xiàn)問(wèn)題的

這時(shí)就需要用到下面的標(biāo)簽了

5.2. <trim>

主要用于去除 SQL 語(yǔ)句中多余的關(guān)鍵字或者字符,同時(shí)也可以添加自定義的前綴和后綴

?prefix:用于為包含在trim標(biāo)簽內(nèi)部的 SQL 語(yǔ)句塊添加一個(gè)前綴
?suffix:表示整個(gè)語(yǔ)句塊,以 suffix 的值作為后綴.
?prefixOverrides:為trim標(biāo)簽內(nèi)的 SQL 語(yǔ)句塊添加一個(gè)后綴.
?suffixOverrides:表示整個(gè)語(yǔ)句塊要去除掉的后綴.

<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 語(yǔ)句前面加上一個(gè) '(' ,后面加上 ')' ,如果最后是以逗號(hào)結(jié)尾的就把逗號(hào)刪了,以此來(lái)實(shí)現(xiàn) SQL 語(yǔ)句拼接的效果

5.3. <where>

來(lái)看一下條件查詢

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

但是這時(shí)其實(shí)還有一個(gè)問(wèn)題,如果說(shuō) age 和 deleteFlag 都沒有傳入的話,最后的 SQL 語(yǔ)句 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 語(yǔ)句中的 where 也不會(huì)添加,并且如果只有一個(gè)值的話,前面的 and 也會(huì)被去掉,也不用 trim 標(biāo)簽了,不過(guò)去掉的是前面的 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 語(yǔ)句還是有問(wèn)題

<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 語(yǔ)句中遍歷集合,動(dòng)態(tài)地構(gòu)建包含多個(gè)參數(shù)的 SQL 語(yǔ)句,比如IN子句、批量插入語(yǔ)句等

  • collection:綁定方法參數(shù)中的集合,如 List,Set,Map 或數(shù)組對(duì)象。
  • item:遍歷時(shí)的每一個(gè)對(duì)象。
  • open:語(yǔ)句塊開頭的字符串。
  • close:語(yǔ)句塊結(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 語(yǔ)句中,使得 SQL 語(yǔ)句的編寫更加模塊化,減少重復(fù)代碼

例如上面的重復(fù)語(yǔ)句就可以提取出來(lái)

<sql id="insertCol">
  insert into user_info(username, password, age, gender)
</sql>

然后就可以通過(guò) include 標(biāo)簽來(lái)引用了

6. 注解方式的動(dòng)態(tài) SQL

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

主頁(yè) 

到此這篇關(guān)于MyBatis中#{} 和 ${} 的區(qū)別和動(dòng)態(tài) SQL的文章就介紹到這了,更多相關(guān)MyBatis #{} 和 ${} 區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論