MyBatis 使用

📢 本文由 gemini-3-flash-preview 翻譯

MyBatis 系列

MyBatis 安裝與入門: https://blog.yexca.net/zh-tw/archives/215
MyBatis 使用: 本文

刪除

使用 #{} 作為佔位符,內部為參數名稱

1
2
3
4
5
6
@Mapper
public interface EmpMapper {
    // 刪除
    @Delete("delete from mybatis.emp where id = #{id}")
    public void delete(Integer id);
}

測試

1
2
3
4
5
6
7
8
9
@SpringBootTest
class Mybatis02ApplicationTests {
    @Autowired
    private EmpMapper empMapper;
    @Test
    void contextLoads() {
        empMapper.delete(17);
    }
}

一般不需要回傳值,回傳值為此次操作影響的列數(筆數)

1
2
3
4
5
6
@Mapper
public interface EmpMapper {
    // 刪除
    @Delete("delete from mybatis.emp where id = #{id}")
    public int delete(Integer id);
}

佔位符

參數佔位符有 #{}${}

佔位符#{}${}
形式預編譯拼接
使用時機參數傳遞、登入等對資料表名稱、列表動態設置
優劣安全,效能高存在 SQL 注入問題

插入 (新增)

同樣使用佔位符,不過若傳遞的參數過多可以使用物件封裝,形式參數名稱為物件的屬性名稱

1
2
3
4
5
6
7
@Mapper
public interface EmpMapper {
    // 新增
    @Insert("insert into mybatis.emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) "+
            "values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime});")
    public void insert(Emp emp);
}

測試

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SpringBootTest
class Mybatis02ApplicationTests {
    
    @Autowired
    private EmpMapper empMapper;
    
    @Test
    public void testInsert(){
        Emp emp = new Emp();
        emp.setUsername("Tom");
        emp.setName("湯姆");
        emp.setGender((short) 1);
        emp.setImage("tom.jpg");
        emp.setJob((short) 1);
        emp.setEntrydate(LocalDate.of(2000,1,1));
        emp.setDeptId(1);
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());

        empMapper.insert(emp);
    }
}

獲取主鍵值

某些情況下在資料新增成功後,需要獲取寫入資料庫資料的主鍵

1
2
3
4
5
6
7
8
9
@Mapper
public interface EmpMapper {
    // 獲取主鍵,第一個屬性定義主鍵賦值在 emp 物件的 id 屬性,第二個屬性代表需要獲取回傳主鍵值
    @Options(keyProperty = "id", useGeneratedKeys = true)
    // 新增
    @Insert("insert into mybatis.emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) "+
            "values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime});")
    public void insert(Emp emp);
}

更新 (修改)

與新增類似,可以封裝到一個物件裡

1
2
3
4
5
6
7
@Mapper
public interface EmpMapper {
    // 修改
    @Update("update mybatis.emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}, job = #{job}, "+
            "entrydate = #{entrydate}, dept_id = #{deptId}, update_time = #{updateTime} where id = #{id};")
    public void update(Emp emp);
}

測試

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@SpringBootTest
class Mybatis02ApplicationTests {
    
    @Autowired
    private EmpMapper empMapper;
    
    @Test
    public void testUpdate(){
        Emp emp = new Emp();
        emp.setUsername("Tom2");
        emp.setName("湯姆2");
        emp.setGender((short) 1);
        emp.setImage("tom.jpg");
        emp.setJob((short) 1);
        emp.setEntrydate(LocalDate.of(2000,1,1));
        emp.setDeptId(1);
        emp.setUpdateTime(LocalDateTime.now());
        emp.setId(19);
        empMapper.update(emp);
    }
}

查詢

查詢有兩種,根據 id 查詢全部屬性,以及根據條件查詢

根據 ID 查詢

介面

1
2
3
4
5
6
@Mapper
public interface EmpMapper {
    // 根據ID查詢
    @Select("select * from mybatis.emp where id = #{id}")
    public Emp idSelect(Integer id);
}

測試

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@SpringBootTest
class Mybatis02ApplicationTests {
    
    @Autowired
    private EmpMapper empMapper;
    
    @Test
    public void testIdSelect(){
        Emp emp = empMapper.idSelect(19);
        System.out.println(emp);
    }
}

結果

1
Emp(id=19, username=Tom2, password=123456, name=湯姆2, gender=1, image=tom.jpg, job=1, entrydate=2000-01-01, deptId=null, createTime=null, updateTime=null)

