mybatis 07: sql標籤中 "#{}" 和 "${}" 的作用和比較

2022-08-12 21:00:49

"#{}"預留位置

作用

  • 傳參大部分使用"#{}",在資料庫底層使用的是:PreparedStatement預編譯處理物件
  • 資料庫底層被解析為"?",用來傳值,是安全的資料庫存取,可以防止sql注入
  • 通過在SqlMapConfig.xml新增紀錄檔輸出設定,在後文測試輸出的結果中可以驗證
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!-- 設定紀錄檔輸出-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

</configuration>

寫法

  • "#{}"裡引數的寫法,要參考parameterType的型別
  • 如果parameterType的型別是簡單型別(8種基本型別(封裝) + String型別),則"#{}"裡變數名稱任意
    <!--
        //根據使用者主鍵查取使用者資訊
        User getById();
    -->

    <select id="getById" resultType="user" parameterType="int">
        select
            id, username, birthday, sex, address
        from
            users
        where
            id=#{asYouLike}
    </select>
  • 如果parameterType的型別是實體類的型別,則"#{}"裡只能是類中成員變數的名稱,而且區分大小寫
//User實體類中的屬性

public class User {
    private Integer id;
    private String userName;
    private Date birthday;
    private String sex;
    private String address;
}
   <!--
        //向用戶表中增加使用者資訊
        int insert(User user);
   -->

    <insert id="insert" parameterType="user">
        insert into
                users(username, birthday, sex, address)
        values(#{userName}, #{birthday}, #{sex}, #{address})
    </insert>

"${}"預留位置

用於字串的拼接和字串的替換

字串拼接

作用

  • 一般用於模糊查詢,建議少用,因為存在sql注入風險

寫法

  • "${}"中引數名稱的寫法,分兩種情況,與"#{}"的兩種情況相同,可參考之
  • 注意:對於parameterType的型別是簡單型別時,"${}"裡變數名稱隨便寫,但是分版本
    • 如果是3.5.1及以下版本,只能寫"value"

模糊查詢範例

  • 未優化前,存在sql注入風險
    <!--
        //根據使用者名稱模糊查詢使用者資訊
        List<User> getByName(String name);
    -->

    <select id="getByName" parameterType="string" resultType="user">
        select
            id, username, birthday, sex, address
        from
            users
        where username like '%${name}%'
    </select>
  • 優化後,使用"#{}"接受傳參,底層是preparedStatement預編譯物件,可以防止sql注入
    <!--
        //優化後的模糊查詢
        List<User> getByNameBetter(String name);
    -->

    <select id="getByNameBetter" parameterType="string" resultType="user">
        select
            id, username, birthday, sex, address
        from
            users
        where username like concat('%', #{name}, '%')
    </select>

字串替換("${}"的主要作用)

需求:在users表中,根據地址或者使用者名稱模糊查詢使用者資訊

sql語句:

select * from users where username like '%模糊查詢條件%'

select * from users where address like '%模糊查詢條件%'

存在的問題:兩條sql語句的結構在本質上是相同的,寫兩條語句十分冗餘,可以採用替換列名的方式進行優化

UsersMapper.java介面

package com.example.mapper;

import com.example.pojo.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 資料存取層的介面,定義對資料庫完成的CRUD的操作
 */
public interface UsersMapper {
    
    //根據使用者名稱或者地址模糊查詢
    List<User> getByNameOrAddress(
            @Param("colName")
            String colName,
            @Param("userName")
            String userName
    );
}
  • 介面分析:當介面中的方法的引數有多個時,用註解標識引數,sql標籤可通過註解中宣告的引數名獲取引數

UsersMapper.xml檔案

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.UsersMapper">

    <!--
        //根據使用者名稱或者地址模糊查詢
        List<User> getByNameOrAddress(
                @Param("colName")
                String colName,
                @Param("userName")
                String userName);
    -->
    
    <select id="getByNameOrAddress" resultType="user">
        select
            id, username, birthday, sex, address
        from
            users
        where
            ${colName} like concat('%', #{userName}, '%')
    </select>

</mapper>
  • sql標籤分析
    • 當標籤對應的介面中的方法有多個引數時,標籤中的入參型別,即:parameterType,取消不寫,通過方法中註解的引數名稱獲取引數
    • 用於替換時只可以使用"${}",應該放在like前。"#{}"用來傳值,應該放在like後用來佔位傳值

測試程式碼

package com.example.mapper;

import com.example.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class TestUsersMapper {

    //時間刷
    SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd");

    //SqlSession物件
    SqlSession sqlSession;

    //mybatis動態代理物件
    UsersMapper usersMapper;

    //獲取SqlSession
    @Before
    public void getSqlSession() throws IOException {
        //讀取核心組態檔
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //建立SqlSessionFactory物件
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //獲取SqlSession
        sqlSession = factory.openSession();
        //獲取mybatis動態代理物件
        usersMapper = sqlSession.getMapper(UsersMapper.class);
    }

    //歸還SqlSession
    @After
    public void closeSession(){
        sqlSession.close();
    }
    

    @Test
    public void testGetByNameOrAddress(){
        List<User> users = usersMapper.getByNameOrAddress("username", "小");
        //List<User> users = usersMapper.getByNameOrAddress("address", "市");
        users.forEach(System.out::println);
    }
}

測試輸出

根據使用者名稱模糊查詢

Checking to see if class com.example.mapper.TestUsersMapper matches criteria [is assignable to Object]
Checking to see if class com.example.mapper.UsersMapper matches criteria [is assignable to Object]
Opening JDBC Connection
Created connection 1293462056.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d18aa28]
    
==>  Preparing: select id, username, birthday, sex, address from users where username like concat('%', ?, '%')
==> Parameters: 小(String)
    
<==    Columns: id, username, birthday, sex, address
<==        Row: 2, 小王, 2001-07-12, 1, 蕪湖市
<==        Row: 3, 小張, 1999-02-22, 1, 長沙
<==        Row: 29, 小昕, 2001-03-14, 女, 忻州
<==      Total: 3
Users{id=2, userName='小王', birthday=Thu Jul 12 00:00:00 CST 2001, sex='1', address='蕪湖市'}
Users{id=3, userName='小張', birthday=Mon Feb 22 00:00:00 CST 1999, sex='1', address='長沙'}
Users{id=29, userName='小昕', birthday=Wed Mar 14 00:00:00 CST 2001, sex='女', address='忻州'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d18aa28]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d18aa28]
Returned connection 1293462056 to pool.

Process finished with exit code 0

根據地址模糊查詢

Checking to see if class com.example.mapper.TestUsersMapper matches criteria [is assignable to Object]
Checking to see if class com.example.mapper.UsersMapper matches criteria [is assignable to Object]
Opening JDBC Connection
Created connection 1293462056.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d18aa28]
    
==>  Preparing: select id, username, birthday, sex, address from users where address like concat('%', ?, '%')
==> Parameters: 市(String)
    
<==    Columns: id, username, birthday, sex, address
<==        Row: 2, 小王, 2001-07-12, 1, 蕪湖市
<==        Row: 7, 學委, 2001-05-13, 2, 平頂山市
<==      Total: 2
Users{id=2, userName='小王', birthday=Thu Jul 12 00:00:00 CST 2001, sex='1', address='蕪湖市'}
Users{id=7, userName='學委', birthday=Sun May 13 00:00:00 CST 2001, sex='2', address='平頂山市'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d18aa28]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d18aa28]
Returned connection 1293462056 to pool.

Process finished with exit code 0

測試結果分析

  • sql標籤在底層分別被解析為
==>  Preparing: select id, username, birthday, sex, address from users where username like concat('%', ?, '%')
==> Parameters: 小(String)
==>  Preparing: select id, username, birthday, sex, address from users where address like concat('%', ?, '%')
==> Parameters: 市(String)
  • 由上可知
    • 標籤中的${colName}分別被替換成了傳入的"username"和"address",起到了替換作用
    • "#{}"被解析成"?",#{userName}分別拿到值:小(String)和市(String),起到佔位傳值作用