當前位置: 妍妍網 > 碼農

MyBatis 關於${}、#{}, 這個坑真不小~

2024-04-22碼農

MyBatis作為一個輕量級的ORM框架,套用廣泛,其上手使用也比較簡單;一個成熟的框架,必然有精巧的設計,值得學習。

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

  • ${paramName}

  • #{paramName}

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

    先來回顧下原生Jdbc查詢:

    publicstaticvoidmain(String[] args)throws Exception {
    // sql語句
    String sql = "select id,name from customer limit 2";
    // 1.載入驅動, 此處使用的mysql驅動包是8.0版本, 若為5.0+版本, 請修改以下類路徑
    class.forName("com.mysql.cj.jdbc.Driver");
    // 2.獲取資料庫連線
    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、獲得可以執行sql語句的物件
    Statement st = conn.createStatement();
    // 4、使用物件去執行SQL語句
    ResultSet rs = st.executeQuery(sql);
    // 5、處理sql語句返回的結果集
    while(rs.next()){
    // 獲得一行數據
    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的人會知道,其中第3、4步兩條語句也可以換成如下兩條:

    // 3.建立 PreparedStatement 物件去執行sql
    PreparedStatement preparedStatement = conn.prepareStatement(sql);
    // 4.執行sql語句
    ResultSet rs = preparedStatement.executeQuery();

    我們來比較下區別:

  • 建立 PreparedStatement 物件時就把sql語句傳入,在執行語句時就不用傳入sql了;而 Statement 則剛好相反

  • 這就引出了 預編譯 的概念:

  • 如果使用 PreparedStatement 物件,那麽在執行第3步時,你既然已經傳入了sql,則相當於這條sql會被資料庫編譯(資料庫對sql語句的編譯也是相當復雜的),所以在第4步執行的時候就不用再傳入sql了,因為資料庫已經知道你要執行的sql了,你只需要傳入參數即可;

  • 如果使用 Statement 物件,那容易理解,資料庫就沒有提前去解析你的sql,因為你建立物件時都沒有傳入;當執行sql時,資料庫再編譯與執行。

  • 看到這裏,可能也僅僅只記住了一個預先編譯sql了,一個沒有預先編譯,並沒有了解到對於實際開發中的區別,以下將會舉例說明。

    那是否PreparedStatement物件這種方式就一定比Statement物件方式好?

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

    PreparedStatement 物件的好處是,sql已經提前編譯好,剩下的工作就是傳入參數即可,編譯好的sql可以復用,傳入不同的參數,則資料庫就將相應的參數填入編譯好的sql。而 Statement 物件就是每次都要傳入sql,丟給資料庫去編譯再執行;但是建立 PreparedStatement 物件的開銷是比 Statement 物件大的。

    回歸到日常開發中,以上的區別我們壓根也不用在意,事實上,百分之九十的場景我們使用的是 PreparedStatement 物件的方式,可能平時沒有感知到,因為這是框架已經封裝了。再者,當系統出現效能問題時,也絕對不會是因為這兩個物件的原因。

    以上簡單回顧了下Jdbc中 PreparedStatement Statement 物件;

    可以預料,mybatis中${} 與 #{} 這兩種取值方式就是相當於對應著 PreparedStatement Statement 物件的區別了。

  • #{} 傳參,代表sql已經預編譯好了,你傳入的參數真的就僅僅是參數!

  • ${} 傳參,隨便你傳,傳完了之後我再統一編譯

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

    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 = \'李白\''

    不能!會報sql語句規則錯誤,之前說了,#{} 取值代表sql已經編譯好了,你傳入的僅僅是參數

    對應上面範例:

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

    參數是指:and name = '李白'

    此時sql可以正確執行,但是帶上傳入的參數就不行了,要理解你傳入的真的僅僅是參數,不要和前面的sql混了。

    但是這明顯不對,因為想表達的其實是這樣:

    sql是指:

    select * from customer where 1 = 1 and name = ?

    參數是指:'李白'

    此時把參數替換進占位符是可以正常執行整個語句的。

    所以此時應該用 ,因為用{}就沒有提前編譯好哪些是屬於sql。

    此範例表明:當你傳入的參數不僅僅是參數,其實是一小段sql,想和原sql拼接在一起時,那就得用${}傳參,相當於拼接好了之後丟給

    資料庫去解析整個語句;

    sql中的問號代表參數占位符,這也是PreparedStatement物件特點之一,會將你傳入的參數一一替換進占位符

    反之如下:

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

    這種情況使用 #{} 就是對的了,因為傳入的參數僅僅就是參數,替換進sql語句中即可。

    2.對參數型別的影響

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

    以上程式碼能執行成功嗎?

    按理說,傳入的僅僅是參數,不管是否預編譯都應該能執行,但是實際還是會報錯。

    這是執行時打印出的sql語句:

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

    顯然,問題就在於參數沒有加單引號,name欄位是字串型別,傳入的也是字串,偏偏mybatis轉換之後沒有加單引號。

    所以當傳入字串型別參數時,應該用 #{} 取值,此時會自動加上單引號。

    再看下面這種語句:

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

    此時傳入的參數是要排序的欄位名稱,之前說了,如果采用#{} 取值,則實際是會自動加上單引號的,但是order by後面的排序欄位需要單引號嗎?

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

    你可能會發現此處用 #{} 取值也不會報錯,那是因為mysql支持這種寫法,但是查詢的結果並不對。

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

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

    >>

    END

    精品資料,超贊福利,免費領

    微信掃碼/長按辨識 添加【技術交流群

    群內每天分享精品學習資料

    最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

    👇👇

    👇點選"閱讀原文",獲取更多資料(持續更新中