這樣查詢出來的結果,因為類別 Emp 與資料庫的部分欄位名稱不同,導致結果為 null

有三種方法可以解決

使用別名

1
2
3
4
5
6
7
@Mapper
public interface EmpMapper {
    // 方法一:別名
    @Select("select id, username, password, name, gender, image, job, entrydate, dept_id deptId, create_time createTime, update_time updateTime" +
            " from mybatis.emp where id = #{id}")
    public Emp idSelect(Integer id);
}

使用 @Results 註解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Mapper
public interface EmpMapper {
    // 方法二:@Results @Result 註解
    @Results({
            @Result(column = "dept_id", property = "deptId"),
            @Result(column = "create_time", property = "createTime"),
            @Result(column = "update_time", property = "updateTime")
    })
    @Select("select * from mybatis.emp where id = #{id}")
    public Emp idSelect(Integer id);
}

使用自動封裝

如果欄位名稱與屬性名稱符合駝峰式命名規則,可以開啟駝峰式命名自動封裝。MyBatis 會自動透過駝峰式命名規則映射,具體為資料庫使用底線 a_column 映射到 Java 屬性 aColumn

開啟駝峰式命名需在 application.properties 加入

1
mybatis.configuration.map-underscore-to-camel-case=true

然後直接使用最初的程式碼

1
2
3
4
5
6
@Mapper
public interface EmpMapper {
    // 根據ID查詢
    @Select("select * from mybatis.emp where id = #{id}")
    public Emp idSelect(Integer id);
}

為了方便使用,可以在 IDEA 中安裝 MyBatisX 外掛

根據條件查詢

需求:根據員工姓名 (模糊比對)、性別 (精準比對)、到職時間 (範圍) 搜尋符合條件的員工資訊

1
2
3
4
5
6
7
8
@Mapper
public interface EmpMapper {
    // 根據條件查詢
    // 注意此處 name like '%${name}%' 使用的是 ${},可以在 '' 內使用
    @Select("select * from mybatis.emp where name like '%${name}%' and gender = #{gender} and " +
            "entrydate between #{begin} and #{end} order by update_time desc")
    public List<Emp> conditionSelect(String name, short gender, LocalDate begin, LocalDate end);
}

因為使用了插值進行字串拼接,所以此方法不安全

1
2
3
4
5
6
7
@Mapper
public interface EmpMapper {
    // 更安全的條件查詢
    @Select("select * from mybatis.emp where name like concat('%',#{name},'%') and gender = #{gender} and " +
            "entrydate between #{begin} and #{end} order by update_time desc")
    public List<Emp> conditionSelect(String name, short gender, LocalDate begin, LocalDate end);
}

使用了 concat 函式拼接字串。測試

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@SpringBootTest
class Mybatis02ApplicationTests {
    
    @Autowired
    private EmpMapper empMapper;
    
    @Test
    public void testConditionSelect(){
        List<Emp> empList = empMapper.conditionSelect("張", (short) 1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));
        System.out.println(empList);
    }
}

參數名稱說明

在 SQL 中使用的 #{} 變數名稱在 SpringBoot 2.x 以後的版本會被自動辨識,但在 1.x 版本或單獨使用 MyBatis 時,方法定義的變數名稱需要使用註解指定才能被辨識

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Mapper
public interface EmpMapper {
    // 更安全的條件查詢
    @Select("select * from mybatis.emp where name like concat('%',#{name},'%') and gender = #{gender} and " +
            "entrydate between #{begin} and #{end} order by update_time desc")
    public List<Emp> conditionSelect(String name, short gender, LocalDate begin, LocalDate end);
}

// 上述程式碼在 1.x 或單獨使用 MyBatis 時不會生效,函式要添加註解

public List<Emp> conditionSelect(@Param("name") String name, @Param("gender") short gender, @Param("begin") LocalDate begin, @Param("end") LocalDate end);

使用 XML 映射文件

如果 SQL 程式碼比較簡短,使用註解很方便,但若 SQL 較長或複雜則會顯得稍微凌亂,為此可以使用 XML 映射文件。以下為規範:

  • XML 映射文件的名稱與 Mapper 介面名稱一致,且將 XML 映射文件和 Mapper 介面放在相同套件 (Package) 下 (同套件同名)
  • XML 中的 namespace 屬性與 Mapper 介面全限定名一致
  • XML 中 SQL 語句的 id 屬性與 Mapper 介面中的方法名稱一致,且回傳型別一致

