MyBatis從入門到精通(四):MyBatis XML方式的基本用法之增刪改

申城異鄉人發表於2019-07-04

最近在讀劉增輝老師所著的《MyBatis從入門到精通》一書,很有收穫,於是將自己學習的過程以部落格形式輸出,如有錯誤,歡迎指正,如幫助到你,不勝榮幸!

1. insert用法

1.1 簡單的insert方法

假如現在我們想新增一個使用者,該如何操作呢?

首先,在介面SysUserMapper中新增如下方法。

/**
 * 新增使用者
 *
 * @param sysUser
 * @return
 */
int insert(SysUser sysUser);

然後開啟對應的SysUserMapper.xml檔案,新增如下語句。

<insert id="insert">
    INSERT INTO sys_user(id, user_name, user_password, user_email, user_info, head_img, create_time)
    VALUES (#{id},#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
</insert>

特別說明:

1)為了防止型別錯誤,對於一些特殊的資料型別,建議指定具體的jdbcType值。例如headImg指定BLOB型別,createTime指定TIMESTAMP型別。

2)BLOB對應的型別是ByteArrayInputStream,就是二進位制資料流。

3)由於資料庫區分date、time、datetime型別,但是在Java中一般都使用java.util.Date型別。因此為了保證資料型別的正確,需要手動指定日期型別。date、time、datetime對應的JDBC型別分別為DATE、TIME、TIMESTAMP。

在SysUserMapperTest測試類中新增如下程式碼,測試下insert()方法。

@Test
public void testInsert() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);

        SysUser sysUser = new SysUser();
        sysUser.setUserName("test1");
        sysUser.setUserPassword("123456");
        sysUser.setUserEmail("test@mybatis.tk");
        sysUser.setUserInfo("test info");
        // 正常情況下應該讀入一張圖片儲存到byte陣列中
        sysUser.setHeadImg(new byte[]{1, 2, 3});
        sysUser.setCreateTime(new Date());

        // 這裡的返回值result是執行的SQL影響的行數
        int result = sysUserMapper.insert(sysUser);
        // 只插入1條資料
        Assert.assertEquals(1, result);
        // id為null,沒有給id賦值,並且沒有配置回寫id的值
        Assert.assertNull(sysUser.getId());
    } finally {
        // 為了不影響其他測試,這裡選擇回滾
        // 預設的sqlSessionFactory.openSession()是不自動提交的
        // 因此不手動執行commit也不會提交到資料庫
        sqlSession.rollback();
        sqlSession.close();
    }
}

執行該測試方法,輸出日誌如下。

DEBUG [main] - ==> Preparing: INSERT INTO sys_user(id, user_name, user_password, user_email, user_info, head_img, create_time) VALUES (?,?,?,?,?,?,?)

DEBUG [main] - ==> Parameters: null, test1(String), 123456(String), test@mybatis.tk(String), test info(String), java.io.ByteArrayInputStream@544a2ea6(ByteArrayInputStream), 2019-07-02 13:09:07.822(Timestamp)

DEBUG [main] - <== Updates: 1

現在我們修改下createTime指定的jdbcType型別,直觀的理解下jdbcType值的作用。

createTime,jdbcType=DATE

再次執行測試方法,日誌中createTime欄位的值如下。

2019-07-02(Date)

再次修改createTime指定的jdbcType型別為TIME。

createTime,jdbcType=TIME

再次執行測試方法,發現報如下錯誤:

MyBatis從入門到精通(四):MyBatis XML方式的基本用法之增刪改

報錯的原因是,資料庫中的欄位型別為datetime,但是這裡只有time部分的值。

通過上面的測試,說明資料庫的datetime型別可以儲存DATE(時間部分預設為00:00:00)和TIMESTAMP這兩種型別的時間,不能儲存TIME型別的時間。

1.2 返回主鍵值(JDBC方式)

在1.1的例子中,新增完資料,我們並沒有拿到資料庫中自增的id值,但有些場景中,我們需要先拿到資料庫中自增的值,然後再處理其餘的邏輯,那麼如何拿到資料庫中的自增的id值呢?

首先,在介面SysUserMapper中新增方法如下。

/**
 * 新增使用者-使用useGeneratedKeys方式
 *
 * @param sysUser
 * @return
 */
