美文网首页
【程序猿】返回全部组织机构并构造层次结构

【程序猿】返回全部组织机构并构造层次结构

作者: 龙腾九天ly | 来源:发表于2022-08-10 12:00 被阅读0次

组织机构是在开发过程中遇到的非常常见的一个功能,通常组织机构都有非常明确的层级或者说是等级关系,以树或者森林的形式展现的居多。

在查询组织机构时,常见的功能有根据某个机构查询其子机构、查询某个机构的父级机构,以及我在这里要介绍的,查询全部机构并返回层次结构。

查询全部机构并非难事,单独说返回层次结构,如果使用例如MongoDB数据库存储的话也不难实现,但是如果使用关系型数据库存储数据,同时想减少数据库的访问次数,就需要做一些工作了,例如一次访问拿回数据后,通过代码来组拼。

我这里使用的表结构如下所示

QQ截图20220810221825.png

建表语句如下所示

CREATE TABLE `dept` (

  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '主键id',

  `code` varchar(10) DEFAULT NULL COMMENT '机构编码',

  `name` varchar(50) DEFAULT NULL COMMENT '机构名称',

  `deep` int(4) DEFAULT NULL COMMENT '机构所在层级',

  `parid` int(10) DEFAULT NULL COMMENT '父级id',

  `sort` int(4) DEFAULT NULL COMMENT '部门顺序',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

通过insert语句初始化数据:

INSERT INTO `dept` VALUES ('1', '01', '根目录', '1', '0', '1');

INSERT INTO `dept` VALUES ('2', '0101', '子一', '2', '1', '1');

INSERT INTO `dept` VALUES ('3', '0102', '子二', '2', '1', '2');

INSERT INTO `dept` VALUES ('4', '0103', '子三', '2', '1', '3');

INSERT INTO `dept` VALUES ('5', '010201', '孙一', '3', '3', '1');

INSERT INTO `dept` VALUES ('6', '010202', '孙二', '3', '3', '2');

INSERT INTO `dept` VALUES ('7', '010301', '孙三', '3', '4', '1');

INSERT INTO `dept` VALUES ('8', '01020101', '孙孙一', '4', '5', '1');

INSERT INTO `dept` VALUES ('9', '0102010101', 'sun孙孙一', '5', '8', '1');

INSERT INTO `dept` VALUES ('10', '0104', '子四', '2', '1', '4');

INSERT INTO `dept` VALUES ('11', '01020201', '孙孙二', '4', '6', '1');

使用sql语句 select * from dept order by deep desc, parid desc; 查询的结果如下所示

QQ截图20220810222126.png

通过数据可以很容易看出这是一个深度为5的树,即组织机构只有一个根目录,存在5级部门,同时通过id和parid可以很容易判断出部门之间的父子关系,或者通过code字段的编码规则也可以得出结论(本篇文章没有用到code字段)。正是通过这条sql语句查询出全部的组织机构数据,同时制定了排序规则,使我们拿到的是一个按层级和父级节点分好类的一组数据,下面将通过代码,将已经完成分类的数据整合成有层次的json字符串结构。

数据返回的顺序是从最底层到最顶层,而通过parid字段可以找到父级机构,所以我将这个过程称为“找爸爸”,也就是找到每个数据的父级节点数据,并将自己作为父级节点的子级数据嵌套进去。当然了,如果仅仅是当前节点“找爸爸”还不够,正确的做法应该是“带着儿子找爸爸”,或者说是“拖家带口找爸爸”,也就是当前节点应该先获取到所有的子孙数据完成层次的构造,再将自己作为父级节点的子级节点嵌套进去。

构造的实体类如下(这里省略了get和set方法):

public class Dept {

    private Integer id;

    private String code;

    private String name;

    private Integer deep;

    private Integer parid;

    private Integer sort;

    private List<Dept> child;

}

示例代码如下,mapper中使用的sql语句就是上文中举的例子:

List<Dept> deptList = ctmDeptMapper.findAll();
//记录已经处理过的节点所处的层级,初始化为-1
int initDeep = -1;
//记录已经处理过的节点的父级机构id,初始化为-1
int initParId = -1;

List<Dept> depts = new ArrayList<>();
//结果缓存数据,用于记录机构id,以及此机构对应的子级机构列表,最后的结果数据也会缓存到这里
Map<Integer, List<Dept>> listByParid = new HashMap<>();

//将第一个节点初始化到结果缓存中,否则会丢失该数据
Dept firstDept = deptList.get(0);
List<Dept> initDeptList = new ArrayList<>();
initDeptList.add(firstDept);
listByParid.put(firstDept.getParid(), initDeptList);

//带着儿子找爸爸
for(Dept dept : deptList){
    int id = dept.getId();
    int parid = dept.getParid();
    int deep = dept.getDeep();

    if(listByParid.containsKey(id)){
        //当前节点存在若干子节点
        //在hashmap中获取所有
        List<Dept> childList = listByParid.get(id);
        //将该节点的子树维护到当前数据中
        dept.setChild(childList);
    }

    if(deep != initDeep){
        //进入新的层级
        if(!depts.isEmpty()){
            //当前节点处于新的层级,说明depts中的数据就是一个完整的子树

            List<Dept> oldChildren = new ArrayList<>(depts);
            //这个子树的根节点就是上一次循环中访问的节点
            //所以这个完整的字树的父级id也就是上一个节点的父级id,该数据缓存在initParId中,将父级id和子树放入缓存
            listByParid.put(initParId, oldChildren);
            //进入新的层级,那么一定有新的孩子,需要初始化一个list
            depts = new ArrayList<>();
        }
    }else{
        if(parid != initParId){
            //当前节点位于新的分支,则说明depts中的数据是一个完整的子树,与进入新的层级时处理方法一致
            List<Dept> oldChildren = new ArrayList<>(depts);

            listByParid.put(initParId, oldChildren);
            depts = new ArrayList<>();
        }
    }
    depts.add(dept);
    initDeep = deep;
    initParId = parid;
}

List<Dept> result = new ArrayList<>();
//按照上述方法,会有若干节点缓存到名为depts的list中(亲兄弟队列)
//若只有一个节点,说明这个节点是根节点
//若存在若干节点,说明这些节点是兄弟关系,他们有一个共同的虚根节点(组织机构为森林结构)
int size = depts.size();

if(size == 1){
    Dept dept = depts.get(0);
    int deptId = dept.getId();

    List<Dept> childList = listByParid.get(deptId);
    if(childList != null && !childList.isEmpty()){
        dept.setChild(listByParid.get(deptId));
    }

    result.add(dept);
}else{
    result.addAll(depts);
}

return gson.toJson(result);

返回结果展示:

[{"id":1,"code":"01","name":"根目录","deep":1,"parid":0,"sort":1,"child":[{"id":2,"code":"0101","name":"子一","deep":2,"parid":1,"sort":1},{"id":3,"code":"0102","name":"子二","deep":2,"parid":1,"sort":2,"child":[{"id":5,"code":"010201","name":"孙一","deep":3,"parid":3,"sort":1,"child":[{"id":8,"code":"01020101","name":"孙孙一","deep":4,"parid":5,"sort":1,"child":[{"id":9,"code":"0102010101","name":"sun孙孙一","deep":5,"parid":8,"sort":1}]}]},{"id":6,"code":"010202","name":"孙二","deep":3,"parid":3,"sort":2,"child":[{"id":11,"code":"01020201","name":"孙孙二","deep":4,"parid":6,"sort":1}]}]},{"id":4,"code":"0103","name":"子三","deep":2,"parid":1,"sort":3,"child":[{"id":7,"code":"010301","name":"孙三","deep":3,"parid":4,"sort":1}]},{"id":10,"code":"0104","name":"子四","deep":2,"parid":1,"sort":4}]}]

相关文章

  • 14.5 结构和其他数据形式:传递结构

    输出示例 【结构作为参数传递并返回结构】 【传递结构的地址】

  • 软考架构师-知识点总结2

    架构风格 五大架构风格 数据流风格:批处理、管道-过滤器 调用-返回风格:主子程序、面向对象、层次结构 独立构件风...

  • 揭秘 | 为什么程序猿996会猝死,而企业家007却不会?

    来源:公众号程序员吴小胖 程序猿 企业家 程序猿 企业家 程序猿 企业家 程序猿 企业家 程序猿 企业家 程序猿 ...

  • Swift学习( 十一:构造过程(1))

    构造过程是使用类、结构体或枚举类型的实例之前的准备过程。通过定义构造器来实现构造过程。swift的构造器不需要返回...

  • go语言得到子程序的返回值

    go语言调用外部程序,并获取外部程序的返回值。 例子1: 程序返回0 运行结果 例子2:程序返回非零 运行结果 这...

  • C# 构造函数

    每当创建类或结构时,将会调用其构造函数。 类或结构可能具有采用不同参数的多个构造函数。 使用构造函数,程序员能够设...

  • swift中的构造器

    构造器会为程序中的类、结构体或枚举引入构造器。构造器使用关键字init来声明,有两种基本的形式。结构体、枚举、类可...

  • Day5.构造程序逻辑

    构造程序逻辑 分支和循环结构会帮助我们将程序中逻辑建立起来,将来我们的程序无论简单复杂,都是由顺序结构、分支结构、...

  • 深入理解计算机系统 第十二章 并发编程

    使用应 用级并 发的应 用程序 称为并 发程序。 现代 操作系 统提供了三种 基本的 构造并 发程序 的方法 : ...

  • 程序员必备表情包

    程序猿怒产品 : 程序猿不想和你说话,并… 被吐槽写BUG时怎么办 产品又来提需求 产品又要改需求,怎么办 产品说...

网友评论

      本文标题:【程序猿】返回全部组织机构并构造层次结构

      本文链接:https://www.haomeiwen.com/subject/pdnzwrtx.html