OrientDB实战进阶 - RBAC案例
本文以最最常见的 RBAC(Role-Based Access Control,基于角色的访问控制)作为入口来对比关系型数据库与OrientDB在建模、查询效率方面的区别,通过实战来进阶最直观,最直接。
关键词:OrientDB, GraphDB
开始之前,我们对RBAC模型做两个小改动,如此更符合自然世界关系的复杂语义,对比后也更能直观体现图数据库的强大:
1.角色Roles之间有继承关系,即角色A是角色B的Parent, 则角色A拥有角色B的所有权限;
2.除开角色Roles可以拥有权限资源,某个具体的User也可以拥有具体的权限;
OK, 有了这两个变动后,在RDBMS中我们怎么设计表结构呢? 如下图示简化的PDM设计。简单说明下:
1. 共五张表:三张基本信息表,两张映射关系表(紫色)。其中Group的继承关系,我们暂时简化每个分组只能有一个Parent Group,所以继承关系直接体现在Group表了。
2. ResourceOwner关系表中有用户姓名和分组名字段,这两个字段互斥;当用户与资源映射时,分组列为空, 当分组与资源映射时,用户列为空。
本文后续的建模名称略有变化,但是横向类比比较容易,应该不会影响大家的阅读和理解。
Users=Person, Roles=Group, Rights=Resource
为了让大家理解下这个模型的运作机制,我已经在OrientDB中同步建模并生成了部分示例数据。
上图左侧的图示,圆圈代表顶点Vertex, 横线代表边Edge。示例数据直接通过图形化展示如上,无需过多解释,相比于RDBMS,这里更加一目了然。
解释一下该图的示例数据的权限情况:
p1属于分组g1
p1拥有r1-r5所有的资源,因为g1是其他所有分组的Parent
p3只拥有r1, 而g2没有拥有任何资源,也不是任何分组的Parent
p5只拥有r4, 因为p5所属的g5拥有r4
p4对r5无权限, 因为r5是p4所属分组的Parent组拥有的
模型和示例数据描述完毕,现在开始我们的需求实现!
1 查询用户名为John拥有的所有资源
SQL 写法
--查询John直接拥有的资源
select rname from ResourceOwner where pname='John'
union all
--查询John所属分组拥有的资源
select rname from ResourceOwner ro, PersonGroup pg
where ro.gname=pg.gname
and pg.pname='John'
union all
--查询John所属分组的所有子分组拥有的资源
select rname
from ResourceOwner ro, Group g, PersonGroup pg
where ro.gname=g.gname
and g.parent = pg.gname
and pg.pname='John'
Graph 写法
select from (
traverse out('Owns'), out('BelongsTo'), in('Parent')
from (select from Person where name='John')
)
where @class='Resource'
注解:
从SQL写法中我们可以看出关系型面向这种多重关系的处理只能穷举、笛卡尔积来scan出所有的记录,然后再做一个聚合;
当然该示例只是为了更清晰的让读者可以理解SQL的实现思路,没有做任何优化;
另外我们的用户、分组、资源表都是一个字段的最简单的表,现实情况肯定是多个字段,关系表存的ID, 那么就不是上面三个表关联这么简单的事了,可能要关联所有的5张表。
Graph语句首先从精炼程度上就比SQL优秀,读者没有OrientDB语法基础也不妨碍阅读这句话。
图数据库的首要也是基本原则就是能找到某个/多个具体的顶点然后开始遍历图,找到你需要的数据。
此语句充分验证了这个原则,先找到名字叫John的Person顶点(很可能不止一个), 以此为中心进行遍历(Traverse),罗列出所有需要遍历的边(Owns, Belongs, Parent), 最后过滤所有遍历过的顶点,只保留Resource作为结果。图的遍历是线性计算,相对于SQL中的笛卡尔积指数性计算,性能要快到飞起来。
2 查询出所有拥有资源123的用户
SQL 写法
--查询直接拥有资源123的用户
select pname from ResourceOwner where rname='123'
union all
--查询直接拥有资源123的分组内的用户
select pg.pname from ResourceOwner ro, PersonGroup pg
where ro.gname=pg.gname
and ro.rname='123'
union all
--查询直接拥有资源123的分组的父分组/祖父/曾祖父...的用户
???
Graph 写法
select from (
traverse out('Owns'), out('BelongsTo'), in('Parent')
from (select from Resource where id='123')
)
where @class='Person'
注解:
SQL你会发现在向上回溯获取N级父节点的时候做不到!当然你可以重新设计表结构来解决,但是第一种场景你会发现又解决不掉了...
Graph语法与第一个问题的语句基本一致,遍历+过滤,搞定!
3 查询John的所有分组,且这些分组不能有子组
SQL 写法
select gname
from Group g,
(select gname from PersonGroup pg where pname='John') tmp
where g.gname=tmp.gname
and g.children is null --重新设计表结构
Graph 写法
select person.name as name, group.name as groupName
from (
Match
{class:Person, as:person, where:(name='John')}
.out('BelongsTo')
{class:Group, as:group, where:(in('Parent').size()==0)}
return person, group)
)
注解:
SQL语法中的如何判断“子分组”不存在的实现比较有难度:
若group表里面加上children字段,逗号分隔,对该场景比较容易实现;
若新建一张group + children的映射关系表,那这里SQL要再多加一层JOIN;
不管哪种方案都是与Parent表设计的数据冗余。
OrientDB的Match语句,通过John遍历到所有的Group, 然后过滤条件 in('Parent').size==0 表示这个Group有0个Parent入边,表达的就是Group没有子组,搞定!
本文太干,请静心阅读
如有帮助,务多多支持
○
Nicholas Qu