本篇博客主要讲解使用collection标签实现嵌套查询的方法。

1. 需求升级

在上篇博客中,我们实现了需求:根据用户id查询用户信息的同时获取用户拥有的角色。

因为角色可以拥有多个权限,所以本篇博客我们升级需求为:根据用户id查询用户信息的同时获取用户拥有的角色以及角色包含的权限。

2. 实现方式

因为我们需要使用到权限表的映射,所以我们需要先在SysPrivilegeMapper.xml中添加如下映射:

<resultMap id="sysPrivilegeMap" type="com.zwwhnly.mybatisaction.model.SysPrivilege">
<id property="id" column="id"/>
<result property="privilegeName" column="privilege_name"/>
<result property="privilegeUrl" column="privilege_url"/>
</resultMap>

一般情况下不建议修改数据库表对应的实体类,所以这里我们新建类SysRoleExtend,让它继承SysRole类,并添加如下字段:

package com.zwwhnly.mybatisaction.model;

import java.util.List;

public class SysRoleExtend extends SysRole {
/**
* 角色包含的权限列表
*/
private List<SysPrivilege> sysPrivilegeList; public List<SysPrivilege> getSysPrivilegeList() {
return sysPrivilegeList;
} public void setSysPrivilegeList(List<SysPrivilege> sysPrivilegeList) {
this.sysPrivilegeList = sysPrivilegeList;
}
}

然后在SysRoleMapper.xml中新建如下映射:

<resultMap id="rolePrivilegeListMap" extends="roleMap"
type="com.zwwhnly.mybatisaction.model.SysRoleExtend">
<collection property="sysPrivilegeList" columnPrefix="privilege_"
resultMap="com.zwwhnly.mybatisaction.mapper.SysPrivilegeMapper.sysPrivilegeMap"/>
</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>

com.zwwhnly.mybatisaction.mapper.SysPrivilegeMapper.sysPrivilegeMap就是我们刚刚在SysPrivilegeMapper.xml中新建的映射sysPrivilegeMap。

然后,需要将上篇博客中的userRoleListMap修改为:

<resultMap id="userRoleListMap" type="com.zwwhnly.mybatisaction.model.SysUserExtend" extends="sysUserMap">
<collection property="sysRoleList" columnPrefix="role_"
resultMap="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.rolePrivilegeListMap">
</collection>
</resultMap>

并且要修改上篇博客中id为selectAllUserAndRoles的select标签代码,因为要关联角色权限关系表和权限表:

<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,
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表的列名的别名前缀为role_privilege_,这是因为userRoleListMap中collection的columnPrefix属性为role_,并且指定的com.zwwhnly.mybatisaction.mapper.SysRoleMapper.rolePrivilegeListMap中collection的columnPrefix属性为privilege_,所以这里的前缀需要叠加,就变成了role_privilege_

3. 单元测试

修改上篇博客中建的测试方法testSelectAllUserAndRoles()代码为:

@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 (SysRoleExtend sysRoleExtend : sysUser.getSysRoleList()) {
System.out.println("角色名:" + sysRoleExtend.getRoleName());
for (SysPrivilege sysPrivilege : sysRoleExtend.getSysPrivilegeList()) {
System.out.println("权限名:" + sysPrivilege.getPrivilegeName());
}
}
}
} 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, 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

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, role_privilege_id, role_privilege_privilege_name, role_privilege_privilege_url

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, 1, 用户管理, /users

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, 2, 角色管理, /roles

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, 3, 系统日志, /logs

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, 4, 人员维护, /persons

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, 5, 单位维护, /companies

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, 4, 人员维护, /persons

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, 5, 单位维护, /companies

DEBUG [main] - <== Total: 7

用户数:2

用户名:admin

角色名:管理员

权限名:用户管理

权限名:角色管理

权限名:系统日志

角色名:普通用户

权限名:人员维护

权限名:单位维护

用户名:test

角色名:普通用户

权限名:人员维护

权限名:单位维护

从日志可以看出,不仅查询出了用户拥有的角色信息,也查询出了角色包含的权限信息。

4. 延迟加载

有的同学可能会说,返回的角色信息和权限信息我不一定用啊,每次关联这么多表查询一次数据库,好影响性能啊,能不能在我使用到角色信息即获取sysRoleList属性时再去数据库查询呢?答案当然是能,那么如何实现呢?

实现延迟加载需要使用collection标签的fetchType属性,该属性有lazy和eager两个值,分别代表延迟加载和积极加载。

由于需要根据角色Id获取该角色对应的所有权限信息,所以我们要先在SysPrivilegeMapper.xml中定义如下查询:

<select id="selectPrivilegeByRoleId" resultMap="sysPrivilegeMap">
SELECT p.*
FROM sys_privilege p
INNER JOIN sys_role_privilege rp ON rp.privilege_id = p.id
WHERE rp.role_id = #{roleId}
</select>

然后在SysRoleMapper.xml中添加如下查询:

<resultMap id="rolePrivilegeListMapSelect" extends="roleMap"
type="com.zwwhnly.mybatisaction.model.SysRoleExtend">
<collection property="sysPrivilegeList" fetchType="lazy"
column="{roleId=id}"
select="com.zwwhnly.mybatisaction.mapper.SysPrivilegeMapper.selectPrivilegeByRoleId"/>
</resultMap>
<select id="selectRoleByUserId" resultMap="rolePrivilegeListMapSelect">
SELECT
r.id,
r.role_name,
r.enabled,
r.create_by,
r.create_time
FROM sys_role r
INNER JOIN sys_user_role ur ON ur.role_id = r.id
WHERE ur.user_id = #{userId}
</select>

上面的column="{roleId=id}"中,roleId指的是select指定的方法selectPrivilegeByRoleId的参数,id指的是查询selectRoleByUserId中查询出的角色id。

然后在SysUserMapper.xml中添加如下查询:

<resultMap id="userRoleListMapSelect" extends="sysUserMap"
type="com.zwwhnly.mybatisaction.model.SysUserExtend">
<collection property="sysRoleList" fetchType="lazy"
select="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleByUserId"
column="{userId=id}"/>
</resultMap>
<select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
SELECT
u.id,
u.user_name,
u.user_password,
u.user_email,
u.create_time
FROM sys_user u
WHERE u.id = #{id}
</select>

上面的column="{userId=id}"中,userId指的是select指定的方法selectRoleByUserId的参数,id指的是查询selectAllUserAndRolesSelect中查询出的用户id。

然后,在SysUserMapper接口中,添加如下方法:

/**
* 通过嵌套查询获取指定用户的信息以及用户的角色和权限信息
*
* @param id
* @return
*/
SysUserExtend selectAllUserAndRolesSelect(Long id);

最后,在SysUserMapperTest类中添加如下测试方法:

@Test
public void testSelectAllUserAndRolesSelect() {
SqlSession sqlSession = getSqlSession(); try {
SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class); SysUserExtend sysUserExtend = sysUserMapper.selectAllUserAndRolesSelect(1L);
System.out.println("用户名:" + sysUserExtend.getUserName());
for (SysRoleExtend sysRoleExtend : sysUserExtend.getSysRoleList()) {
System.out.println("角色名:" + sysRoleExtend.getRoleName());
for (SysPrivilege sysPrivilege : sysRoleExtend.getSysPrivilegeList()) {
System.out.println("权限名:" + sysPrivilege.getPrivilegeName());
}
}
} finally {
sqlSession.close();
}
}

运行测试方法,输出日志如下:

DEBUG [main] - ==> Preparing: SELECT u.id, u.user_name, u.user_password, u.user_email, u.create_time FROM sys_user u WHERE u.id = ?

DEBUG [main] - ==> Parameters: 1(Long)

TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time

TRACE [main] - <== Row: 1, admin, 123456, admin@mybatis.tk, 2019-06-27 18:21:07.0

DEBUG [main] - <== Total: 1

用户名:admin

DEBUG [main] - ==> Preparing: SELECT r.id, r.role_name, r.enabled, r.create_by, r.create_time FROM sys_role r INNER JOIN sys_user_role ur ON ur.role_id = r.id WHERE ur.user_id = ?

DEBUG [main] - ==> Parameters: 1(Long)

TRACE [main] - <== Columns: id, role_name, enabled, create_by, create_time

TRACE [main] - <== Row: 1, 管理员, 1, 1, 2019-06-27 18:21:12.0

TRACE [main] - <== Row: 2, 普通用户, 1, 1, 2019-06-27 18:21:12.0

DEBUG [main] - <== Total: 2

角色名:管理员

DEBUG [main] - ==> Preparing: SELECT p.* FROM sys_privilege p INNER JOIN sys_role_privilege rp ON rp.privilege_id = p.id WHERE rp.role_id = ?

DEBUG [main] - ==> Parameters: 1(Long)

TRACE [main] - <== Columns: id, privilege_name, privilege_url

TRACE [main] - <== Row: 1, 用户管理, /users

TRACE [main] - <== Row: 2, 角色管理, /roles

TRACE [main] - <== Row: 3, 系统日志, /logs

DEBUG [main] - <== Total: 3

权限名:用户管理

权限名:角色管理

权限名:系统日志

角色名:普通用户

DEBUG [main] - ==> Preparing: SELECT p.* FROM sys_privilege p INNER JOIN sys_role_privilege rp ON rp.privilege_id = p.id WHERE rp.role_id = ?

