MyBatis系列(十一):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從入門到精通》

原創不易,如果覺得文章能學到東西的話,歡迎點個贊、評個論、關個注,這是我堅持寫作的最大動力。

如果有興趣,歡迎添加我的微信:zwwhnly,等你來聊技術、職場、工作等話題(PS:我是一名奮斗在上海的程序員)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM