MyBatis高级结果映射
本篇文章主要介绍
- association和collection标签的用法
- 嵌套结果映射和嵌套查询映射
介绍-一对一映射
一对一映射因为不需要考虑是否存在重复数据,使用简单,可以直接使用MyBatis自动映射。
实践-自动映射
要求一个用户对应一个角色,类定义如下
public class SysUser implements Serializable {
private static final long serialVersionUID = 854674092994608168L;
.....
private SysRole role;
......
}
自动映射就是MyBatis将查询的结果通过别名映射到返回的对象上。复杂的属性映射如:将role.role_name映射给role.roleName上;MyBatis会先查找role属性,如果存在role属性就创建role对象,然后再role对象中继续查找roleName,将role_name的值绑定到role对象的roleName属性上。
mapper接口
/**
* 根据用户id获取用户信息和用户的角色信息
* @param id 用户id
* @return 用户和角色信息
*/
SysUser selectUserAndRoleById(Long id);
xml,注意其中role属性的roleName字段,我用的是数据库字段,role_name;其他字段都是驼峰规则,直接对应role对象的属性字段
<select id="selectUserAndRoleById" resultType="tk.mybatis.simple.model.SysUser">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
sr.id "role.id",
sr.role_name "role.role_name",
sr.enabled "role.enabled",
sr.create_by "role.createBy",
sr.create_time "role.createTime"
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
inner join sys_role sr on sur.role_id = sr.id
where u.id =#{id}
</select>
测试方法:
public void selectUserAndRoleById() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
SysUser sysUser = mapper.selectUserAndRoleById(1L);
Assert.assertNotNull(sysUser);
Assert.assertNotNull(sysUser.getRole());
LOGGER.info("用户信息:{}",sysUser.toString());
} finally {
sqlSession.close();
}
}
测试结果:能够发现role对象的roleName属性也有值,表示mybatis查询的属性列,会将下划线转化为驼峰规则。因为SysRole类中也有SysUser对象,发现最终role对象中的user属性值为空。
==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time, sr.id "role.id", sr.role_name "role.role_name", sr.enabled "role.enabled", sr.create_by "role.createBy", sr.create_time "role.createTime" from sys_user u inner join sys_user_role sur on u.id = sur.user_id inner join sys_role sr on sur.role_id = sr.id where u.id =?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time, role.id, role.role_name, role.enabled, role.createBy, role.createTime
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<== Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021, role=SysRole{id=1, roleName='管理员', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}}
总结:这种通过一次查询将结果映射导不同对象的方式,被称为嵌套结果查询。
这种方式的好处是减少数据库查询次数,减轻数据库的压力,缺点是要写复杂的sql;由于要在应用服务器上将结果映射到不同类上,会增加服务器的压力。
实践-resultMap配置
该方法使用resultMap进行列名与对象属性映射,需要保证列名不可重复。
mapper,接口返回和上面的自动映射实践一致
/**
* 根据用户id获取用户信息和用户的角色信息
* @param id 用户id
* @return 用户和角色信息
*/
SysUser selectUserAndRoleById2(Long id);
xml,userRoleMap中对role对象关联的部分列名做了别名映射,因为resulMap中列名不能重复。resultMap中result标签与sql的映射关系可以看为:sql查询列名->result标签column属性名->result标签property属性->resultType类属性。
<resultMap id="userRoleMap" type="tk.mybatis.simple.model.SysUser">
<!--@Table sys_user-->
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
<result property="userPassword" column="user_password" jdbcType="VARCHAR"/>
<result property="userEmail" column="user_email" jdbcType="VARCHAR"/>
<result property="userInfo" column="user_info" jdbcType="VARCHAR"/>
<result property="headImg" column="head_img" jdbcType="OTHER"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<!--role相关属性-->
<result property="role.id" column="role_id"/>
<result property="role.roleName" column="role_name"/>
<result property="role.enabled" column="enabled"/>
<result property="role.createBy" column="create_by"/>
<result property="role.createTime" column="role_create_time" jdbcType="TIMESTAMP"/>
</resultMap>
<select id="selectUserAndRoleById2" resultMap="userRoleMap">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
sr.id role_id,
sr.role_name,
sr.enabled,
sr.create_by ,
sr.create_time role_create_time
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
inner join sys_role sr on sur.role_id = sr.id
where u.id =#{id}
</select>
测试方法:和上面自动映射的测试方法一致,只修改查询的方法为selectUserAndRoleById2即可。
测试结果:也可以查询成功。
==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time, sr.id role_id, sr.role_name, sr.enabled, sr.create_by , sr.create_time role_create_time from sys_user u inner join sys_user_role sur on u.id = sur.user_id inner join sys_role sr on sur.role_id = sr.id where u.id =?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time, role_id, role_name, enabled, create_by, role_create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<== Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021, role=SysRole{id=1, roleName='管理员', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}}
总结:这个userRoleMap使用起来十分繁琐。可以使用resultMap的继承来简化userRoleMap。
使用如下的resultMap写法,继承基础resutlMap,extends="BaseResultMap"
,也可以得到上面相同的结果。
<resultMap id="userRoleMap" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<!--role相关属性-->
<result property="role.id" column="role_id"/>
<result property="role.roleName" column="role_name"/>
<result property="role.enabled" column="enabled"/>
<result property="role.createBy" column="create_by"/>
<result property="role.createTime" column="role_create_time" jdbcType="TIMESTAMP"/>
</resultMap>
实践-association配置
association标签用于和一个复杂的类型进行关联,即用于一对一的关联配置。
mapper接口
/**
* 根据用户id获取用户信息和用户的角色信息
* @param id 用户id
* @return 用户和角色信息
*/
SysUser selectUserAndRoleById3(Long id);
xml,使用association标签来映射role属性,其中property就是SysUser中的SysRole属性role。columnPrefix表示SysRole类中属性对应的数据库列都有一个前缀。association标签内部的result标签中property指的就是SysRole类中的属性。MyBatis在映射结果时会自动使用前缀和column值得组合去sql查询的结果取值。
<resultMap id="userRoleMap3" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" javaType="tk.mybatis.simple.model.SysRole" columnPrefix="role_">
<!--role相关属性-->
<result 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"/>
</association>
</resultMap>
<select id="selectUserAndRoleById3" resultMap="userRoleMap3">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
sr.id role_id,
sr.role_name role_role_name,
sr.enabled role_enabled,
sr.create_by role_create_by,
sr.create_time role_create_time
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
inner join sys_role sr on sur.role_id = sr.id
where u.id =#{id}
</select>
association标签包含以下属性:
- property:对应被配置一对一属性对应实体类中的属性名,必填
- javaType:对应被配置一对一属性对应的Java类型
- resultMap:可以直接使用现有的resultMap
- columnPrefix:查询的列的前缀,配置前缀后,在子标签配置result的column时可以省略前缀
association标签中的resultMap可以使用现有的配置,例如上面的属性类型是SysRole,我们就可以使用SysRole对应的baseMap。如下的配置,也可以成功运行。
<resultMap id="userRoleMap3" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" javaType="tk.mybatis.simple.model.SysRole" columnPrefix="role_" resultMap="tk.mybatis.simple.mapper.SysRoleMapper.BaseResultMap">
</association>
</resultMap>
实践-association嵌套查询
除了上面的关联嵌套结果映射查询,还有一种利用简单的sql通过多次查询转化为我们需要的结果,这种方式与根据业务逻辑手动执行多次sql的方式类似。
association标签的嵌套查询常用的属性如下:
- select:查询标签的id,MyBatis会额外执行这个查询获取嵌套对象的结果
- column:列名,将主查询种的列的结果作为嵌套查询的参数,配置方式如
column={prop1=col1,prop2=col2}
,prop1和pro2将作为嵌套查询的参数 - fetchType:数据加载方式,可选值为lazy和eager,这个配置会覆盖全局的lazyLoadingEnabled配置
先在SysRoleMapper中添加一个通过主键查数据的方法
/**
* 通过主键查角色
* @param id 主键
* @return 角色
*/
SysRole queryById(Long id);
在SysUserMapper中添加一个接口
/**
* 通过association嵌套查询获取用户信息
* @param id 用户主键
* @return 用户和角色信息
*/
SysUser selectUserAndRoleByIdSelect(Long id);
xml,对resultMap中的association标签添加一个column属性,该属性中的{id=role_id}
表示select查询标签的查询参数id取值来自当前查询返回的role_id值。所以sql中必须要返回一个名为role_id的字段。
即column属性中等号左边的表示select标签的参数,等号右边的表示来源的值。该参数也可以为多个,如{id=role_id,name=role_name}
。
<resultMap id="userRoleMapSelect" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" javaType="tk.mybatis.simple.model.SysRole"
column="{id=role_id}" select="tk.mybatis.simple.mapper.SysRoleMapper.queryById">
</association>
</resultMap>
<select id="selectUserAndRoleByIdSelect" resultMap="userRoleMapSelect">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
sur.role_id
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
where u.id =#{id}
</select>
测试方法和上面的都是类似的,测试结果如下。执行了两次sql。
==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time, sur.role_id from sys_user u inner join sys_user_role sur on u.id = sur.user_id where u.id =?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time, role_id
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1
====> Preparing: SELECT r.id, r.role_name, r.enabled, r.create_by, r.create_time from sys_role r where r.id = ?
====> Parameters: 1(Long)
<==== Columns: id, role_name, enabled, create_by, create_time
<==== Row: 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<==== Total: 1
<== Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021, role=SysRole{id=1, roleName='管理员', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}}
上面这种嵌套查询,想要在需要role属性的时候再去做查询,这就需要添加association的加载标签和修改mybatis-config.xml中的aggressiveLazyLoading值。
<resultMap id="userRoleMapSelect" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" javaType="tk.mybatis.simple.model.SysRole"
column="{id=role_id}" select="tk.mybatis.simple.mapper.SysRoleMapper.queryById" fetchType="lazy">
</association>
</resultMap>
<settings>
<setting name="logImpl" value="LOG4J"/>
<!-- 开启数据库列驼峰转化-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
aggressiveLazyLoading属性的含义是:当该参数设置为true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载,反之,每种属性都将按需加载。
当我们需要在出发某些方法时,将所有的数据加载进来,aggressiveLazyLoading值为false时,我们可以使用lazyLoadTriggerMethods,该参数的含义是,当调用配置中的方法时,加载全部的延迟加载数据。
<settings>
<setting name="logImpl" value="LOG4J"/>
<!-- 开启数据库列驼峰转化-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
介绍-一对多映射
实践-collection嵌套结果映射
和association类似,集合嵌套结果映射就是指通过一次sql查询所有结果,然后通过配置的结果映射,将数据映射到不同的对象中。
一个用户有多个角色,一个角色有多种权限。
SysUser2定义,和SysUser的区别在于存储用户角色用List集合存储。
private List<SysRole> roleList;
Mapper接口
/**
* 查询所有用户对应的角色信息
* @return 用户信息
*/
List<SysUser2> selectAllUserAndRoles();
xml写法
1.第一种自定义map,需要注意collection标签中的ofType,ofType值表示property值list存储的类型。
<resultMap id="userRoleListMap" type="tk.mybatis.simple.model.SysUser2">
<!--@Table sys_user-->
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
<result property="userPassword" column="user_password" jdbcType="VARCHAR"/>
<result property="userEmail" column="user_email" jdbcType="VARCHAR"/>
<result property="userInfo" column="user_info" jdbcType="VARCHAR"/>
<result property="headImg" column="head_img" jdbcType="OTHER"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<collection property="roleList" ofType="tk.mybatis.simple.model.SysRole"
columnPrefix="role_">
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="roleName" column="role_name" jdbcType="VARCHAR"/>
<result property="enabled" column="enabled" jdbcType="INTEGER"/>
<result property="createBy" column="create_by" jdbcType="INTEGER"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</collection>
</resultMap>
2.第二种map,和上面的区别在于将SysUser2的字段用extends来继承,roleList里的字段在collection标签中用resultMap来存储。
<resultMap id="userRoleListMap2" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser2">
<collection property="roleList"
columnPrefix="role_" resultMap="tk.mybatis.simple.mapper.SysRoleMapper.BaseResultMap">
</collection>
</resultMap>
查询的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,
sr.id role_id,
sr.role_name role_role_name,
sr.enabled role_enabled,
sr.create_by role_create_by,
sr.create_time role_create_time
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
inner join sys_role sr on sur.role_id = sr.id
</select>
测试方法
public void selectAllUserAndRoles() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
List<SysUser2> sysUserList = mapper.selectAllUserAndRoles();
for (int i = 0; i < sysUserList.size(); i++) {
LOGGER.info("第{}个{}",i+1,sysUserList.get(i));
}
} finally {
sqlSession.close();
}
}
测试结果,用上面两种resultMap执行结果一致
==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time, sr.id role_id, sr.role_name role_role_name, sr.enabled role_enabled, sr.create_by role_create_by, sr.create_time role_create_time from sys_user u inner join sys_user_role sur on u.id = sur.user_id inner join sys_role sr on sur.role_id = sr.id
==> Parameters:
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time, role_id, role_role_name, role_enabled, role_create_by, role_create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<== Row: 1001, test, 123456, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:58:32.0, 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0
<== Total: 3
第1个SysUser2{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021, roleList=[SysRole{id=1, roleName='管理员', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}, SysRole{id=2, roleName='普通用户', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}]}
第2个SysUser2{id=1001, userName='test', userPassword='123456', userEmail='test@mybatis.tk', userInfo='测试用户', headImg=null, createTime=Thu Jan 28 21:58:32 CST 2021, roleList=[SysRole{id=2, roleName='普通用户', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}]}
总结:sql查出来的结果有3条,经过MyBatis对collection数据处理后,变成了两条。
MyBatis在处理结果的时候,会判断结果是否相同,如果是相同的结果,则只会保留第一个结果。MyBatis如何判断结果是否相同呢?
- 最简单的情况就是在外层映射配置文件中至少有一个id标签,即
<id property="id" column="id" jdbcType="INTEGER"/>
。id标签配置的字段为表的主键,因为MyBatis的resultMap只用于配置结果如何映射,并不知道这个表具体如何。id的唯一作用就是在嵌套的映射配置时判断数据是否相同,当配置id标签时,MyBatis只需要逐条比较所有数据中id标签配置的字段值是否相同即可。在配置嵌套结果查询时,配置id标签可以提高处理效率。
为了验证上面id标签的作用,我们修改resultMap的值,将id标签的值换为userPassword,执行测试方法。
<id property="userPassword" column="user_password" jdbcType="VARCHAR"/>
测试结果:三条记录的密码都是123456,按照id标签配置,这三条数据会合并成一条数据。发现只保留了查询的第一条admin用户,角色信息只有两条,因为普通用户这个角色重复了。
==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time, sr.id role_id, sr.role_name role_role_name, sr.enabled role_enabled, sr.create_by role_create_by, sr.create_time role_create_time from sys_user u inner join sys_user_role sur on u.id = sur.user_id inner join sys_role sr on sur.role_id = sr.id
==> Parameters:
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time, role_id, role_role_name, role_enabled, role_create_by, role_create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<== Row: 1001, test, 123456, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:58:32.0, 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0
<== Total: 3
第1个SysUser2{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021, roleList=[SysRole{id=1, roleName='管理员', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}, SysRole{id=2, roleName='普通用户', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}]}
注意:如果没有配置id标签,MyBatis就会把resultMap中配置的所有result字段进行比较,如果所有的字段值相同就合并。在嵌套结果配置id属性时,如果查询语句中没有查询id属性配置的列,就会导致id对应的值为null。这种情况下,所有值的id都相同,因此会使嵌套的集合中只有一条数据。所以在配置id列时,查询语句中必须包含该列。
现在需要将一个角色对应多个权限,所以需要在角色的resultmap中配置权限字段。
SysPrivilege.xml,指定权限类与表对应的基础BaseResultMap
<resultMap id="BaseResultMap" type="tk.mybatis.simple.model.SysPrivilege">
<!--@Table sys_privilege-->
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="privilegeName" column="privilege_name" jdbcType="VARCHAR"/>
<result property="privilegeUrl" column="privilege_url" jdbcType="VARCHAR"/>
</resultMap>
SysRole类添加存储角色包含的权限列表
private List<SysPrivilege> privilegeList;
SysRoleMapper.xml,一个角色对应多个权限,配置collection,并在collection标签中添加resultMap属性绑定SysPrivilegeMapper中的BaseResultMap的值
<resultMap id="rolePrivilegeListMap" extends="BaseResultMap"
type="tk.mybatis.simple.model.SysRole">
<collection property="privilegeList" columnPrefix="privilege_"
resultMap="tk.mybatis.simple.mapper.SysPrivilegeMapper.BaseResultMap">
</collection>
</resultMap>
SysUserMapper.xml,将用户一对多角色配置的collection中resultMap指定为上面配置在SysRoleMapper.xml中的rolePrivilegeListMap
<resultMap id="userRoleListMap3" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser2">
<collection property="roleList"
columnPrefix="role_" resultMap="tk.mybatis.simple.mapper.SysRoleMapper.rolePrivilegeListMap">
</collection>
</resultMap>
最终sql的写法:
select u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
sr.id role_id,
sr.role_name role_role_name,
sr.enabled role_enabled,
sr.create_by role_create_by,
sr.create_time role_create_time,
sp.id role_privilege_id,
sp.privilege_name role_privilege_privilege_name,
sp.privilege_url role_privilege_privilege_url
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
inner join sys_role sr on sur.role_id = sr.id
inner join sys_role_privilege srp on sur.role_id = srp.role_id
inner join sys_privilege sp on srp.privilege_id = sp.id
需要注意的是,我们嵌套使用了collection标签,每一个collection标签上都有一个column_prefix字段。SysUserMapper中第一层嵌套是role_
,所以在绑定的resultMap(rolePrivilegeListMap)的所有查询出的字段都要加上role_
;rolePrivilegeListMap中的resultMap也有一层嵌套privilege_
,所以绑定的resultMap(SysPrivilegeMapper.BaseResultMap)的所有查询出来的字段都要加上privilege_
;前缀的叠加顺序是从外到内的。
测试输出结果:
==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time, sr.id role_id, sr.role_name role_role_name, sr.enabled role_enabled, sr.create_by role_create_by, sr.create_time role_create_time, sp.id role_privilege_id, sp.privilege_name role_privilege_privilege_name, sp.privilege_url role_privilege_privilege_url from sys_user u inner join sys_user_role sur on u.id = sur.user_id inner join sys_role sr on sur.role_id = sr.id inner join sys_role_privilege srp on sur.role_id = srp.role_id inner join sys_privilege sp on srp.privilege_id = sp.id
==> Parameters:
<== Columns: id, user_name, user_password, user_email, user_info, head_img, 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
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1, 管理员, 1, 1, 2021-01-28 21:58:32.0, 1, 用户管理, /users
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1, 管理员, 1, 1, 2021-01-28 21:58:32.0, 3, 系统日志, /logs
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1, 管理员, 1, 1, 2021-01-28 21:58:32.0, 2, 角色管理, /roles
<== Row: 1001, test, 123456, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:58:32.0, 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0, 4, 人员维护, /persons
<== Row: 1001, test, 123456, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:58:32.0, 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0, 5, 单位维护, /companies
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0, 4, 人员维护, /persons
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0, 5, 单位维护, /companies
<== Total: 7
第1个SysUser2{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021, roleList=[SysRole{id=1, roleName='管理员', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null, privilegeList=[SysPrivilege{id=1, privilegeName='用户管理', privilegeUrl='/users'}, SysPrivilege{id=3, privilegeName='系统日志', privilegeUrl='/logs'}, SysPrivilege{id=2, privilegeName='角色管理', privilegeUrl='/roles'}]}, SysRole{id=2, roleName='普通用户', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null, privilegeList=[SysPrivilege{id=4, privilegeName='人员维护', privilegeUrl='/persons'}, SysPrivilege{id=5, privilegeName='单位维护', privilegeUrl='/companies'}]}]}
第2个SysUser2{id=1001, userName='test', userPassword='123456', userEmail='test@mybatis.tk', userInfo='测试用户', headImg=null, createTime=Thu Jan 28 21:58:32 CST 2021, roleList=[SysRole{id=2, roleName='普通用户', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null, privilegeList=[SysPrivilege{id=4, privilegeName='人员维护', privilegeUrl='/persons'}, SysPrivilege{id=5, privilegeName='单位维护', privilegeUrl='/companies'}]}]}
总结:一个复杂的映射是由一个基本的映射配置组成。通常情况下,如果要配置一个复杂的映射,要从基础映射开始配置,每增加一些配置就进行对应的测试。
实践-collection嵌套select查询
一个用户对应多个角色,一个角色对应多个权限。
1.先进行一个角色查权限
PrivilegeMapper中添加一个通过角色id查询权限的方法
/**
* 通过角色id查询权限信息
* @param roleId 角色id
* @return 权限信息
*/
List<SysPrivilege> selectByRoleId(Long roleId);
PrivilegeMapper.xml
<select id="selectByRoleId" resultMap="BaseResultMap">
select p.id, p.privilege_url, p.privilege_name
from sys_privilege p
inner join sys_role_privilege srp on p.id = srp.privilege_id
where srp.role_id = #{roleId}
</select>
2.角色中定义一个resultMap来利用上面的通过角色id查询权限的select
SysRoleMapper.xml,其中collection标签中:property表示属性名,fetchType表示子查询加载方式,column中{}表示可以有多个字段对应的条件,其中=左边是子查询的参数,右边是当前查询列字段;select表示子查询。
<resultMap id="rolePrivilegeListMap2" extends="BaseResultMap"
type="tk.mybatis.simple.model.SysRole">
<collection property="privilegeList"
fetchType="lazy"
column="{roleId = id}"
select="tk.mybatis.simple.mapper.SysPrivilegeMapper.selectByRoleId">
</collection>
</resultMap>
3.角色mapper中定义一个查询利用resultMap
<select id="selectRoleByUserId" resultMap="rolePrivilegeListMap2">
select r.id, r.role_name, r.enabled, r.create_by, r.create_time from sys_role r
inner join sys_user_role sur on r.id = sur.role_id
where sur.user_id = #{userId}
</select>
4.在用户mapper中定义一个resultMap,利用上面通过用户id查角色的select方法
SysUserMapper.xml
<resultMap id="userRoleListMapSelect" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser2">
<collection property="roleList"
column="{userId =id}"
fetchType="lazy"
select="tk.mybatis.simple.mapper.SysRoleMapper.selectRoleByUserId">
</collection>
</resultMap>
SysUserMapper中的接口
/**
* 查询指定用户角色信息
* @param id 用户主键
* @return 角色信息
*/
SysUser2 selectAllUserAndRolesSelect(Long id);
查询方法
<select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
select
id, user_name, user_password, user_email, user_info, head_img, create_time
from mybatis.sys_user
where id = #{id}
</select>
测试方法:
public void selectAllUserAndRolesSelect() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
SysUser2 sysUser = mapper.selectAllUserAndRolesSelect(1L);
LOGGER.info("用户名:{}", sysUser.getUserName());
for (SysRole sysRole : sysUser.getRoleList()) {
LOGGER.info("角色名:{}",sysRole.getRoleName());
for (SysPrivilege sysPrivilege : sysRole.getPrivilegeList()) {
LOGGER.info("权限名:{}",sysPrivilege.getPrivilegeName());
}
}
} finally {
sqlSession.close();
}
}
测试结果:当执行selectAllUserAndRolesSelect方法后,会直接查询用户信息;因为使用了懒加载,当调用了user.getRoleList()方法时,MyBatis执行了第一层的嵌套查询,查询出了该用户的两个角色。对这两个角色进行遍历获取对应的权限信息,因为已经有两个角色,当遍历角色调用sysRole.getPrivilegeList()后就查询角色的权限信息。
需要注意的是,之所以能根据需要查询数据,除了和fetchType有关,还和全局的aggressiveLazyLoading属性有关,这个属性在介绍association时被配置成了false,所以才会起到按需加载的作用。
==> Preparing: select id, user_name, user_password, user_email, user_info, head_img, create_time from mybatis.sys_user where id = ?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0
<== Total: 1
用户名:admin
==> Preparing: select r.id, r.role_name, r.enabled, r.create_by, r.create_time from sys_role r inner join sys_user_role sur on r.id = sur.role_id where sur.user_id = ?
==> Parameters: 1(Long)
<== Columns: id, role_name, enabled, create_by, create_time
<== Row: 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<== Row: 2, 普通用户, 1, 1, 2021-01-28 21:58:32.0
<== Total: 2
角色名:管理员
==> Preparing: select p.id, p.privilege_url, p.privilege_name from sys_privilege p inner join sys_role_privilege srp on p.id = srp.privilege_id where srp.role_id = ?
==> Parameters: 1(Long)
<== Columns: id, privilege_url, privilege_name
<== Row: 1, /users, 用户管理
<== Row: 3, /logs, 系统日志
<== Row: 2, /roles, 角色管理
<== Total: 3
权限名:用户管理
权限名:系统日志
权限名:角色管理
角色名:普通用户
==> Preparing: select p.id, p.privilege_url, p.privilege_name from sys_privilege p inner join sys_role_privilege srp on p.id = srp.privilege_id where srp.role_id = ?
==> Parameters: 2(Long)
<== Columns: id, privilege_url, privilege_name
<== Row: 4, /persons, 人员维护
<== Row: 5, /companies, 单位维护
<== Total: 2
权限名:人员维护
权限名:单位维护
介绍-鉴别器映射(待完善)
…