DEBUG [main] - ==> Parameters: 2(Long)

TRACE [main] - <== Columns: id, privilege_name, privilege_url

TRACE [main] - <== Row: 4, 人员维护, /persons

TRACE [main] - <== Row: 5, 单位维护, /companies

DEBUG [main] - <== Total: 2

权限名:人员维护

权限名:单位维护

仔细分析上面的日志,会发现只有在使用到了角色信息和权限信息时,才执行了对应的数据库查询。

需要注意的是,延迟加载依赖于MyBatis全局配置中的aggressiveLazyLoading,在之前的博客讲解association标签时,我们已经将其配置为了false,所以这里的执行结果符合我们的预期:

<settings>
<!--其他配置-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

关于该参数的详细讲解,请查看MyBatis从入门到精通(十):使用association标签实现嵌套查询

5. 总结

使用collection标签实现嵌套查询,用到的属性总结如下:

1)select:另一个映射查询的id,MyBatis会额外执行这个查询获取嵌套对象的结果。

2)column:将主查询中列的结果作为嵌套查询的参数,配置方式如column="{prop1=col1,prop2=col2}",prop1和prop2将作为嵌套查询的参数。

3)fetchType:数据加载方式,可选值为lazy和eager,分别为延迟加载和积极加载。

4)如果要使用延迟加载,除了将fetchType设置为lazy,还需要注意全局配置aggressiveLazyLoading的值应该为false。这个参数在3.4.5版本之前默认值为ture,从3.4.5版本开始默认值改为false。

5)MyBatis提供的lazyLoadTriggerMethods参数,支持在触发某方法时直接触发延迟加载属性的查询,如equals()方法。

6. 源码及参考

源码地址:https://github.com/zwwhnly/mybatis-action.git,欢迎下载。

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

MyBatis从入门到精通(十二):使用collection标签实现嵌套查询的更多相关文章

  1. MyBatis从入门到精通(十):使用association标签实现嵌套查询

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

  2. MyBatis从入门到精通(十四):在MyBatis中使用类型处理器

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

  3. Spring+SpringMVC+MyBatis深入学习及搭建(十二)——SpringMVC入门程序(一)

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6999743.html 前面讲到:Spring+SpringMVC+MyBatis深入学习及搭建(十一)——S ...

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

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

  5. sql索引从入门到精通(十亿行数据测试报告)

    原文:sql索引从入门到精通(十亿行数据测试报告) 导读部分 --------------------------------------------------------------------- ...

  6. [OpenCV入门教程之十二】OpenCV边缘检测:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑

    http://blog.csdn.net/poem_qianmo/article/details/25560901 本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog ...

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

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

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

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

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

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

随机推荐

  1. 【shell】编译脚本

    #!/bin/bash #设置环境变量 export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export JRE_HOME=${JAVA_HOME}/ ...

  2. 一定要在commit之前做RAR备份,这样在出问题的时候,可以排除别人代码的干扰

    否则找错实在是太痛苦了,根本不知道来自哪里...而这样上面那样做,可以节省时间.

  3. Wiki上的C++哲学

    Philosophy[edit] Throughout C++'s life, its development and evolution has been informally governed b ...

  4. UILabel实现自适应宽高需要注意的地方(二)

    需求图如下所示   UILabel "上期"   距离屏幕最左边 有35px UILabel "下期"   距离屏幕最右边 有35px 进行中文字在UIlabe ...

  5. 只言片语 - cell 图片复用问题

    一. 今日做项目遇到图片复用问题,返回cell高度相同,由于网络不好出现图片复用,发现问题   Cell 图片加载方法如下: - (void)sd_setImageWithURL:(NSURL *)u ...

  6. java8计算时间差

    示例1:计算指定时间单位的时间差 import java.time.Instant;import java.time.LocalDateTime;import java.time.temporal.C ...

  7. spring boot之security

    上一节的时候,我们打开了springboot的端点,有一些数据是非常敏感的,比如/shutdown. 这一节,我们要给一些敏感信息加上权限控制. spring boot本身的security模块就很好 ...

  8. 关于Git 的管理凭据操作

    1.桌面-->2.我的电脑-->3.右击选择属性-->4.控制面板主页-->5.在用户账户和家庭安全下,选择添加或删除用户账户-->转到“主用户账户”页面-->6. ...

  9. 【转】Mysql索引最左匹配原则理解

    作者:沈杰 链接:https://www.zhihu.com/question/36996520/answer/93256153来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...

  10. Laravel ---【转】PhpStorm下Laravel代码智能提示

    [转]http://blog.csdn.net/pangchengyong0724/article/details/54706775 第一步:在项目的composer.json中添加如下一行 &quo ...