int insertUseGeneratedKeys(SysUser sysUser);

然後開啟對應的SysUserMapper.xml,新增如下程式碼。

<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO sys_user(user_name, user_password, user_email, user_info, head_img, create_time)
    VALUES (#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
</insert>

useGeneratedKeys設定為ture後,MyBatis會使用JDBC的getGeneratedKeys()方法來取出由資料庫內部生成的主鍵。獲取到主鍵後將其賦值給keyProperty配置的id屬性。

在SysUserMapperTest測試類中新增如下程式碼,測試新增的insertUseGeneratedKeys()方法。

@Test
public void testInsertUseGeneratedKeys() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);

        SysUser sysUser = new SysUser();
        sysUser.setUserName("test1");
        sysUser.setUserPassword("123456");
        sysUser.setUserEmail("test@mybatis.tk");
        sysUser.setUserInfo("test info");
        // 正常情況下應該讀入一張圖片儲存到byte陣列中
        sysUser.setHeadImg(new byte[]{1, 2, 3});
        sysUser.setCreateTime(new Date());

        // 這裡的返回值result是執行的SQL影響的行數
        int result = sysUserMapper.insertUseGeneratedKeys(sysUser);
        // 只插入1條資料
        Assert.assertEquals(1, result);
        // 因為id回寫,所以id不為null
        Assert.assertNotNull(sysUser.getId());
    } finally {
        sqlSession.rollback();
        sqlSession.close();
    }
}

執行該測試方法,測試通過,輸出日誌如下。

DEBUG [main] - ==> Preparing: INSERT INTO sys_user(user_name, user_password, user_email, user_info, head_img, create_time) VALUES (?,?,?,?,?,?)

DEBUG [main] - ==> Parameters: test1(String), 123456(String), test@mybatis.tk(String), test info(String), java.io.ByteArrayInputStream@544a2ea6(ByteArrayInputStream), 2019-07-02 14:02:22.506(Timestamp)

DEBUG [main] - <== Updates: 1

1.3 返回主鍵值(selectKey方式)

1.2中回寫主鍵的方法只適用於支援主鍵自增的資料庫。

但有些資料庫(比如Oracle)不提供主鍵自增的功能,而是使用序列得到一個值,然後將這個值賦給id,再將資料插入到資料庫。

對於這種情況,就可以採用selectKey方式,因為selectKey方式不僅適用於不提供主鍵自增功能的資料庫,也適用於提供主鍵自增功能的資料庫。

我們先來看下MySql的例子。

首先,在介面SysUserMapper中新增如下方法。

/**
 * 新增使用者-使用selectKey方式
 *
 * @param sysUser
 * @return
 */
int insertUseSelectKey(SysUser sysUser);

然後開啟對應的SysUserMapper.xml檔案,新增如下程式碼。

