<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          mybatis中 ${} 和 #{} 傻傻分不清

          共 6308字,需瀏覽 13分鐘

           ·

          2021-09-17 13:22

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          mybatis作為一個(gè)輕量級的ORM框架,應(yīng)用廣泛,其上手使用也比較簡單;一個(gè)成熟的框架,必然有精巧的設(shè)計(jì),值得學(xué)習(xí)。

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

          • ${paramName}

          • #{paramName}

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

          先來回顧下原生Jdbc查詢:

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

            // sql語句
            String sql = "select id,name from customer limit 2";
            // 1.加載驅(qū)動(dòng), 此處使用的mysql驅(qū)動(dòng)包是8.0版本, 若為5.0+版本, 請修改以下類路徑
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2.獲取數(shù)據(jù)庫連接
            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語句的對象
            Statement st = conn.createStatement();
            // 4、使用對象去執(zhí)行SQL語句
            ResultSet rs = st.executeQuery(sql);
            // 5、處理sql語句返回的結(jié)果集
            while(rs.next()){
              // 獲得一行數(shù)據(jù)
              Integer id = rs.getInt("id");
              String name = rs.getString("name");
              System.out.println("sql查詢: id = " + id + " , name = " + name);
            }
            // 6、釋放資源
            rs.close();
            st.close();
            conn.close();
          }

          控制臺打印:

          sql查詢: id = 1 , name = 李白
          sql查詢: id = 2 , name = 杜甫

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

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

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

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

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

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

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

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

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

          沒有那么絕對的事,大家要理解:

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

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


          以上簡單回顧了下Jdbc中PreparedStatementStatement對象;

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

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

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

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

          1.看如下service和sql語句

          @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>

          以上代碼能正常查詢嗎?

          ### 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語句規(guī)則錯(cuò)誤,之前說了,#{} 取值代表sql已經(jīng)編譯好了,你傳入的僅僅是參數(shù)

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

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

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

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

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

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

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

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

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

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

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

          sql中的問號代表參數(shù)占位符,這也是PreparedStatement對象特點(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>

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

          2.對參數(shù)類型的影響

          @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í)行成功嗎?

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

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

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

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

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

          再看下面這種語句:

          @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ù)是要排序的字段名稱,之前說了,如果采用#{} 取值,則實(shí)際是會(huì)自動(dòng)加上單引號的,但是order by后面的排序字段需要單引號嗎?

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

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

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


            作者 |  tjstep 

          來源 |  cnblogs.com/tjstep/p/15256463.html


          加鋒哥微信: java1239  
          圍觀鋒哥朋友圈,每天推送Java干貨!

          瀏覽 24
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  啪啪官网 | 午夜神马久久 | 操BXXXXX | 韩国免费一区二区 | 婷婷五月天社区 |