溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

mybatis中${}和#{}取值的區(qū)別有哪些

發(fā)布時(shí)間:2021-09-13 12:47:33 來(lái)源:億速云 閱讀:194 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)mybatis中${}和#{}取值的區(qū)別有哪些,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

在使用mybatis框架時(shí),在sql語(yǔ)句中獲取傳入的參數(shù)有如下兩種方式:

  • ${paramName}

  • #{paramName}

那如何理解這兩種傳參方式呢?如下帶你走近背后的奧義。

先來(lái)回顧下原生Jdbc查詢(xún):

public static void main(String[] args) throws Exception {

  // sql語(yǔ)句
  String sql = "select id,name from customer limit 2";
  // 1.加載驅(qū)動(dòng), 此處使用的mysql驅(qū)動(dòng)包是8.0版本, 若為5.0+版本, 請(qǐng)修改以下類(lèi)路徑
  Class.forName("com.mysql.cj.jdbc.Driver");
  // 2.獲取數(shù)據(jù)庫(kù)連接
  String url = "jdbc:mysql://localhost:3306/work?useSSL=false&useUnicode=true" +
    "&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true" +
    "&useLegacyDatetimeCode=false&serverTimezone=UTC";
  Connection conn = DriverManager.getConnection(url,"root", "123456");
  // 3、獲得可以執(zhí)行sql語(yǔ)句的對(duì)象
  Statement st = conn.createStatement();
  // 4、使用對(duì)象去執(zhí)行SQL語(yǔ)句
  ResultSet rs = st.executeQuery(sql);
  // 5、處理sql語(yǔ)句返回的結(jié)果集
  while(rs.next()){
    // 獲得一行數(shù)據(jù)
    Integer id = rs.getInt("id");
    String name = rs.getString("name");
    System.out.println("sql查詢(xún): id = " + id + " , name = " + name);
  }
  // 6、釋放資源
  rs.close();
  st.close();
  conn.close();
}

控制臺(tái)打?。?/p>

sql查詢(xún): id = 1 , name = 李白
sql查詢(xún): id = 2 , name = 杜甫

了解Jdbc的人會(huì)知道,其中第3、4步兩條語(yǔ)句也可以換成如下兩條:

// 3.創(chuàng)建 PreparedStatement 對(duì)象去執(zhí)行sql
PreparedStatement preparedStatement = conn.prepareStatement(sql);
// 4.執(zhí)行sql語(yǔ)句
ResultSet rs = preparedStatement.executeQuery();

我們來(lái)比較下區(qū)別:

  • 創(chuàng)建 PreparedStatement 對(duì)象時(shí)就把sql語(yǔ)句傳入,在執(zhí)行語(yǔ)句時(shí)就不用傳入sql了;而 Statement 則剛好相反

這就引出了預(yù)編譯的概念:

  • 如果使用PreparedStatement 對(duì)象,那么在執(zhí)行第3步時(shí),你既然已經(jīng)傳入了sql,則相當(dāng)于這條sql會(huì)被數(shù)據(jù)庫(kù)編譯(數(shù)據(jù)庫(kù)對(duì)sql語(yǔ)句的編譯也是相當(dāng)復(fù)雜的),所以在第4步執(zhí)行的時(shí)候就不用再傳入sql了,因?yàn)閿?shù)據(jù)庫(kù)已經(jīng)知道你要執(zhí)行的sql了,你只需要傳入?yún)?shù)即可;

  • 如果使用Statement對(duì)象,那容易理解,數(shù)據(jù)庫(kù)就沒(méi)有提前去解析你的sql,因?yàn)槟銊?chuàng)建對(duì)象時(shí)都沒(méi)有傳入;當(dāng)執(zhí)行sql時(shí),數(shù)據(jù)庫(kù)再編譯與執(zhí)行。

看到這里,可能也僅僅只記住了一個(gè)預(yù)先編譯sql了,一個(gè)沒(méi)有預(yù)先編譯,并沒(méi)有了解到對(duì)于實(shí)際開(kāi)發(fā)中的區(qū)別,以下將會(huì)舉例說(shuō)明。

那是否PreparedStatement對(duì)象這種方式就一定比Statement對(duì)象方式好?

沒(méi)有那么絕對(duì)的事,大家要理解:

PreparedStatement對(duì)象的好處是,sql已經(jīng)提前編譯好,剩下的工作就是傳入?yún)?shù)即可,編譯好的sql可以復(fù)用,傳入不同的參數(shù),則數(shù)據(jù)庫(kù)就將相應(yīng)的參數(shù)填入編譯好的sql。而Statement對(duì)象就是每次都要傳入sql,丟給數(shù)據(jù)庫(kù)去編譯再執(zhí)行;但是創(chuàng)建PreparedStatement對(duì)象的開(kāi)銷(xiāo)是比Statement對(duì)象大的。

回歸到日常開(kāi)發(fā)中,以上的區(qū)別我們壓根也不用在意,事實(shí)上,百分之九十的場(chǎng)景我們使用的是PreparedStatement對(duì)象的方式,可能平時(shí)沒(méi)有感知到,因?yàn)檫@是框架已經(jīng)封裝了。再者,當(dāng)系統(tǒng)出現(xiàn)性能問(wèn)題時(shí),也絕對(duì)不會(huì)是因?yàn)檫@兩個(gè)對(duì)象的原因。