<insert id="insertUseSelectKey">
    INSERT INTO sys_user(user_name, user_password, user_email, user_info, head_img, create_time)
    VALUES (#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>

和1.2相比,這裡的語句多了selectKey標籤,其中:

  • keyColumn:主鍵的資料庫列名。
  • resultType:返回值型別。
  • keyProperty:主鍵對應的屬性名。
  • order:該屬性的設定和使用的資料庫有關,如果使用的是MySql資料庫,設定的值是AFTER,因為當前記錄的主鍵值在insert語句執行成功後才能獲取到。如果使用的是Oracle資料庫,設定的值是BEFORE,因為Oracle中需要先從序列獲取值,然後將值作為主鍵插入到資料庫中。

如果資料庫是Oracle的話,語句如下(因為環境問題,以下程式碼我並未驗證,有興趣的同學可以自己試下)。

<insert id="insertUseSelectKey">
    <selectKey keyColumn="id" resultType="long" keyProperty="id" order="BEFORE">
        SELECT SEQ_ID.nextval from dual
    </selectKey>
    INSERT INTO sys_user(id,user_name, user_password, user_email, user_info, head_img, create_time)
    VALUES (#{id},#{userName},#{userPassword},#{userEmail},#{userInfo},#{headImg,jdbcType=BLOB},#{createTime,jdbcType=TIMESTAMP})
</insert>

2. update用法

假如我們現在希望通過主鍵id來更新使用者資訊,該如何操作呢?

首先,在介面SysUserMapper中新增如下方法。

/**
 * 根據主鍵更新
 *
 * @param sysUser
 * @return
 */
int updateById(SysUser sysUser);

然後,開啟對應的SysUserMapper.xml檔案,新增如下程式碼。

<update id="updateById">
    UPDATE sys_user
    SET user_name = #{userName},
        user_password = #{userPassword},
        user_email = #{userEmail},
        user_info = #{userInfo},
        head_img = #{headImg,jdbcType=BLOB},
        create_time = #{createTime,jdbcType=TIMESTAMP}
    WHERE id = #{id}
</update>

最後在SysUserMapperTest測試類中,新增如下測試方法。

@Test
public void testUpdateById() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
        SysUser sysUser = sysUserMapper.selectById(1L);

        Assert.assertEquals("admin", sysUser.getUserName());

        sysUser.setUserName("admin_test");
        sysUser.setUserEmail("admin_test@mybatis.tk");
        sysUser.setUserInfo("test info");
        // 正常情況下應該讀入一張圖片儲存到byte陣列中
        sysUser.setHeadImg(new byte[]{1, 2, 3});
        sysUser.setCreateTime(new Date());

        // 這裡的返回值result是執行的SQL影響的行數
        int result = sysUserMapper.updateById(sysUser);
        // 只更新1條資料
        Assert.assertEquals(1, result);

        sysUser = sysUserMapper.selectById(1L);
        Assert.assertEquals("admin_test", sysUser.getUserName());
        Assert.assertEquals("admin_test@mybatis.tk", sysUser.getUserEmail());
    } finally {
        sqlSession.rollback();
        sqlSession.close();
    }
}

執行測試方法,測試通過,輸出的部分日誌如下。

DEBUG [main] - ==> Preparing: UPDATE sys_user SET user_name = ?, user_password = ?, user_email = ?, user_info = ?, head_img = ?, create_time = ? WHERE id = ?

DEBUG [main] - ==> Parameters: admin_test(String), 123456(String), admin_test@mybatis.tk(String), test info(String), java.io.ByteArrayInputStream@78186a70(ByteArrayInputStream), 2019-07-02 14:57:34.792(Timestamp), 1(Long)

DEBUG [main] - <== Updates: 1

3. delete用法

假如我們現在希望通過主鍵id來刪除使用者資訊,該如何操作呢?

首先,在介面SysUserMapper中新增如下方法。

/**
* 根據主鍵刪除
*
* @param id
* @return
*/
int deleteById(Long id);

/**
* 根據物件的主鍵刪除
*
* @param sysUser
* @return
*/
int deleteBySysUser(SysUser sysUser);

然後,開啟對應的SysUserMapper.xml檔案,新增如下程式碼。

<delete id="deleteById">
    DELETE FROM sys_user WHERE id = #{id}
</delete>
<delete id="deleteBySysUser">
    DELETE FROM sys_user WHERE id = #{id}
</delete>

最後在SysUserMapperTest測試類中,新增如下測試方法。

@Test
public void testDeleteById() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
        SysUser sysUser = sysUserMapper.selectById(1L);
        Assert.assertNotNull(sysUser);

        // 這裡是直接根據id刪除
        int result = sysUserMapper.deleteById(1L);
        // 只刪除1條資料
        Assert.assertEquals(1, result);

        Assert.assertNull(sysUserMapper.selectById(1L));

        SysUser sysUser2 = sysUserMapper.selectById(1001L);
        Assert.assertNotNull(sysUser2);

        // 這裡是根據物件的id屬性刪除
        Assert.assertEquals(1, sysUserMapper.deleteBySysUser(sysUser2));

        Assert.assertNull(sysUserMapper.selectById(1001L));
    } finally {
        sqlSession.rollback();
        sqlSession.close();
    }
}

執行測試方法,測試通過,輸出的部分日誌如下。

DEBUG [main] - ==> Preparing: DELETE FROM sys_user WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <== Updates: 1

4. 多個介面引數的用法

4.1 引數型別是基本型別

截止目前,我們定義的方法都只有1個引數,要麼是隻有1個基本型別的引數,比如selectById(Long id);。

要麼是隻有1個物件作為引數,即將多個引數合併成了1個物件。

