本篇博客主要讲解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:我是一名奋斗在上海的程序员)。

MyBatis从入门到精通(十一):MyBatis高级结果映射之一对多映射的更多相关文章

  1. MyBatis从入门到精通(九):MyBatis高级结果映射之一对一映射

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解MyBatis中实现查 ...

  2. MyBatis从入门到精通(一):MyBatis入门

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. MyBatis简介 ​ 2001 ...

  3. MyBatis从入门到精通(1):MyBatis入门

    作为一个自学Java的自动化专业211大学本科生,在学习和实践过程中"趟了不少雷",所以有志于建立一个适合同样有热情学习Java技术的参考"排雷手册". 最近在 ...

  4. MyBatis从入门到精通(二):MyBatis XML方式的基本用法之Select

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. 明确需求 书中提到的需求是一个基 ...

  5. MyBatis从入门到精通(三):MyBatis XML方式的基本用法之多表查询

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. 多表查询 上篇博客中,我们示例的 ...

  6. MyBatis从入门到精通(四):MyBatis XML方式的基本用法之增删改

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. insert用法 1.1 简单的 ...

  7. MyBatis从入门到精通(五):MyBatis 注解方式的基本用法

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. @Select 注解 1.1 使 ...

  8. MyBatis从入门到精通(六):MyBatis动态Sql之if标签的用法

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解如何使用if标签生成动 ...

  9. MyBatis从入门到精通(七):MyBatis动态Sql之choose,where,set标签的用法

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解如何使用choose, ...

随机推荐

  1. 【windows】常见的系统环境变量,如%appdata%表示什么意思

    原文:[windows]常见的系统环境变量,如%appdata%表示什么意思 1.介绍 %appdata%就代表了C:Users\用户名\AppData\Roaming这个文件夹. “%”是系统变量的 ...

  2. 数据在数组中存储的顺序:小端 OR 大端模式 详解

    大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放: 小端模式,是指数据的高 ...

  3. .NET VS 自定义新建代码文件模板

    参考:http://www.cnblogs.com/fightingtong/p/3765914.html 在VS中新建文件时,可使用模板在文件中生成指定内容.只需要把IDE安装目录下的模板进行修改保 ...

  4. C#基础加强篇—委托、Lambda表达式和事件(下)

    3.事件 事件作为C#中的一种类型,为类和类的实例定义发出通知的能力,从而将事件和可执行代码捆绑在了一起.事件是对象发送的消息,以发信号通知操作的发生.操作可能是由用户交互引起的,也可能是由某些其他的 ...

  5. Python中实现switch分支结构

    Python不像C/C++,Java等有switch-case的语法.不过其这个功能,比如用Dictionary以及lambda匿名函数特性来替代实现. 实现方法分为两步: 首先,定义一个字典: 其次 ...

  6. Java实现Qt的SIGNAL-SLOT机制(保存到Map中,从而将它们关联起来,收到信号进行解析,最后反射调用)

    SIGNAL-SLOT是Qt的一大特色,使用起来十分方便.在传统的AWT和Swing编程中,我们都是为要在 监听的对象上添加Listener监听器.被监听对象中保存有Listener的列表,当相关事件 ...

  7. java之继承中的静态变量

    package Test; /** * Created by wangbin10 on 2018/7/9. * 我们知道静态变量属于类级别变量,对应每个类只有一份,类的所有实例共有一份,而成员变量则分 ...

  8. Android native进程间通信实例-binder篇之——简单的单工通信

    网上找了很多binder相关文章,大部分都是在跟踪binder实现源代码,然后再把框架代码贴出来,看着实在费力. 这篇文章从实际出发,直接用一个案例下手,后续想了解binder相关原理的话,可以参考& ...

  9. tkinter + cefpython 仿美团桌面程序

    使用js开发桌面程序目前是一个趋势,Electron是其中一个佼佼者,网上也不乏很多文章.今天主要是来讲一下cefpython. 用python的朋友,特别使用过tkinter开发过界面的,一定会觉得 ...

  10. composer使用gitlab搭建私有库

    { "repositories": [ { "type": "vcs", // 使用gitlab固定 "url": &q ...