以上簡(jiǎn)單回顧了下Jdbc中PreparedStatement與Statement對(duì)象;

可以預(yù)料,mybatis中${} 與 #{} 這兩種取值方式就是相當(dāng)于對(duì)應(yīng)著PreparedStatement和Statement對(duì)象的區(qū)別了。

  • #{} 傳參,代表sql已經(jīng)預(yù)編譯好了,你傳入的參數(shù)真的就僅僅是參數(shù)!

  • ${} 傳參,隨便你傳,傳完了之后我再統(tǒng)一編譯

那具體在使用中有什么不同呢?理解如下兩種場(chǎng)景:

1.看如下service和sql語(yǔ)句

@Override
public List<Map<String, Object>> listUser() {
  String param = " and name = '李白'";
  return indexMapper.listUser(param);
}
<select id="listUser" resultType="map">
  select * from customer
  where 1 = 1 #{param}
</select>

以上代碼能正常查詢(xún)嗎?

### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: You have
  an error in your SQL syntax; check the manual that corresponds to your MySQL
    server version for the right syntax to use near '' and name = \'李白\''

不能!會(huì)報(bào)sql語(yǔ)句規(guī)則錯(cuò)誤,之前說(shuō)了,#{} 取值代表sql已經(jīng)編譯好了,你傳入的僅僅是參數(shù)

對(duì)應(yīng)上面示例:

sql是指:select * from customer where 1 = 1

參數(shù)是指:and name = '李白'

此時(shí)sql可以正確運(yùn)行,但是帶上傳入的參數(shù)就不行了,要理解你傳入的真的僅僅是參數(shù),不要和前面的sql混了。

但是這明顯不對(duì),因?yàn)橄氡磉_(dá)的其實(shí)是這樣:

sql是指:select * from customer where 1 = 1 and name = ?

參數(shù)是指:'李白'

此時(shí)把參數(shù)替換進(jìn)占位符是可以正常運(yùn)行整個(gè)語(yǔ)句的。

所以此時(shí)應(yīng)該用 ${param},因?yàn)橛?{}就沒(méi)有提前編譯好哪些是屬于sql。

此示例表明:當(dāng)你傳入的參數(shù)不僅僅是參數(shù),其實(shí)是一小段sql,想和原sql拼接在一起時(shí),那就得用${}傳參,相當(dāng)于拼接好了之后丟給

數(shù)據(jù)庫(kù)去解析整個(gè)語(yǔ)句;

sql中的問(wèn)號(hào)代表參數(shù)占位符,這也是PreparedStatement對(duì)象特點(diǎn)之一,會(huì)將你傳入的參數(shù)一一替換進(jìn)占位符

反之如下:

@Override
public List<Map<String, Object>> listUser() {
  String param = "李白";
  return indexMapper.listUser(param);
}
<select id="listUser" resultType="map">
  select * from customer
  where 1 = 1 and name = #{param}
</select>

這種情況使用 #{} 就是對(duì)的了,因?yàn)閭魅氲膮?shù)僅僅就是參數(shù),替換進(jìn)sql語(yǔ)句中即可。

2.對(duì)參數(shù)類(lèi)型的影響

@Override
public List<Map<String, Object>> listUser() {
  String param = "李白";
  return indexMapper.listUser(param);
}
<select id="listUser" resultType="map">
  select * from customer
  where 1 = 1 and name = ${param}
</select>

以上代碼能執(zhí)行成功嗎?

按理說(shuō),傳入的僅僅是參數(shù),不管是否預(yù)編譯都應(yīng)該能執(zhí)行,但是實(shí)際還是會(huì)報(bào)錯(cuò)。

這是執(zhí)行時(shí)打印出的sql語(yǔ)句:

select * from customer where 1 = 1 and name = 李白

顯然,問(wèn)題就在于參數(shù)沒(méi)有加單引號(hào),name字段是字符串類(lèi)型,傳入的也是字符串,偏偏mybatis轉(zhuǎn)換之后沒(méi)有加單引號(hào)。

所以當(dāng)傳入字符串類(lèi)型參數(shù)時(shí),應(yīng)該用 #{} 取值,此時(shí)會(huì)自動(dòng)加上單引號(hào)。

再看下面這種語(yǔ)句:

@Override
public List<Map<String, Object>> listUser() {
  String param = "name";
  return indexMapper.listUser(param);
}
<select id="listUser" resultType="map">
  select * from customer
  where 1 = 1
  order by ${param} desc
</select>

此時(shí)傳入的參數(shù)是要排序的字段名稱(chēng),之前說(shuō)了,如果采用#{} 取值,則實(shí)際是會(huì)自動(dòng)加上單引號(hào)的,但是order by后面的排序字段需要單引號(hào)嗎?

不需要,所以這種情況只能使用 ${} 取值。

你可能會(huì)發(fā)現(xiàn)此處用 #{} 取值也不會(huì)報(bào)錯(cuò),那是因?yàn)閙ysql支持這種寫(xiě)法,但是查詢(xún)的結(jié)果并不對(duì)。

日常開(kāi)發(fā)中,只要能理解上述兩種情形,那么就能正確使用 ${} 和 #{},由于這兩種方式取值原理的區(qū)別,也容易明白 #{} 這種方式是可以防止sql注入的。

關(guān)于“mybatis中${}和#{}取值的區(qū)別有哪些”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI