Java Mybatis 框架 IV

Java Mybatis 框架 IV

1.关联查询

● 首先,请准备一些数据表,及必要的测试数据,例如:存在若干条用户数
据,存在若干条角色数据,某个用户存在与角色的关联,最好有些用户有
多个关联,又有些用户只有1个关联,还有些用户没有关联

2.RBAC

● RBAC = Role Based Access Control(基于角色的访问控制)
● RBAC是经典的用户权限管理的设计思路。在这样的设计中,会存在3种类型:用户、角色、权限,权限将分配到各种角色上,用户可以关联某种角色,进而实现用户与权限相关。使用这样的设计,更加利于统一管理若干个用户的权限。

● 在RBAC的设计思路中,用户与角色一般是多对多的关系,而在数据库中,仅仅只是使用“用户”和“角色”这2张表是不利于维护多对多关系的,通常会增加一张中间表,专门记录对应关系,同理,角色和权限也是多对多的关系,也需要使用中间表来记录对应关系!

● 典型的RBAC设计–ams_admin:管理员表

-- 管理员表:创建数据表
drop table if exists ams_admin;
create table ams_admin (
id bigint unsigned auto_increment,
username varchar(50) default null unique comment '用户名',
password char(64) default null comment '密码(密文)',
nickname varchar(50) default null comment '昵称',
avatar varchar(255) default null comment '头像URL',
phone varchar(50) default null unique comment '手机号码',
email varchar(50) default null unique comment '电子邮箱',
description varchar(255) default null comment '描述',
is_enable tinyint unsigned default 0 comment '是否启用,1=启用,0=未启用',
last_login_ip varchar(50) default null comment '最后登录IP地址(冗余)',
login_count int unsigned default 0 comment '累计登录次数(冗余)',
gmt_last_login datetime default null comment '最后登录时间(冗余)',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '管理员' charset utf8mb4;

● 典型的RBAC设计–ams_role:角色表

-- 角色表:创建数据表
drop table if exists ams_role;
create table ams_role (
id bigint unsigned auto_increment,
name varchar(50) default null comment '名称',
description varchar(255) default null comment '描述',
sort tinyint unsigned default 0 comment '自定义排序序号',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '角色' charset utf8mb4;

● 典型的RBAC设计–ams_admin_role:管理员与角色的关联表

-- 管理员角色关联表:创建数据表
drop table if exists ams_admin_role;
create table ams_admin_role (
id bigint unsigned auto_increment,
admin_id bigint unsigned default null comment '管理员id',
role_id bigint unsigned default null comment '角色id',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '管理员角色关联' charset utf8mb4;

● 典型的RBAC设计–ams_permission:权限表

-- 权限表:创建数据表
drop table if exists ams_permission;
create table ams_permission (
id bigint unsigned auto_increment,
name varchar(50) default null comment '名称',
value varchar(255) default null comment '值',
description varchar(255) default null comment '描述',
sort tinyint unsigned default 0 comment '自定义排序序号',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '权限' charset utf8mb4;

● 典型的RBAC设计–ams_role_permission:角色与权限的关联表

-- 角色权限关联表:创建数据表
drop table if exists ams_role_permission;
create table ams_role_permission (
id bigint unsigned auto_increment,
role_id bigint unsigned default null comment '角色id',
permission_id bigint unsigned default null comment '权限id',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '角色权限关联' charset utf8mb4;

● 插入测试数据

truncate ams_admin;
truncate ams_admin_role;
truncate ams_role;
truncate ams_permission;
insert into ams_admin (username, password) values ('admin001', '123456');
insert into ams_admin (username, password) values ('admin002', '123456');
insert into ams_admin (username, password) values ('admin003', '123456');
insert into ams_admin (username, password) values ('admin004', '123456');
insert into ams_admin (username, password) values ('admin005', '123456');

● 插入测试数

insert into ams_permission (name, value, description) values
('商品-商品管理-读取', '/pms/product/read', '读取商品数据,含列表、详情、查询等'),
('商品-商品管理-编辑', '/pms/product/update', '修改商品数据'),
('商品-商品管理-删除', '/pms/product/delete', '删除商品数据'),
('后台管理-管理员-读取', '/ams/admin/read', '读取管理员数据,含列表、详情、查询等'),
('后台管理-管理员-编辑', '/ams/admin/update', '编辑管理员数据'),
('后台管理-管理员-删除', '/ams/admin/delete', '删除管理员数据');
insert into ams_role (name) values
('超级管理员'), ('系统管理员'), ('商品管理员'), ('订单管理员');
insert into ams_admin_role (admin_id, role_id) values
(1, 1), (1, 2), (1, 3), (1, 4),
(2, 1), (2, 2), (2, 3),
(3, 1), (3, 2),
(4, 1);

● 当有了新的数据表后,你应该在课后使用这些表继续练习常规数据管理,例如:
– 向权限表中插入新的数据
– 根据id删除某个权限
– 查询权限表中的所有权限

● 提示:
– 需要新的数据类型,例如Permission类
– 需要新的接口文件,用于定义以上2个数据访问功能的抽象方法
– 需要新的XML文件,用于配置抽象方法对应的SQL语句
– 需要修改配置信息,将此前指定的XML文件由AdminMapper.xml改为*.xml,并把SpringConfig类中sqlSessionFactoryBean()方法的第2个参数由Resource类型改为Resource…类型
– 当需要测试时,使用新的测试类

3.关联查询

● 本次查询需要执行的SQL语句大致是:

select *
from ams_admin
left join ams_admin_role on ams_admin.id=ams_admin_role.admin_id
left join ams_role on ams_admin_role.role_id=ams_role.id
where ams_admin.id=

● 在阿里巴巴的《Java开发手册》中指出:
– 【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。

● 由于不建议使用星号表示字段列表,而关联查询时,由于涉及多张表,则字段列表可能较长,可以使用节点封装字段列表,并在编写SQL语句时,使用节点进行引用
● 提示:在IntelliJ IDEA中,如果在节点中直接写字段列表,会提示错误,这是IntelliJ IDEA的误判,可以无视,并不影响运行,或者,使用其它方式避免误判,例如添加恒等式(见下页示例)
● 注意:使用节点可以封装SQL语句的任何部分,而封装字段列表是最常见的做法

● 使用与的简单示例:

<sql id="SimpleQueryFields">
<if test="true">
id,
username,
password
</if>
</sql>
<select id="getById" resultType="xx.xx.xx.AdminVO">
select
<include refid="SimpleQueryFields" />
from ams_admin
where id=#{id}
</select>

● 如果使用封装了查询的字段列表,与的相性更好,所以,在开发实践中,通常结合一起使用
● 另外,在开发实践中,许多不同条件的查询的字段列表是相同的,使用可以很好的实现代码片段的复用

● 通过测试运行,可以发现(必须基于以上测试数据):
– 当使用的id值为1时,共查询到4条记录,并且用户的基本信息是相同的,只是与角色关联的数据不同
– 当使用的id值为2时,共查询到3条记录
– 当使用的id值为3时,共查询到2条记录
– 当使用其它有效用户的id时,共查询到1条记录

● 此类查询期望的结果应该是:

public class xxx {
// 用户基本信息的若干个属性,例如用户名、密码等
// 此用户的若干个角色数据,可以使用 List
}

● 则先创建“角色”对应的数据类型:

public class Role {
private Long id;
private String name;
private String description;
private Integer sort;
private LocalDateTime gmtCreate;
private LocalDateTime gmtModified;
// Setters & Getterss
// toString()
}

● 再创建用于封装此次查询结果的类型:

public class AdminDetailsVO { private Long id; private String username; private String password; private String nickname; private String avatar; private String phone; private String email; private String description; private Integer isEnable; private String lastLoginIp; private Integer loginCount; private LocalDateTime gmtLastLogin; private LocalDateTime gmtCreate; private LocalDateTime gmtModified; private List<Role> roles; // Setters & Getterss // toString() }

● 在AdminMapper接口中添加抽象方法:

AdminDetailsVO getDetailsById(Long id)

● 需要注意,由于此次关联了3张表一起查询,结果集中必然出现某些列的名称是完全相同的,所以,在查询时,不可以使用星号表示字段列表(因为这样的结果集中的列名就是字段名,会出现相同的列名),而是应该至少为其中的一部分相同名称的列定义别名

● 例如

select
ams_admin.id, ams_admin.username, ams_admin.password, ams_admin.nickname,
ams_admin.avatar, ams_admin.phone, ams_admin.email, ams_admin.description,
ams_admin.is_enable, ams_admin.last_login_ip, ams_admin.login_count,
ams_admin.gmt_last_login, ams_admin.gmt_create, ams_admin.gmt_modified,
ams_role.id AS role_id,
ams_role.name AS role_name,
ams_role.description AS role_description,
ams_role.sort AS role_sort,
ams_role.gmt_create AS role_gmt_create,
ams_role.gmt_modified AS role_gmt_modified
from ams_admin
left join ams_admin_role on ams_admin.id=ams_admin_role.admin_id
left join ams_role on ams_admin_role.role_id=ams_role.id
where ams_admin.id=1;

● 在Mybatis处理中此查询时,并不会那么智能的完成结果集的封装,所以,必须自行配置用于指导Mybatis完成封装!

<resultMap id="DetailsResultMap" type="xx.xx.xx.xx.AdminDetailsVO">
<!-- 在1对多、多对多的查询中,即使名称匹配的结果,也必须显式的配置 -->
<!-- 主键字段的结果必须使用id节点进行配置,配置方式与result节点完全相同 -->
<id column="id" property="id" />
<result column="gmt_create" property="gmtCreate" />
<!-- 需要使用collection节点配置1对多中“多”的数据 -->
<collection property="roles" ofType="xx.xx.xx.Role">
<id column="role_id" property="id" />
<result column="gmt_create" property="gmtCreate" />
</collection>
</resultMap>

● 完整的XML示例:

<sql id="DetailsQueryFields"> <if test="true"> ams_admin.id, ams_admin.username, ams_admin.password, ams_admin.nickname, ams_admin.avatar, ams_admin.phone, ams_admin.email, ams_admin.description, ams_admin.is_enable, ams_admin.last_login_ip, ams_admin.login_count, ams_admin.gmt_last_login, ams_admin.gmt_create, ams_admin.gmt_modified
ams_role.id AS role_id,
ams_role.name AS role_name,
ams_role.description AS role_description,
ams_role.sort AS role_sort,
ams_role.gmt_create AS role_gmt_create,
ams_role.gmt_modified AS role_gmt_modified
</if>
</sql>
<resultMap id="DetailsResultMap" type="cn.tedu.mybatis.AdminDetailsVO">
<!-- 在1对多、多对多的查询中,即使名称匹配的结果,也必须显式的配置 -->
<!-- 主键字段的结果必须使用id节点进行配置,配置方式与result节点完全相同 -->
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
<result column="nickname" property="nickname" />
<result column="avatar" property="avatar" />
<result column="phone" property="phone" />
<result column="email" property="email" />
<result column="description" property="description" />
<result column="is_enable" property="isEnable" />
<result column="last_login_ip" property="lastLoginIp" />
<result column="login_count" property="loginCount" />
<result column="gmt_last_login" property="gmtLastLogin" />
<result column="gmt_create" property="gmtCreate" />
<result column="gmt_modified" property="gmtModified" />
<!-- 需要使用collection节点配置1对多中“多”的数据 -->
<collection property="roles" ofType="cn.tedu.mybatis.Role">
<id column="role_id" property="id" />
<result column="role_name" property="name" />
<result column="role_description" property="description" />
<result column="role_sort" property="sort" />
<result column="role_gmt_create" property="gmtCreate" />
<result column="role_gmt_modified" property="gmtModified" />
</collection>
</resultMap>
<select id="getDetailsById" resultMap="DetailsResultMap">
select <include refid="DetailsQueryFields" />
from ams_admin
left join ams_admin_role on ams_admin.id=ams_admin_role.admin_id
left join ams_role on ams_admin_role.role_id=ams_role.id
where ams_admin.id=#{id}
</select>

我是将军;我一直都在,。!

你可能感兴趣的:(Java,Spring系列,mybatis,java,数据库)