最近在讀劉增輝老師所著的《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
再次執行測試方法,發現報如下錯誤:
報錯的原因是,資料庫中的欄位型別為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();
}
}
執行該測試方法,發現報如下錯誤。
報錯資訊中說未找到引數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從入門到精通》