手记

【MyBatis】MyBatis多表操作

MyBatis多表操作

前言

在前面的两个小节里,我们已经初步接触到MyBatis,并且通过MyBatis实现了单表的增删改查操作,但在实际开发过程中,经常遇到的是多表之间的操作,MyBatis在多表操作方面也提供非常方便的工具用于将结果集映射到对象中,这一节,我们将详细学习这一部分。

多表操作

由于本节涉及到多表操作,在前面建立的数据表明显不符合,所以这里我们需要再建立一些表以及插入一些数据

本节所使用的表以及数据均来自刘增辉老师的《MyBatis从入门到精通》

create table sys_user (  id bigint not null auto_increment comment '用户ID',
  user_name varchar(50) comment '用户名',
  user_password varchar(50) comment '密码',
  user_email varchar(50) comment '邮箱',
  create_time datetime comment '创建时间',
  primary key (id)
);alter table sys_user comment '用户表';create table sys_role (  id bigint not null auto_increment comment '角色ID',
  role_name varchar(50) comment '角色名',
  enabled int comment '有效标志',
  create_by bigint comment '创建人',
  create_time datetime comment '创建时间',
  primary key (id)
);alter table sys_role comment '角色表';create table sys_privilege (  id bigint not null  auto_increment comment '权限ID',
  privilege_name varchar(50) comment '权限名称',
  privilege_url varchar(200) comment '权限URL',
  primary key (id)
);alter table sys_privilege comment '权限表';create table sys_user_role (
  user_id bigint comment '用户ID',
  role_id bigint comment '角色ID');alter table sys_user_role comment '用户角色关联表';create table sys_role_privilege (
  role_id bigint comment '角色ID',
  privilege_id bigint comment '权限ID');alter table sys_role_privilege comment '角色权限关联表';

测试数据

insert into `sys_user`
  values
    (1, 'admin', '123456', 'admin@mybatis', '管理员', null, now()),
    (1001, 'test', '123456', 'test@mybatis', '测试用户', null, now());insert into sys_role    values
      (1, '管理员', '1', '1', now()),
      (2, '普通用户', '1', '1', now());insert into sys_user_role values (1, 1), (1, 2), (1001, 2);insert sys_privilege  values
    (1, '用户管理', '/users'),
    (2, '角色管理', '/roles'),
    (3, '系统日志', '/logs'),
    (4, '人员维护', '/persons'),
    (5, '单位维护', '/companies');insert sys_role_privilege  values (1, 1), (1, 3), (1, 2), (2, 4), (2, 5);

对应的实体类根据数据库的字段建立就好了。

关于每个表的单表操作,在前面一个小节已经研究过了,所以在这个小节里,就不演示单表的操作了。

多表操作,本质上其实就是连接多个表,然后查询出数据,根据关联对象之间的关系,又可以分为1对1操作,1对多操作,多对多操作(本质上而言其实也是1对多),所以接下来,我们分两个部分来看如何通过MyBatis来操作

1对1操作

假设我们要根据用户的ID查询出用户的角色,并且假定一个用户只有一个角色(当然,实际上不止),这里以1001号用户为例,其在数据库中也仅有一个角色,所以符合我们操作的要求。

为了能通过MyBatis自动封装,我们在SysUser中增加一个字段SysRole

