详细解读权限控制中的ER关系(新手入门必备知识)

本文将结合权限控制进行对ER关系的详细讲解,希望能给JavaWeb开发的新手朋友们带来一点帮助,本文中叙述可能存在不足之处,还请大家指出!(写博不易,尊重版权)

关于权限控制,无外乎用户User、角色Role、菜单Menu三张表,所以先建表如下:

以上为常用两种建表模式,是针对于用户和角色的ER关系不同进行分类的,下面进行详细解读:

首先角色和权限是多对多关系,这毫无疑问,一个角色可以使用多个菜单,一个菜单可以被多个角色使用

图一:用户和角色是多对多关系,这是最符合实际情况也是最常用的,因为一个用户可以担任多个角色(用户A可以同时担任项目经理和架构师两种角色),一个角色也可以对应多个用户(项目经理有A、B、C三人)

图二:用户和角色是多对一关系,这是结合实际问题中对权限控制进行的简化处理,即认为一个用户只能担任一个角色或者只考虑用户的主要角色,次要角色不予考虑,如A是管理员(一对一),B、C都是普通用户(多对一)


开始编写javabean和解析SSM和SSH中的ER关系映射文件(以简化形式图二为例):

User表实体类:(仅仅写主要部分)

public class Admin {
private Integer id;
private String account;
private String password;
private String name;
private Integer state;
// 多对一:外键r_id
private Role role;

}

Role表实体类:

public class Role {
private Integer id;
private String name;
private Integer state;
// 一对多
private Set<Admin> admins;
// 一对多 中间表mid
private Set<Menu> menus;

}

Menu表实体类:

public class Menu {

private Integer id;

private Integer state;
private String name;
private String url;
// 多对一:外键p_id
private Menu parent;
// 一对多:(非表中数据,只是方便后面将根据p_id查询出来的子菜单结果进行封装到menu实体)
private List<Menu> childs;
// 一对多:中间表rid
private Set<Role> roles;

}

分析以上实体类设计的理由和ER关系如下:

ER关系分析的前提是要搞清楚是针对哪个实体对象(Java是面向对象设计的)而言的,如果没有这个前提还大夸奇谈一对一、一对多、多对多关系简直是扯淡,也是很多初学者搞不清的根本原因。



一对一关系:      实体和普通属性是一对一关系,针对User实体而言, User实体和 name属性的关系;

多对一关系:      实体和实体属性的关系(涉及外键),针对User实体而言,User实体和role属性(r_id外键)的关系;针对Menu实体而言,Menu实体和父菜单parent的关系;

一对多关系:      本质与多对一同理,只是针对对象不一样(涉及集合)  针对Role实体而言,Role实体和属性set<Admin> admins的关系;

