欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

MyBatis从入门到精通(十一):MyBatis高级结果映射之一对多映射

程序员文章站 2022-06-05 20:30:23
最近在读刘增辉老师所著的《MyBatis从入门到精通》一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解MyBatis中如何使用collection标签实现查询结果一对多映射。 1. 使用collection标签 需求:根据用户id查询用 ......

最近在读刘增辉老师所著的《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从入门到精通(十一):MyBatis高级结果映射之一对多映射

但经过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. 源码及参考

源码地址:,欢迎下载。

刘增辉《mybatis从入门到精通》

4. 最后

打个小广告,欢迎扫码关注微信公众号:「申城异乡人」,不定期分享java技术干货,让我们一起进步。

MyBatis从入门到精通(十一):MyBatis高级结果映射之一对多映射