【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;
}

SysRole

public class SysRole {
    // 一个角色可能有多个权限
    private List<SysPrivilege> privilegeList;
}

然后为每个实体类编写对应的resultMap,这个参考上面的编写方式就行啦,这里就不贴代码了

接下来组合多个resultMap,这里我们采用自底向上的方式

<resultMap id="rolePrivilegeMap" type="domain.SysRole">
    <id property="id" column="id"/>
    <result property="createBy" column="create_by"/>
    <result property="createTime" column="create_time"/>
    <result property="roleName" column="role_name"/>
    <result property="enabled" column="enabled"/>
    <!--组装privilegeMap-->
    <collection property="privilegeList" columnPrefix="pri_" resultMap="mapper.SysUserMapper.privilegeMap"/>
</resultMap>
<resultMap id="userRoleMap" type="domain.SysUser">
    <id property="id" column="id"/>
    <result property="userName" column="user_name"/>
    <result property="userPassword" column="user_password"/>
    <result property="userEmail" column="user_email"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    <!--组装rolePrivilegeMap-->
    <collection property="role" columnPrefix="role_" resultMap="mapper.SysRoleMapper.rolePrivilegeMap"/>
</resultMap>

通过上面的两层组装之后,当我们需要使用的时候,就可以直接指定resultMap="userRoleMap"即可啦

关于ResultMap,还有一个小点需要注意,如果查询的数据中不包含某些字段,而resultMap中有该字段时,MyBatis会忽略该字段,所以,一个resultMap可以复用在其他场景,即使查询的字段跟resultMap中的字段不完全匹配,只要resultMap中包含我们需要的字段即可

discriminator

在ResultMap中,还有一个<discriminator>,该标签的用途在于,根据不同的字段值进行分类,比如在上面的案例中,有一些角色是启用的,有一些是不允许启用的,那么,对于不允许启用的角色,我们就不需要获取其角色以及权限信息,所以,这时,可以通过discriminator来实现根据不同的值来映射到不同的resutMap中,如下面所示

<resultMap id="userMap" type="domain.SysUser">
    <id property="id" column="id"/>
    <result property="userName" column="user_name"/>
    <result property="userPassword" column="user_password"/>
    <result property="userEmail" column="user_email"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>

<resultMap id="userRoleMap" type="domain.SysUser">
    <!--根据role_enabled的状态来选择不同的查询-->
    <discriminator javaType="int" column="role_enabled">
        <case value="1" resultMap="userRoleMapSelect" />
        <!--只获取用户的基本信息,不获取角色以及权限信息-->
        <case value="0" resultMap="userMap"/>
    </discriminator>
</resultMap>

<!--直接继承userMap,可以避免编写过多的result标签-->
<resultMap id="userRoleMapSelect" type="domain.SysUser" extends="userMap">
    <collection property="role" columnPrefix="role_" resultMap="mapper.SysRoleMapper.rolePrivilegeMap"/>
</resultMap>

通过上面的例子,可以看到discriminator的强大之处了,在使用discriminator的时候需要注意,discriminator是作用在当前的resultMap的,也就是说,discriminator中的resultMap封装的是当前的result中的内容,而不是决定子查询中的内容

总结

本小节主要学习了MyBatis中的多表查询,通过MyBatis中的resultMap以及resultMap中的associationcollection,可以实现一对一,一对多查询中结果的自动封装,而通过discriminator则可以根据不同的数值来选择返回不同的resultMap,通过resultMap中的extends属性,可以复用一个已经存在的resultMap,通过多个resultMap的复用,可以极大地提高代码的复用率,使得代码更加简洁。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,422评论 0 4
  • MyBatis 理论篇 [TOC] 什么是MyBatis  MyBatis是支持普通SQL查询,存储过程和高级映射...
    有_味阅读 2,876评论 0 26
  • 早已经习惯一个人想就这么简单的生活到老却在不经意间与你遇到于是又有了祈祷想要那一生一次一次一生的拥抱 早已经不相信...
    justwind阅读 184评论 0 1
  • 在去年的时候就有在微博见过一个博主推荐过这本书,光看书名真是没有觉得这是一本时间管理书。 但是这本书确定就是时间管...
    郑珍容阅读 439评论 0 1