组织机构是在开发过程中遇到的非常常见的一个功能,通常组织机构都有非常明确的层级或者说是等级关系,以树或者森林的形式展现的居多。
在查询组织机构时,常见的功能有根据某个机构查询其子机构、查询某个机构的父级机构,以及我在这里要介绍的,查询全部机构并返回层次结构。
查询全部机构并非难事,单独说返回层次结构,如果使用例如MongoDB数据库存储的话也不难实现,但是如果使用关系型数据库存储数据,同时想减少数据库的访问次数,就需要做一些工作了,例如一次访问拿回数据后,通过代码来组拼。
我这里使用的表结构如下所示
建表语句如下所示
CREATETABLE`dept`(
`id`int(10)NOTNULLAUTO_INCREMENTCOMMENT主键id,
`code`varchar(10)DEFAULTNULLCOMMENT机构编码,
`name`varchar(50)DEFAULTNULLCOMMENT机构名称,
`deep`int(4)DEFAULTNULLCOMMENT机构所在层级,
`parid`int(10)DEFAULTNULLCOMMENT父级id,
`sort`int(4)DEFAULTNULLCOMMENT部门顺序,
PRIMARYKEY(`id`)
)ENGINE=InnoDBAUTO_INCREMENT=12DEFAULTCHARSET=utf8;
通过insert语句初始化数据:
INSERTINTO`dept`VALUES(1,01,根目录,1,0,1);
INSERTINTO`dept`VALUES(2,,子一,2,1,1);
INSERTINTO`dept`VALUES(3,,子二,2,1,2);
INSERTINTO`dept`VALUES(4,,子三,2,1,3);
INSERTINTO`dept`VALUES(5,01,孙一,3,3,1);
INSERTINTO`dept`VALUES(6,02,孙二,3,3,2);
INSERTINTO`dept`VALUES(7,01,孙三,3,4,1);
INSERTINTO`dept`VALUES(8,,孙孙一,4,5,1);
INSERTINTO`dept`VALUES(9,01,sun孙孙一,5,8,1);
INSERTINTO`dept`VALUES(10,,子四,2,1,4);
INSERTINTO`dept`VALUES(11,0201,孙孙二,4,6,1);
使用sql语句select*fromdeptorderbydeepdesc,pariddesc;查询的结果如下所示
通过数据可以很容易看出这是一个深度为5的树,即组织机构只有一个根目录,存在5级部门,同时通过id和parid可以很容易判断出部门之间的父子关系,或者通过code字段的编码规则也可以得出结论(本篇文章没有用到code字段)。正是通过这条sql语句查询出全部的组织机构数据,同时制定了排序规则,使我们拿到的是一个按层级和父级节点分好类的一组数据,下面将通过代码,将已经完成分类的数据整合成有层次的json字符串结构。
数据返回的顺序是从最底层到最顶层,而通过parid字段可以找到父级机构,所以我将这个过程称为“找爸爸”,也就是找到每个数据的父级节点数据,并将自己作为父级节点的子级数据嵌套进去。当然了,如果仅仅是当前节点“找爸爸”还不够,正确的做法应该是“带着儿子找爸爸”,或者说是“拖家带口找爸爸”,也就是当前节点应该先获取到所有的子孙数据完成层次的构造,再将自己作为父级节点的子级节点嵌套进去。
构造的实体类如下(这里省略了get和set方法):
publicclassDept{privateIntegerid;privateStringcode;privateStringname;privateIntegerdeep;privateIntegerparid;privateIntegersort;privateListDeptchild;}
示例代码如下,mapper中使用的sql语句就是上文中举的例子:
ListDeptdeptList=ctmDeptMapper.findAll();//记录已经处理过的节点所处的层级,初始化为-1intinitDeep=-1;//记录已经处理过的节点的父级机构id,初始化为-1intinitParId=-1;ListDeptdepts=newArrayList();//结果缓存数据,用于记录机构id,以及此机构对应的子级机构列表,最后的结果数据也会缓存到这里MapInteger,ListDeptlistByParid=newHashMap();//将第一个节点初始化到结果缓存中,否则会丢失该数据DeptfirstDept=deptList.get(0);ListDeptinitDeptList=newArrayList();initDeptList.add(firstDept);listByParid.put(firstDept.getParid(),initDeptList);//带着儿子找爸爸for(Deptdept:deptList){intid=dept.getId();intparid=dept.getParid();intdeep=dept.getDeep();if(listByParid.containsKey(id)){//当前节点存在若干子节点//在hashmap中获取所有ListDeptchildList=listByParid.get(id);//将该节点的子树维护到当前数据中dept.setChild(childList);}if(deep!=initDeep){//进入新的层级if(!depts.isEmpty()){//当前节点处于新的层级,说明depts中的数据就是一个完整的子树ListDeptoldChildren=newArrayList(depts);//这个子树的根节点就是上一次循环中访问的节点//所以这个完整的字树的父级id也就是上一个节点的父级id,该数据缓存在initParId中,将父级id和子树放入缓存listByParid.put(initParId,oldChildren);//进入新的层级,那么一定有新的孩子,需要初始化一个listdepts=newArrayList();}}else{if(parid!=initParId){//当前节点位于新的分支,则说明depts中的数据是一个完整的子树,与进入新的层级时处理方法一致ListDeptoldChildren=newArrayList(depts);listByParid.put(initParId,oldChildren);depts=newArrayList();}}depts.add(dept);initDeep=deep;initParId=parid;}ListDeptresult=newArrayList();//按照上述方法,会有若干节点缓存到名为depts的list中(亲兄弟队列)//若只有一个节点,说明这个节点是根节点//若存在若干节点,说明这些节点是兄弟关系,他们有一个共同的虚根节点(组织机构为森林结构)intsize=depts.size();if(size==1){Deptdept=depts.get(0);intdeptId=dept.getId();ListDeptchildList=listByParid.get(deptId);if(childList!=null!childList.isEmpty()){dept.setChild(listByParid.get(deptId));}result.add(dept);}else{result.addAll(depts);}returngson.toJson(result);
返回结果展示:
[{"id":1,"code":"01","name":"根目录","deep":1,"parid":0,"sort":1,"child":[{"id":2,"code":"","name":"子一","deep":2,"parid":1,"sort":1},{"id":3,"code":"","name":"子二","deep":2,"parid":1,"sort":2,"child":[{"id":5,"code":"01","name":"孙一","deep":3,"parid":3,"sort":1,"child":[{"id":8,"code":"","name":"孙孙一","deep":4,"parid":5,"sort":1,"child":[{"id":9,"code":"01","name":"sun孙孙一","deep":5,"parid":8,"sort":1}]}]},{"id":6,"code":"02","name":"孙二","deep":3,"parid":3,"sort":2,"child":[{"id":11,"code":"0201","name":"孙孙二","deep":4,"parid":6,"sort":1}]}]},{"id":4,"code":"","name":"子三","deep":2,"parid":1,"sort":3,"child":[{"id":7,"code":"01","name":"孙三","deep":3,"parid":4,"sort":1}]},{"id":10,"code":"","name":"子四","deep":2,"parid":1,"sort":4}]}]