zoukankan      html  css  js  c++  java
  • 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:我是一名奋斗在上海的程序员)。

  • 相关阅读:
    如何在外部获取当前A标签的ID值
    获取<a>标签值</a>的标签值及更改
    Mysql : Maximum execution time of 30 seconds exceeded
    Ajax+PHP实现的进度条--实例
    HTML控件 隐藏
    Ajax学习--理解 Ajax 及其工作原理
    XMLHttpRequest 对象属性参数参考
    七、smarty--缓存的控制
    六、smarty-缓存控制前的页面静态化原理
    Java 的 List 与 Scala 的 Seq 相互转换
  • 原文地址:https://www.cnblogs.com/zwwhnly/p/11194028.html
Copyright © 2011-2022 走看看