public class SysUser {    // 其他字段与数据库保持一致即可
    private SysRole role;    // set() get() toString()}

在查询操作中,我们可以通过下面的方式来获取数据

<select id="selectUserAndRoleById" resultType="domain.SysUser">
    select
        u.id,
        u.user_name userName,
        u.user_password userPassword,
        u.user_email userEmail,
        u.create_time createTime,        <!--
            注意从这里开始的别名是"role.XXX",因为字段中是role
            为了能够自动注入,所以需要采用obj.attr的形式,
            如果有多级对象,则是 a.b.c这种形式
        -->
        r.id "role.id",
        r.role_name "role.roleName",
        r.enabled "role.enable",
        r.create_by "role.createBy",
        r.create_time "role.createTime"
    from sys_user u 
        join sys_user_role ur on u.id = ur.user_id
        join sys_role r on r.id = ur.role_id
    where u.id = #{id}</select>

上面的实现方式从结果来看是没有问题的,但是从工程的角度来讲,其实不太好,尤其是当存在多个不同类型的查询,比如根据ID,根据名称,根据邮箱地址等,我们需要编写多份的代码,并且其中的select部分基本上是不变的,也就是带来非常明显的冗余了。

更好地解决方案是使用MyBatis中的resultMap,通过resultMap来封装,可以实现代码复用的目的

<resultMap id="userRoleMap" type="domain.SysUser">
    <id property="id" column="id"/>
    <!--其他的字段-->
    <result property="role.id" column="r.id"/>
    <!--其他的字段--></resultMap><select id="selectUserAndRoleById" resultMap="userRoleMap">
 ... 
 这里根据对应的字段调整一下,只需要能正确映射就行</select>

不过上面的内容语义不明显,更好的方式是使用resutlMap<association>标签来关联对象,如下

<resultMap id="userRoleMap" type="domain.SysUser">
    <id property="id" column="id"/>
    <!--其他的字段-->

    <!--
        注意这里,使用的是association,association的使用跟resultMap是类似的
        并且使用多了columnPrefix属性,为了区分来自不同表的字段,
        如果是多级的嵌套,则需要指定多级,如 role_pri_XXX,一个层次的columnPrefix会
        去过滤每一次匹配的前缀
        当然,在查询的时候也需要将对应的前缀标注出来
    -->
    <association property="role" javaType="domain.SysRole" columnPrefix="role_">
        <result property="id" column="id"/>
        <!--其他的字段-->
    </association></resultMap><!--注意下面的内容 role_也即是columnPrefix=""中指定的字段--><select>
    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</select>

通过上面的方式,当需要的时候,就可以直接指定查询的resultMap="userRoleMap"即可,已经减少了一部分的重复操作了,但是,上面的方式仍然不是合适的,因为既然有user对应的map,那实际上将role对应的字段也封装到map中,然后直接调用即可,这样,多个使用到role的地方都可以直接使用了

首先在SysRoleMapper.xml定义对应的roleMap,当然,放在其他的mapper里也是可以,但是放在SysRoleMapper.xml是最合适的

<mapper namespace="mapper.SysRoleMapper">
    <resultMap id="roleMapper" type="domain.SysRole">
        <id property="id" column="id"/>
        <!--其他的字段-->
    </resultMap></mapper>

整理完之后的userRoleMap内容如下

<resultMap id="userRoleMap" type="domain.SysUser">
    <id property="id" column="id"/>
    <!--其他的字段-->
    <!--这里使用resultMap来指定其他的resultMap,如果不在本文件,则使用全限定名-->
    <association property="role" columnPrefix="role_" resultMap="mapper.SysRoleMapper.roleMapper"/></resultMap>

经过上面的整理之后,现在的整体结构就变得非常灵活了,特别是当我们需要组合多个对象的时候,通过这种方式,可以实现只需要定义一个resultMap,然后在多处使用

1对多操作

有了上面封装1对1的操作过程作为基础,实现一对多就容易很多了,只需要将<association>替换为<collection>即可,当然,由于上面为了方便,直接在SysUser中定义了一个SysRole对象,但实际上我们知道,一个用户是可以对应多个角色的,所以,在SysUser中应该定义的是一个SysRole容器,比如list或者set等,也就是实际上1对多的操作啦

<resultMap id="userRoleMap" type="domain.SysUser">
    <!--注意这里-->
    <collection property="role" columnPrefix="role_" resultMap="mapper.SysRoleMapper.roleMapper"/></resultMap>

可以看到,因为为role对象定义roleMap,所以,当改动userRole时,其他的内容完全不需要改动

一个完整的例子

在上面的两步操作中,我们已经充分体验到了MyBatis中的resultMapassocation以及collection提供的便利,下面我们通过完整的例子,来加深对其认识

这里通过用户ID,获取其所有的角色以及所有角色对应的权限

将对应的实体类调整为如下

SysUser

public class SysUser {    // 一个用户可能对应多个角色
    private List<SysRole> role;
}



作者:颜洛滨
链接:https://www.jianshu.com/p/fab0985b103a


1人推荐
随时随地看视频
慕课网APP