多对多关系:     实体和实体的关系(表与表的关系,涉及中间表 本质是针对不同的两个实体一对多关系的结合,实际分析处理拆分为针对不同实体的一对多关系;


说完大家应该已经理清了ER关系的本质,下面我们看看Hibernate和Mybatis框架中ER关系映射配置文件的设计不同:

Hibernate中:

以Menu实体为例:

<hibernate-mapping package="com.xxx.bean">
<!-- lazy表示懒加载技术 ,inverse指定维护关系,order-by根据某属性排序-->

<class name="Menu" table="MENU" lazy="false" schema="SCOTT">
<!-- 主键 -->
<id name="id" column="id" type="long">
<generator class="sequence">
<param name="sequence">seq_menu</param>
</generator>
</id>

<!-- 中间表many-to-many ,针对于Menu实体和属性roles,一对多关系-->
<set name="roles" lazy="false" inverse="true" table="ROLE_MENU" >
<key column="mid" />
<many-to-many class="Role" column="rid" />
</set>

<!-- 多对一关系用many-to-one 属性parent -->
<many-to-one name="parent" column="p_id" class="Menu" lazy="false" />

<!-- 一对多关系用one-too-many 属性childs -->
<list name="childs" lazy="false" inverse="true" order-by="id">
<key column="p_id" />
<one-to-many class="Menu" />
</list> 

<!-- 其他属性 -->
<property name="name" column="name" />
<property name="url" column="url" />

<property name="status" column="status" />

</class>
</hibernate-mapping>

mybatis中:

mybatis中除了一对一关系,其他所有关系转化为 多对一association或一对多collection关系,二者可嵌套使用,即多对多转化为两个针对不同实体对象的一对多

<mapper namespace="com.salmon.mapper.RoleMapper">
<resultMap type="com.salmon.model.Role" id="role_ID">
<id property="id" column="r_id" />
<result property="name" column="rname" />
<result property="state" column="rstate" />
<!-- 一对多: 中间表,针对Role实体和admins属性 -->
<collection property="admins" ofType="com.salmon.model.Admin">
<id property="id" column="a_id" />
<result property="account" column="account" javaType="java.lang.String" jdbcType="VARCHAR" />
<result property="password" column="password" />
<result property="realname" column="realname" />
<result property="state" column="astate" />
</collection>
<!--   ####      嵌套使用     ####   -->
<!-- 一对多:  中间表, 针对Role实体和menus属性 -->
<collection property="menus" ofType="com.salmon.model.Menu">
<id property="id" column="m_id" />
<result property="state" column="mstate" />
<result property="name" column="mname" />
<result property="url" column="url" />
<!-- 多对一: 外键,针对Menu实体和parent属性 -->
<association property="parent" javaType="com.salmon.model.Menu" column="pid">
<id property="id" column="pm_id" />
<result property="state" column="pm_state" />
<result property="name" column="pm_name" />
</association>
</collection>
</resultMap>
<!--  注意resultMap中的属性用到了才配,没用到无须配置(多配不用不影响),且一定要确保包含所有对应的sql查询的结果映射关系;
此处仅作参考模板,实际按需求配置resultMap和撰写sql语句即可,不用完全配置 -->
<!-- 模糊分页查询 -->
<sql id="fuzzy">
<!-- 条件省略-->
</sql>
<select id="fuzzyFindByPage" resultMap="role_ID" parameterType="java.util.Map">
<if test="page!=null">
select * from (
select rownum as rn ,temp.* from (
</if>
select r.id as r_id , r.name as rname,r.state as rstate ,a.id as a_id,a.account ,a.password,a.realname,a.state as astate,
a.rid,m.id as m_id,m.pid,m.state as mstate ,m.name as mname,m.url,pid.id as pm_id,pid.state as pm_state,pid.name as pm_name
 from role r ,admin a,menu m ,role_menu rm ,menu pid where pid.id=m.pid and r.id=rm.rid and m.id=rm.mid and a.rid=r.id
<include refid="fuzzy" />
<if test="page!=null">
) temp
where rownum <![CDATA[<=]]>
#{page.pageNum}*#{page.pageSize}
) tp where tp.rn <![CDATA[>=]]>
(#{page.pageNum}-1)*#{page.pageSize}+1
</if>
</select>
</mapper>

下面简单说说菜单表/权限表的设计问题的个人心得:(直接看图,不详细叙述)

实际中经常碰到编写菜单的增删改操作,实际很多时候这是没有任何意义,因为实际操作中不是说你增加一个菜单就真的有这个菜单(项目的实际菜单是固定的,不是你想增加菜单就写个增删改就行,还得有实际页面和功能,这明显不可能,所以一般对于添加的“伪菜单”进行抛异常处理),但是删除操作是会删除"真菜单"的,防止误操作导致系统崩溃,我们采用"保留部分数据区的方式"对“真菜单”权限进行权限设置,允许查,不允许增删改!对“伪”菜单(途中id大于100的用户自定义菜单)进行完全允许增删改查!(CRUD加id>100的条件)


再简要叙述下关于数据库外键的问题:

发现很多新人,会把外键(实体属性)当作普通属性处理以避免上述繁杂的ER关系配置(即全为一对一关系),这种设计方案极大简化sql查询和ER关系配置,相当所有sql查询拆分为分步查询,但这会给controller层和页面处理带来极大负面影响,多表查询时controller要注入多个daoimpl对象并将其传到页面按需求处理。不说该方法完全不可取或毫无优点(最起码再难的sql语句分步写几乎无须动脑),但建议合理按需使用,严重不建议在多表多关联查询使用,建议尽量不用!


展开阅读全文

没有更多推荐了,返回首页