XML

在 Maven 專案中,非 Java 檔案放在 src/main/resources 下,在此目錄下建立與 Mapper 介面一致的目錄

如介面為 net.yexca.mapper.EmpMapper.java,則 XML 檔案在 net.yexca.mapper.EmpMapper.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 上方標頭檔請至官方網站尋找 -->
<!-- namespace 屬性為介面 -->
<mapper namespace="net.yexca.mapper.EmpMapper">
    <!--  id 屬性為方法名稱 -->
    <!--  resultType 為單筆紀錄所封裝的型別 -->
    <select id="conditionSelect" resultType="net.yexca.pojo.Emp">
        select * from mybatis.emp where name like concat('%',#{name},'%') and gender = #{gender} and
        entrydate between #{begin} and #{end} order by update_time desc
    </select>
</mapper>

介面類別

1
2
3
4
@Mapper
public interface EmpMapper {
    public List<Emp> conditionSelect(String name, short gender, LocalDate begin, LocalDate end);
}

動態 SQL

動態 SQL 是指隨著使用者的輸入或外部條件的變化而變化的 SQL 語句

例如上述最後一個條件查詢,必須輸入全部三個條件才能查詢,但實際使用時或許只想指定其中一兩個或不指定 (查詢全部),如果按照上述指令執行將回傳空結果

where if

用於判斷條件是否成立,使用 test 屬性進行條件判斷,如果條件為 true,則拼接 SQL

例如,當姓名不為空時,拼接條件透過姓名查詢

1
2
3
<if test="name!=null">
    name like contact('%',#{name},'%')
</if>

而 where 標籤則管理是否生成 where 關鍵字,並自動去除多餘的 and 和 or 關鍵字

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<select id="conditionSelect" resultType="net.yexca.pojo.Emp">
        select * from mybatis.emp
        <where>
            <if test="name!=null">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender!=null">
                and gender = #{gender}
            </if>
            <if test="begin!=null and end!=null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
        order by update_time desc
    </select>

set

替換 SQL 中的 set 關鍵字,並自動去除多餘的逗號,用於 UPDATE 語句中

例如將以下 SQL 語句修改為動態 SQL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
update mybatis.emp
    set username    = #{username},
        name        = #{name},
        gender      = #{gender},
        image       = #{image},
        job         = #{job},
        entrydate   = #{entrydate},
        dept_id     = #{deptId},
        update_time = #{updateTime}
    where id = #{id};

XML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<update id="update2">
    update mybatis.emp
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="name != null">name = #{name},</if>
        <if test="gender != null">gender = #{gender},</if>
        <if test="image != null">image = #{image},</if>
        <if test="job != null">job = #{job},</if>
        <if test="entrydate != null">entrydate = #{entrydate},</if>
        <if test="deptId != null">dept_id = #{deptId},</if>
        <if test="updateTime != null">update_time = #{updateTime}</if>
    </set>
    where id = #{id};
</update>

foreach

foreach 標籤用於遍歷

1
2
3
4
      <!--遍歷的集合    遍歷出的元素  分隔符號     開始前後拼接的 SQL 片段-->
<foreach collection="ids" item="id" separator="," open="(" close=")">
    #{id}
</foreach>

假如 ids 為 [13, 14, 15],以上將產生 (13,14,15)

需求:遍歷刪除 id 為 1, 2, 3。SQL 語句為:

1
delete from emp where id in(1,2,3)

介面方法

1
public void deleteByIds(List<Integer> ids);

XML

1
2
3
4
5
6
<delete id="deleteByIds">
    delete from mybatis.emp where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

測試

1
2
3
4
5
@Test
public void testDeleteByIds(){
    List<Integer> ids = Arrays.asList(13,14,15);
    empMapper.deleteByIds(ids);
}

sql 與 include

在開發過程中可能會出現大量重複的 SQL 語句,可以用 sql 標籤定義可重用的 SQL 片段,再透過 include 標籤重用

1
2
3
4
5
6
7
<!-- 透過 id 屬性唯一識別語句 -->
<sql id = "commonCode">
    <!-- SQL 語句 -->
</sql>

<!-- 透過 refid 屬性指定引用的語句 -->
<include refid = "commonCode" />