MyBatis从入门到精通(第6章):MyBatis 高级查询->6.1.2高级结果映射之一对多映射
jdk1.8、MyBatis3.4.6、MySQL数据库5.6.45、IntelliJ IDEA 2019.3.1
本章主要包含的内容为 MyBatis 的高级结果映射,主要处理数据库一对一、一对多的查询,另外就是在 MyBatis 中使用存储过程的方法,处理存储过程的入参和出参方法,最后会介绍 Java 中的枚举方法和数据库表字段的处理方法。
6.1 高级结果映射
在关系型数据库中,我们经常要处理一对一、一对多的关系。
在 RBAC 权限系统中还存在着一个用户拥有多个角色、一个角色拥有多个权限这样复杂的嵌套关系。使用已经学会的 MyBatis 技巧都可以轻松地解决这种复杂的关系。在面对这种关系的时候,我们可能要写多个方法分别查询这些数据,然后再组合到一起。这种处理方式特别适合用在大型系统上,由于分库分表,这种用法可以减少表之间的关联查询,方便系统进行扩展。
但是在一般的企业级应用中,使用 MyBatis 的高级结果映射便可以轻松地处理这种一对一、一对多的关系。
一对多映射只有两种配置方式,都是使用 collection 标签进行的,下面来看具体的介绍。
在 RBAC 权限系统中,一个用户拥有多个角色(注意,使用association 是设定的特例,限制一个用户只有一个角色),每个角色又是多个权限的集合,所以要渐进式地去实现一个 SQL,查询出所有用户和用户拥有的角色,以及角色所包含的所有权限信息的两层嵌套结果。
先来看如何实现一层嵌套的结果,为了能够存储一对多的数据,先对 SysUser 类进行修改,代码如下。
在 SysUser 类中增加 roleList 属性用于存储用户对应的多个角色。
在 UserMapper.xml 中创建 resultMap ,代码如下。
和 6.1.1.3 中的方式对比会很容易发现,此处就是把 association 改成了 collection ,然后将 property 设置为了 roleList,其他的 id 和 result 的配置都还一样。仔细想想应该不难理解,collection 用于配置一对多关系,对应的属性必须是对象中的集合类型,因此这里是 roleList。另外,resultMap 只是为了配置数据库字段和实体属性的映射关系,因此其他都一样。同时能存储一对多的数据结构肯定也能存储一对一关系,所以一对一像是一对多的一种特例。collection 支持的属性以及属性的作用和association 完全相同。
上一节中,我们逐步对 resultMap 进行了简化,在这一节,因为有了上一节的基础,因此可以大刀阔斧地对这个 resultMap 进行快速简化。首先,SysUser 中的属性可以直接通过继承 userMap 来使用 sys_user 的映射关系,其次在 RoleMapper.xml 中的 roleMap 映射包含了 sys_role 的映射关系,因此可以直接引用 roleMap,经过这两个方式的简化,最终的userRoleListMap 如下。
仿照上一节的 selectUserAndRoleById 2 方法,创建selectAllUserAndRoles 方法,代码如下。
这个方法用于查询所有用户及其对应的角色,sys_role 对应的查询列都增加了以“role_”作为前缀的别名。
在 UserMapper 接口中增加如下的对应方法。
/**
* 获取所有的用户以及对应的所有角色
*
* @return
*/
List<SysUser> selectAllUserAndRoles();
针对该方法,在 UserMapperTest 中添加如下测试。
从上图已经可以看到,第一个用户拥有两个角色,实现了一对多的查询。再来看一下测试代码输出的日志。
通过日志可以清楚地看到,SQL 执行的结果数有 3 条,后面输出的用户数是 2,也就是说本来查询出的 3 条结果经过 MyBatis 对 collection 数据的处理后,变成了两条。
提示!
在嵌套结果配置 id 属性时,如果查询语句中没有查询 id 属性配置的列,就会导致 id 对应的值为 null。
这种情况下,所有值的 id 都相同,因此会使嵌套的集合中只有一条数据。所以在配置 id 列时,查询语句中必须包含该列。
在 RBAC 权限系统中,除了一个用户对应多个角色外,每一个角色还会对应多个权限。所以在现有例子的基础上可以再增加一级,获取角色对应的所有权限。
如果在 PrivilegeMapper.xml 中没有 privilegeMap 映射配置,就在该配置文件中添加如下代码。
<resultMap id="privilegeMap" type="tk.mybatis.simple.model.SysPrivilege">
<id property="id" column="id"/>
<result property="privilegeName" column="privilege_name"/>
<result property="privilegeUrl" column="privilege_url"/>
</resultMap>
然后在 SysRole 类中添加如下属性和方法。
/**
* 角色包含的权限列表
*/
List<SysPrivilege> privilegeList; public List<SysPrivilege> getPrivilegeList() {
return privilegeList;
} public void setPrivilegeList(List<SysPrivilege> privilegeList) {
this.privilegeList = privilegeList;
}
在 RoleMapper.xml 文件中,增加如下 resultMap 配置。
<resultMap id="rolePrivilegeListMap" extends="roleMap" type="tk.mybatis.simple.model.SysRole">
<collection property="privilegeList" columnPrefix="privilege_"
resultMap="tk.mybatis.simple.mapper.PrivilegeMapper.privilegeMap"/>
</resultMap>
我们创建了角色权限映射,继承了 roleMap,嵌套了 privilegeList 属性,直接使用了 PrivilegeMapper.xml 中的 privilegeMap 。
最后还要修改 UserMapper.xml 中的 userRoleListMap ,代码如下。
完成以上步骤就配置好了一个两层嵌套的映射。为了得到权限信息,还需要修改 SQL 进行关联,代码如下。
<select id="selectAllUserAndRoles" resultMap="userRoleListMap">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
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,
p.id role_privilege_id,
p.privilege_name role_privilege_privilege_name,
p.privilege_url role_privilege_privilege_url
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
inner join sys_role_privilege rp on rp.role_id = r.id
inner join sys_privilege p on p.id = rp.privilege_id
</select>
这里要特别注意 sys_ privilege 表中列的别名,因为 sys_ privilege 嵌套在 rolePrivilegeListMap 中,而 rolePrivilegeListMap 的前缀是“role_”,所以 rolePrivilegeListMap 中 privilegeMap 的前缀就变成了“role_privilege_”。在嵌套中,这个前缀需要叠加,一定不要写错。
为了加深印象,利用上面的 rolePrivilegeListMap 实现一个查询角色和对应权限的方法。在 RoleMapper.xml 中添加如下方法。
<select id="selectAllRoleAndPrivileges" resultMap="rolePrivilegeListMap">
select
r.id,
r.role_name,
r.enabled,
r.create_by,
r.create_time,
p.id privilege_id,
p.privilege_name privilege_privilege_name,
p.privilege_url privilege_privilege_url
from sys_role r
inner join sys_role_privilege rp on rp.role_id = r.id
inner join sys_privilege p on p.id = rp.privilege_id
</select>
在这个方法中,大家需要注意 sys_ privilege 对应列的别名,请自行在 RoleMapper 中添加对应的接口,并且在 RoleMapperTest 中添加该方法的测试。
此处通过使用 rolePrivilegeListMap ,大家可以了解这样一个映射配置:它不仅可以被嵌套的配置引用,其本身也可以使用。一个复杂的映射就是由这样一个基本的映射配置组成的。通常情况下,如果要配置一个相当复杂的映射,一定要从基础映射开始配置,每增加一些配置就进行对应的测试,在循序渐进的过程中更容易发现和解决问题。
虽然 association 和 collection 标签是分开介绍的,但是这两者可以组合使用或者互相嵌套使用,也可以使用符合自己需要的任何数据结构,不需要局限于数据库表之间的关联关系。
6.1.2.2 collection 集合的嵌套查询
仍然以关联的嵌套结果中的 selectAllUserAndRoles 为基础,以上一节最后的两层嵌套结果为目标,将该方法修改为集合的嵌套查询方式。
面以自下而上的过程来实现这样一个两层嵌套的功能,并且这个自下而上的过程中的每一个方法都是一个独立可用的方法,最后的结果都是以前一个方法为基础的。把所有对象设置为延迟加载,因此每个方法都可以单独作为一个普通(没有嵌套)的查询存在。
首先在 PrivilegeMapper.xml 中添加如下方法。
<select id="selectPrivilegeByRoleId" resultMap="privilegeMap">
select p.*
from sys_privilege p
inner join sys_role_privilege rp on rp.privilege_id = p.id
where role_id = #{roleId}
</select>
这个方法通过角色 id 获取该角色对应的所有权限信息,可以在PrivilegeMapper 接口中增加相应的方法。这是一个很常见的方法,许多时候都需要这样一个方法来获取角色包含的所有权限信息。
下一步,在 RoleMapper.xml 中配置映射和对应的查询方法,代码如下。
在上面代码中要注意 column 属性配置的{roleId=id},roleId 是 select 指定方法 selectPrivilegeByRoleId 查询中的参数,id 是当前查询selectRoleByUserId 中查询出的角色 id。selectRoleByUserId 是一个只有一层嵌套的一对多映射配置,通过调用 PrivilegeMapper 的selectPrivilegeByRoleId 方法,很轻易就实现了嵌套查询的功能。针对这个方法,大家也要添加相应的接口方法进行测试。
终于要轮到顶层的用户信息了,在 UserMapper.xml 中添加如下映射和查询,代码如下。
<resultMap id="userRoleListMapSelect" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<collection property="roleList"
fetchType="lazy"
select="tk.mybatis.simple.mapper.RoleMapper.selectRoleByUserId"
column="{userId=id}"/>
</resultMap>
<select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time
from sys_user u
where u.id = #{id}
</select>
这里也需要注意,collection 的属性 column 配置为{userId=id},将当前查询用户中的 id 赋值给 userId,使用 userId 作为参数再进行selectRoleByUserId 查询。因为所有嵌套查询都配置为延迟加载,因此不存在 N+1 的问题。
在 UserMapper 接口中添加如下方法。
/**
* 通过嵌套查询获取指定用户的信息,以及用户的角色和权限信息
*
* @param id
* @return
*/
SysUser selectAllUserAndRolesSelect(Long id);
然后在 UserMapperTest 中添加相应的测试,代码如下。
@Test
public void testSelectAllUserAndRolesSelect(){
//获取 sqlSession
SqlSession sqlSession = getSqlSession();
try {
//获取 UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
SysUser user = userMapper.selectAllUserAndRolesSelect(1L);
System.out.println("用户名:" + user.getUserName());
for(SysRole role: user.getRoleList()){
System.out.println("角色名:" + role.getRoleName());
for(SysPrivilege privilege : role.getPrivilegeList()){
System.out.println("权限名:" + privilege.getPrivilegeName());
}
}
} finally {
//不要忘记关闭 sqlSession
sqlSession.close();
}
}
由于这里是多层嵌套,并且是延迟加载,因此这段测试会输出很长的日志,日志如下。
简单分析这段日志,当执行 selectAllUserAndRolesSelect 方法后,可以得到 admin 用户的信息,由于延迟加载,此时还不知道该用户有几个角色。当调用 user.getRoleList ()方法进行遍历时,MyBatis 执行了第一层的嵌套查询,查询出了该用户的两个角色。对这两个角色进行遍历获取角色对应的权限信息,因为已经有两个角色,所以分别对两个角色进行遍历时会查询两次角色的权限信息。特别需要注意的是,之所以可以根据需要查询数据,除了和 fetchType 有关,还和全局的aggressiveLazyLoading 属性有关,这个属性在介绍 association 时被配置成了 false,所以才会起到按需加载的作用。
6.1.3 鉴别器映射
鉴别器是一种很少使用的方式,在使用前一定要完全掌握,没有把握的情况下要尽可能避免使用。
===========================================================
end
MyBatis从入门到精通(第6章):MyBatis 高级查询->6.1.2高级结果映射之一对多映射的更多相关文章
- MyBatis从入门到精通(第5章):5.4 Example 介绍
jdk1.8.MyBatis3.4.6.MySQL数据库5.6.45.Eclipse Version: 2019-12 M2 (4.14.0) MyBatis从入门到精通(第5章):MyBatis代码 ...
- MyBatis从入门到精通(第9章):Spring集成MyBatis(下)
MyBatis从入门到精通(第9章):Spring集成MyBatis(下) springmvc执行流程原理 mybatis-spring 可以帮助我们将MyBatis代码无缝整合到Spring中.使 ...
- MyBatis从入门到精通(第9章):Spring集成MyBatis(中)
MyBatis从入门到精通(第9章):Spring集成MyBatis(中) 框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法.应该将应用自身的设计和具体 ...
- MyBatis从入门到精通(第9章):Spring集成MyBatis(上)
MyBatis从入门到精通(第9章):Spring集成MyBatis(上) Spring是一个为了解决企业级Web应用开发过程中面临的复杂性,而被创建的一个非常流行的轻量级框架. mybatis-sp ...
- MyBatis从入门到精通(第5章):MyBatis代码生成器
jdk1.8.MyBatis3.4.6.MySQL数据库5.6.45.Eclipse Version: 2019-12 M2 (4.14.0) MyBatis从入门到精通(第5章):MyBatis代码 ...
- MyBatis从入门到精通(第4章):MyBatis动态SQL【if、choose 和 where、set、trim】
(第4章):MyBatis动态SQL[if.choose 和 where.set.trim] MyBatis 的强大特性之一便是它的动态 SQL.MyBatis 3.4.6版本采用了功能强大的OGNL ...
- MyBatis从入门到精通:第一章实体类与Mapper.xml文件
实体类: package tk.mybatis.simple.model; public class Country { public Long getId() { return id; } publ ...
- MyBatis从入门到精通:第一章配置MyBatis
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC ...
- MyBatis从入门到精通(第3章):MyBatis注解方式的基本使用
MyBatis 注解方式就是将 SQL 语句直接写在DAO层的接口上. 在黑马录制的2018年双元视频课:\08 SSM整合案例[企业权限管理系统]\07.订单操作 有使用MyBatis注解进行多表 ...
随机推荐
- Java中的static关键字和new关键字作用介绍
一.static关键字的作用 1.可以用于修改类的成员变量.代码块和类 通过static可以将类的成员声明为静态成员,静态的成员归属于整个类,而不是属于某个对象.无论通过类还是对象访问静态成员,操作的 ...
- ACM-奇特的立方体
题目描述:奇特的立方体 任意给出8个整数,将这8个整数分别放在一个立方体的八个顶点上,要求检验每个面上的四个数之和相等这个条件能否被满足. 输入 一次输入8个整数 输出 YES或者NO YES表示可能 ...
- main:处理命令行选项
#include<iostream> #include<stdlib.h> using namespace std; int main(int argc, char** arg ...
- 《ES6标准入门》(阮一峰)--2.let 和 const 命令
1.let命令 基本用法 let只在命令所在的代码块内(花括号内)有效. for循环的计数器,就很合适使用let命令. //var var a = []; for (var i = 0; i < ...
- Web基础之Mybatis
Web基础之Mybatis 对比JdbcTempalte,mybatis才能称得上是框架,JdbcTempalte顶多算是工具类,同时,对比Hibernate,Mybatis又有更多的灵活性,算是 ...
- 17. react redux的中间件
1. redux 数据流程图 View 会派发一个 Action Action 通过 Dispatch 方法派发给 Store Store 接收到 Action 连同之前的 State 发给 Red ...
- Django xadmin图片上传与缩略图处理
基本摘要 用python django开发时,个人选中Xadmin后台管理系统框架,因为它*内置功能丰富, 不仅提供了基本的CRUD功能,还内置了丰富的插件功能.包括数据导出.书签.图表.数据添加向导 ...
- VSFTP 连接时425 Security: Bad IP connecting.报错-----解决方法
当登录FTP时候出现这个报错时.是因为PASV模式的安全检查是开启的(默认是开启的) ftp> ls227 Entering Passive Mode (172,16,101,33,35,58 ...
- part9 公用图片画廊组件拆分
1.src中创建 common 再创建 gallery.然后gallery.vue 2.build 中webpack.base.conf 中配置更短路径 module.exports {}中 reso ...
- part5 城市页面列表开发
1.配置路由 先在router文件夹中,创建一个路由.引入组件 { path: '/city', name: 'HelloCity', component: city, meta: { name: ' ...