但有些場景下,比如只有2個引數,沒有必要為這2個引數再新建一個物件,比如我們現在需要根據使用者的id和角色的狀態來獲取使用者的所有角色,那麼該如何使用呢?

首先,在介面SysUserMapper中新增如下方法。

/**
 * 根據使用者id和角色的enabled狀態獲取使用者的角色
 *
 * @param userId
 * @param enabled
 * @return
 */
List<SysRole> selectRolesByUserIdAndRoleEnabled(Long userId,Integer enabled);

然後,開啟對應的SysUserMapper.xml檔案,新增如下程式碼。

<select id="selectRolesByUserIdAndRoleEnabled" resultType="com.zwwhnly.mybatisaction.model.SysRole">
    SELECT r.id,
           r.role_name   roleName,
           r.enabled,
           r.create_by   createBy,
           r.create_time createTime
    FROM sys_user u
    INNER JOIN sys_user_role ur ON u.id = ur.user_id
    INNER JOIN sys_role r ON ur.role_id = r.id
    WHERE u.id = #{userId} AND r.enabled = #{enabled}
</select>

在SysUserMapperTest測試類中,新增如下測試方法。

@Test
public void testselectRolesByUserIdAndRoleEnabled() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
        List<SysRole> sysRoleList = sysUserMapper.selectRolesByUserIdAndRoleEnabled(1L, 1);

        Assert.assertNotNull(sysRoleList);
        Assert.assertTrue(sysRoleList.size() > 0);
    } finally {
        sqlSession.rollback();
        sqlSession.close();
    }
}

執行該測試方法,發現報如下錯誤。

MyBatis從入門到精通(四):MyBatis XML方式的基本用法之增刪改

報錯資訊中說未找到引數userId,可用的引數是[0,1,param1,param2],也就是說我們將程式碼修改為:

WHERE u.id = #{0} AND r.enabled = #{1}

或者修改為:

WHERE u.id = #{param1} AND r.enabled = #{param2}

這麼使用是可以測試通過的,不過這樣使用,程式碼閱讀起來不夠友好,因此並不推薦這麼使用。

推薦在介面方法的引數前新增@Param註解,如下所示:

/**
 * 根據使用者id和角色的enabled狀態獲取使用者的角色
 *
 * @param userId
 * @param enabled
 * @return
 */
List<SysRole> selectRolesByUserIdAndRoleEnabled(@Param("userId") Long userId, @Param("enabled") Integer enabled);

執行剛剛新增的測試方法,測試通過,輸出日誌如下:

DEBUG [main] - ==> Preparing: SELECT r.id, r.role_name roleName, r.enabled, r.create_by createBy, r.create_time createTime FROM sys_user u INNER JOIN sys_user_role ur ON u.id = ur.user_id INNER JOIN sys_role r ON ur.role_id = r.id WHERE u.id = ? AND r.enabled = ?

DEBUG [main] - ==> Parameters: 1(Long), 1(Integer)

TRACE [main] - <== Columns: id, roleName, enabled, createBy, createTime

TRACE [main] - <== Row: 1, 管理員, 1, 1, 2019-06-27 18:21:12.0

TRACE [main] - <== Row: 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0

DEBUG [main] - <== Total: 2

4.2 引數型別是物件

為了演示引數型別是物件的使用方法,我們在介面SysUserMapper中新增如下方法:

/**
 * 根據使用者id和角色的enabled狀態獲取使用者的角色
 *
 * @param user
 * @param role
 * @return
 */
List<SysRole> selectRolesByUserAndRole(@Param("user") SysUser user, @Param("role") SysRole role);

此時對應的xml中的語句為:

<select id="selectRolesByUserAndRole" resultType="com.zwwhnly.mybatisaction.model.SysRole">
    SELECT r.id,
    r.role_name   roleName,
    r.enabled,
    r.create_by   createBy,
    r.create_time createTime
    FROM sys_user u
    INNER JOIN sys_user_role ur ON u.id = ur.user_id
    INNER JOIN sys_role r ON ur.role_id = r.id
    WHERE u.id = #{user.id} AND r.enabled = #{role.enabled}
</select>

5. 原始碼

原始碼地址:https://github.com/zwwhnly/mybatis-action.git,歡迎下載。

6. 參考

劉增輝《MyBatis從入門到精通》

相關文章