我们讨论一下,具有树形特征的一类数据,比如组织架构,菜单等,如何将这一类数据放到数据库中存储?
1 级架构
1.1 二级架构
1.1.1 三级架构
1.2 二级架构
1.2.1 三级架构
1.2.1.1 四级架构
...
那么这个问题难点在于哪呢?
- 组织架构层级不定,导致我们没办法将这些数据按每层一个表的形式存起来。就算固定层级的组织架构,每一层的大部分属性都一样,分表存储显然比较浪费
- 上下级之间的关联关系如何存储。很多人看到这个问题的第一反应是:将子层级的id存粗到父层级的字段中。因为这样最符合直观印象。
public class Organization{
private int id;
private String name;
private List<Organization> subOrgs;
}
但是无论存一个字段还是多个字段好像都不合适。存一个字段增删改都是问题,从子菜单向父菜单查也会是问题。存多个字段的话,到底多少个合适呢?
基于这两点,我们可以得到一个结论:组织架构数据适合存在一个表中,关联关系不宜存在父层级的数据行上。不存在父层级的数据行上就存在子层级的行上吧。
create table t_organization{
id int(8) comment '组织ID',
name varchar(64) comment '组织名称',
parent_id int(8) comment '上级组织ID'
}
使用这个表结构,我们查询树根的时候,可以使用
select * from t_organization where parent_id is null;
查询一个组织的子组织的时候,可以有:
select * from t_organization where parent_id = xxx;
一般的常规的需求就能解决了。
但是事情总不是那么简单。变态的需求一个接一个过来
- 要查询一个组织下所有的子组织,用来做统计。
- 如何快速找到一个节点的根节点
这两个问题本质都是相同的:如何找到和某一个节点有间接关系但是没直接关系的节点?
找到所有子节点的代码可以是
publc List<Organization> allSubs(int rootId){
List<Organization> allSubOrgs=new ArrayList<>();
List<Organization> subOrgs=organizationDao.findSubOrgs(rootId);
allSubOrgs.addAll(subOrgs);
for(Organization org:subOrgs){
allSubOrgs.addAllallSubs(org.getId()));
}
return allSubOrgs;
}
找到根节点的代码可以是
publc Organization rootOrg(int currentId){
Organization current= organizationDao.getById(currentId);
if(current.getParentId==null){
return current;
}else{
return rootOrg(current.getParentId());
}
}
学了这么多年的递归,终于能派上用场了~ 但是一考虑到要多次查库,似乎这种设计也没那么好。
那么还有没有其他的办法解决呢?有!事实上有一种叫做闭包表的设计方案,可以解决这种问题。
create table t_organization(
id int(8) comment '组织ID',
name varchar(64) comment '组织名称'
);
create table t_org_rela(
ancestor_id int(8) comment '祖先id',
node_id int(8) comment '当前节点id',
distance int(8) comment '与祖先节点的距离',
);
存储数据的时候就可以有
组织架构:
| id | name |
|---|---|
| 1 | 国务院 |
| 2 | 北京市政府 |
| 3 | 河北省政府 |
| 4 | 朝阳区政府 |
| 5 | 石家庄市政府 |
关系表
| ancestor_id | node_id | distance |
|---|---|---|
| 1 | 2 | 1 |
| 1 | 3 | 1 |
| 1 | 4 | 2 |
| 1 | 5 | 2 |
| 2 | 4 | 1 |
| 3 | 5 | 1 |
可以看出这是一种拿空间换时间的方案。 我们查询直接子组织的时候
select a.* from t_organization a,t_org_rela b where b.ancestor_id= x and b.distance=1 and b.
node_id=a.id ;
查询所有子节点的时候:
select a.* from t_organization a,t_org_rela b where b.ancestor_id= x and b.
node_id=a.id ;
查询根节点的时候
select a.* from t_organization t,t_org_rela a
left join
(select max(distance) distance,node_id from t_org_rela where node_id = x group by node_id) b on a.node_id= b.node_id and a. distance=b.distance where t.id=a.ancestor_id;
插入一个关联关系的时候
insert into t_org_rela(ancestor_id,node_id,distance) select
ancestor_id, 'x' ,distance+1 from t_org_rela where node_id= 'parent';
insert into t_org_rela(ancestor_id,node_id,distance) values('parent','x',1) ;
可以看出来,闭包表解决了多次查询数据库的问题,但是带来了程序复杂度的提升,所以在要求不是很严苛的时候,闭包表不是一个很好的选择。