MyBatis从入门到精通(十一):MyBatis高级结果映射之一对多映射
转载:https://www.cnblogs.com/zwwhnly/p/11194028.html
最近在读刘增辉老师所著的《MyBatis从入门到精通》一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸!
本篇博客主要讲解MyBatis中如何使用collection标签实现查询结果一对多映射。
1. 使用collection标签
需求:根据用户id查询用户信息的同时获取用户拥有的角色,一个用户可以拥有1个或多个角色。
一般情况下,不建议直接修改数据库表对应的实体类。
所以这里我们延用之前博客中新建的类SysUserExtend,并添加如下代码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 | /** * 用户的角色集合 */ private List<SysRole> sysRoleList; public List<SysRole> getSysRoleList() { return sysRoleList; } public void setSysRoleList(List<SysRole> sysRoleList) { this .sysRoleList = sysRoleList; } |
然后,我们在接口SysUserMapper中添加如下方法:
1 2 3 4 5 6 | /** * 获取所有的用户以及对应的所有角色 * * @return */ List<SysUserExtend> selectAllUserAndRoles(); |
接着,在对应的SysUserMapper.xml中添加如下代码:
1 2 3 4 5 6 7 8 9 10 | <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:
1 2 3 4 5 6 7 | <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标签可以简化为:
1 2 3 | <collection property= "sysRoleList" columnPrefix= "role_" resultMap= "com.zwwhnly.mybatisaction.mapper.SysRoleMapper.roleMap" > </collection> |
新建接口对应的查询代码,使用上面新建的userRoleListMap,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <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测试类中添加如下测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @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标签:
1 | <id property= "id" column= "id" /> |
一般情况下,id标签配置的字段为表的主键,如果是联合主键,可以配置多个id标签。
id标签的作用就是在嵌套的映射配置时判断数据是否相同,当配置id标签时,MyBatis只需要逐条比较所有数据中id标签配置的字段值是否相同即可。
也可以不配置id标签,将上面的代码修改为:
1 | <result property= "id" column= "id" /> |
使用result不会影响查询结果,但是此时MyBatis就要对所有字段进行比较,因此当字段数为M时,如果查询结果有N条,就需要比较M*N次,如果配置了id标签,只需要比较N次即可,所以要尽可能的配置id标签。
结合上面的例子,因为Sql的查询结果中,前2条数据中用户的id是相同的,所以会合并为1个用户,所以最终的结果是2个用户。
为了更清楚的理解id标签的作用,我们将sysUserMap临时修改为:
1 2 3 4 5 6 7 8 9 | <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从入门到精通》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
2018-07-16 Spark认识&环境搭建&运行第一个Spark程序
2018-07-16 Github使用之git回退到某个历史版本