最近在讀劉增輝老師所著的《MyBatis從入門到精通》一書,很有收穫,於是將自己學習的過程以部落格形式輸出,如有錯誤,歡迎指正,如幫助到你,不勝榮幸!
本篇部落格主要講解MyBatis中如何使用collection標籤實現查詢結果一對多對映。
1. 使用collection標籤
需求:根據使用者id查詢使用者資訊的同時獲取使用者擁有的角色,一個使用者可以擁有1個或多個角色。
一般情況下,不建議直接修改資料庫表對應的實體類。
所以這裡我們延用之前部落格中新建的類SysUserExtend,並新增如下程式碼,如下所示:
/**
* 使用者的角色集合
*/
private List<SysRole> sysRoleList;
public List<SysRole> getSysRoleList() {
return sysRoleList;
}
public void setSysRoleList(List<SysRole> sysRoleList) {
this.sysRoleList = sysRoleList;
}
然後,我們在介面SysUserMapper中新增如下方法:
/**
* 獲取所有的使用者以及對應的所有角色
*
* @return
*/
List<SysUserExtend> selectAllUserAndRoles();
接著,在對應的SysUserMapper.xml中新增如下程式碼:
<resultMap id="userRoleListMap" type="com.zwwhnly.mybatisaction.model.SysUserExtend" extends="sysUserMap">
<collection property="sysRoleList" columnPrefix="role_"
ofType="com.zwwhnly.mybatisaction.model.SysRole">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="enabled" column="enabled"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</collection>
</resultMap>
因為我們在前面的部落格中已經建過角色表的roleMap:
<resultMap id="roleMap" type="com.zwwhnly.mybatisaction.model.SysRole">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="enabled" column="enabled"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
所以上面的collection標籤可以簡化為:
<collection property="sysRoleList" columnPrefix="role_"
resultMap="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.roleMap">
</collection>
新建介面對應的查詢程式碼,使用上面新建的userRoleListMap,如下所示:
<select id="selectAllUserAndRoles" resultMap="userRoleListMap">
SELECT
u.id,
u.user_name,
u.user_password,
u.user_email,
u.create_time,
r.id role_id,
r.role_name role_role_name,
r.enabled role_enabled,
r.create_by role_create_by,
r.create_time role_create_time
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
</select>
最後,在SysUserMapperTest測試類中新增如下測試方法:
@Test
public void testSelectAllUserAndRoles() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
List<SysUserExtend> sysUserList = sysUserMapper.selectAllUserAndRoles();
System.out.println("使用者數:" + sysUserList.size());
for (SysUserExtend sysUser : sysUserList) {
System.out.println("使用者名稱:" + sysUser.getUserName());
for (SysRole sysRole : sysUser.getSysRoleList()) {
System.out.println("角色名:" + sysRole.getRoleName());
}
}
} finally {
sqlSession.close();
}
}
執行測試程式碼,測試通過,輸出日誌如下:
DEBUG [main] - ==> Preparing: SELECT u.id, u.user_name, u.user_password, u.user_email, u.create_time, r.id role_id, r.role_name role_role_name, r.enabled role_enabled, r.create_by role_create_by, r.create_time role_create_time 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
DEBUG [main] - ==> Parameters:
TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time, role_id, role_role_name, role_enabled, role_create_by, role_create_time
TRACE [main] - <== Row: 1, admin, 123456, admin@mybatis.tk, 2019-06-27 18:21:07.0, 1, 管理員, 1, 1, 2019-06-27 18:21:12.0
TRACE [main] - <== Row: 1, admin, 123456, admin@mybatis.tk, 2019-06-27 18:21:07.0, 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0
TRACE [main] - <== Row: 1001, test, 123456, test@mybatis.tk, 2019-06-27 18:21:07.0, 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0
DEBUG [main] - <== Total: 3
使用者數:2
使用者名稱:admin
角色名:管理員
角色名:普通使用者
使用者名稱:test
角色名:普通使用者
2. MyBatis合併規則
觀察上面的日誌,我們的Sql語句查詢到了3條資料,在資料庫查詢的話,也是返回如下的資料:
但經過MyBatis配置的對映到,最後合併為了2個使用者,其中第1個使用者包含了2個角色,第2個使用者包含了1個角色,那麼MyBatis是根據什麼規則合併的呢?
MyBatis在處理結果的時候,會判斷結果是否相同,如果是相同的結果,則只會保留第一個結果,所以關鍵點就是MyBatis如何判斷結果是否相同。
判斷結果是否相同時,最簡單的情況就是在對映配置中至少有1個id標籤,上面使用的sysUserMap就配置了id標籤:
<id property="id" column="id"/>
一般情況下,id標籤配置的欄位為表的主鍵,如果是聯合主鍵,可以配置多個id標籤。
id標籤的作用就是在巢狀的對映配置時判斷資料是否相同,當配置id標籤時,MyBatis只需要逐條比較所有資料中id標籤配置的欄位值是否相同即可。
也可以不配置id標籤,將上面的程式碼修改為:
<result property="id" column="id"/>
使用result不會影響查詢結果,但是此時MyBatis就要對所有欄位進行比較,因此當欄位數為M時,如果查詢結果有N條,就需要比較M*N次,如果配置了id標籤,只需要比較N次即可,所以要儘可能的配置id標籤。
結合上面的例子,因為Sql的查詢結果中,前2條資料中使用者的id是相同的,所以會合併為1個使用者,所以最終的結果是2個使用者。
為了更清楚的理解id標籤的作用,我們將sysUserMap臨時修改為:
<resultMap id="sysUserMap" type="com.zwwhnly.mybatisaction.model.SysUser">
<id property="userPassword" column="user_password"/>
<result property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="userEmail" column="user_email"/>
<result property="userInfo" column="user_info"/>
<result property="headImg" column="head_img" jdbcType="BLOB"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
執行測試方法,輸出的部分日誌如下:
使用者數:1
使用者名稱:admin
角色名:管理員
角色名:普通使用者
因為3個使用者的密碼都是123456,所以查詢到的3條資料只保留了第一個使用者admin,包含了2個角色。
有的同學也許會問,為什麼不是擁有3個角色呢?
這是因為MyBatis會對巢狀查詢的每一級物件都進行屬性比較,MyBatis會先比較頂層的物件,如果SysUser部分相同,就繼續比較SysRole部分,如果SysRole不同,就會增加一個SysRole,如果相同就保留前一個。
如果SysRole還有下一級,依次按照規則去比較。
上面的“普通使用者”角色重複了,所以只保留了前1個,導致最終的結果中只包含2個角色而不是3個。
3. 原始碼及參考
原始碼地址:https://github.com/zwwhnly/mybatis-action.git,歡迎下載。
劉增輝《MyBatis從入門到精通》
4. 最後
打個小廣告,歡迎掃碼關注微信公眾號:「申城異鄉人」,不定期分享Java技術乾貨,讓我們一起進步。