企业级Java开发树结构数据封装(开发必用)

985 阅读3分钟

前言

在日常搬砖中,少不了要写树结构的数据,而且各个企业的树结构表,也建的五花八门,基本都差不多,这篇我们就来封装这样的树结构数据,不管什么门道,通通搞定。


一、树结构表模式

树结构表模式:不用说就明白,也是最常见结构,如下图:

在这里插入图片描述 建表结构: 在这里插入图片描述 必备三字段:

{"id":"主键","name":"节点名称","pid":"父节点"}

二、树结构案例

下面要说的就是我们常见的单表树结构数据的封装:

2.1 原生Java递归循环实现

2.1.1 创建实例对象

/**
 * @description: 树结构实体类
 * @author: DT
 * @date: 2021/5/7 20:23
 * @version: v1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TreeEntity {

    /**
     * 主键id
     */
    private Long id;
    /**
     * 节点名称
     */
    private String name;
    /**
     * 父节点
     */
    private Long parentId;
    /**
     * 子节点集合
     */
    private List<TreeEntity> children = new ArrayList<>();

    public TreeEntity(Long id, String name, Long parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }
}

2.1.2 编写测试类

/**
 * @description: 构建树结构数据返回
 * @author: DT
 * @date: 2021/5/7 20:26
 * @version: v1.0
 */
public class TestTreeBuild {

    public static void main(String[] args) {

        List<TreeEntity> list = new ArrayList<>();
        list.add(new TreeEntity(1L,"贵州省",0L));
        list.add(new TreeEntity(2L,"贵阳市",1L));
        list.add(new TreeEntity(3L,"四川省",0L));
        list.add(new TreeEntity(4L,"成都市",3L));
        list.add(new TreeEntity(5L,"云南省",0L));

        // 构建树结构数据返回
        List<TreeEntity> treeEntityList = buildTree(list);

        // 输出json树结构数据
        System.out.println(JSONUtil.parseArray(treeEntityList));
    }

    /**
     * 构建树结构数据
     */
    public static List<TreeEntity> buildTree(List<TreeEntity> entities) {

        // 最终输出树结构list集合
        List<TreeEntity> returnList = new ArrayList<>();

        // 存放所有id集合
        List<Long> tempList = new ArrayList<>();
        for (TreeEntity entity : entities) {
            tempList.add(entity.getId());
        }

        // 遍历循环实体表list集合
        for (TreeEntity treeEntity : entities) {
            // 如果是顶级节点, 遍历该父节点的所有子节点
            if (!tempList.contains(treeEntity.getParentId())) {
                recursionFunction(entities, treeEntity);
                returnList.add(treeEntity);
            }
        }
        if (returnList.isEmpty()) {
            returnList = entities;
        }
        return returnList;
    }

    /**
     * 递归遍历
     */
    private static void recursionFunction(List<TreeEntity> entities, TreeEntity treeEntity) {
        // 得到子节点列表
        List<TreeEntity> childList = getChildList(entities, treeEntity);
        treeEntity.setChildren(childList);
        for (TreeEntity tChild : childList) {
            if (hasChild(entities, tChild)) {
                recursionFunction(entities, tChild);
            }
        }
    }

    /**
     * 得到子节点列表
     */
    private static List<TreeEntity> getChildList(List<TreeEntity> entities, TreeEntity treeEntity) {
        List<TreeEntity> arrayList = new ArrayList<>();
        for (TreeEntity n : entities) {
            if (null != n.getParentId() && n.getParentId().longValue() == treeEntity.getId().longValue()) {
                arrayList.add(n);
            }
        }
        return arrayList;
    }

    /**
     * 判断是否有下级节点
     */
    private static boolean hasChild(List<TreeEntity> list, TreeEntity t) {
        return getChildList(list, t).size() > 0;
    }

}

2.1.3 返回Json结果集

[
  {
    "parentId": 0,
    "children": [
      {
        "parentId": 1,
        "children": [
          
        ],
        "name": "贵阳市",
        "id": 2
      }
    ],
    "name": "贵州省",
    "id": 1
  },
  {
    "parentId": 0,
    "children": [
      {
        "parentId": 3,
        "children": [
          
        ],
        "name": "成都市",
        "id": 4
      }
    ],
    "name": "四川省",
    "id": 3
  },
  {
    "parentId": 0,
    "children": [
      
    ],
    "name": "云南省",
    "id": 5
  }
]

2.2 使用Jdk的Stream流实现

2.2.1 创建实例对象

实例对象同上TreeEntity

2.2.2 编写测试类

public static void main(String[] args) {

     List<TreeEntity> list = new ArrayList<>();
     list.add(new TreeEntity(1L,"贵州省",0L));
     list.add(new TreeEntity(2L,"贵阳市",1L));
     list.add(new TreeEntity(3L,"四川省",0L));
     list.add(new TreeEntity(4L,"成都市",3L));
     list.add(new TreeEntity(5L,"云南省",0L));

     // 构建树结构数据返回
     List<TreeEntity> levelMenus = list.stream()
             .filter(p -> p.getParentId().equals(0L))
             .peek(treeEntity -> treeEntity.setChildren(buildTree(treeEntity, list))).collect(Collectors.toList());

     // 输出json树结构数据
     System.out.println(JSONUtil.toJsonStr(levelMenus));
 }

 /**
  * 构建树结构数据
  */
 private static List<TreeEntity> buildTree(TreeEntity treeEntity, List<TreeEntity> list) {
     List<TreeEntity> children = list.stream().filter(p -> p.getParentId().equals(treeEntity.getId()))
             .peek(menu -> menu.setChildren(buildTree(menu, list)))
             .collect(Collectors.toList());
     return children;
 }

输出结果和上面一个还是一样的,这种方式我们节省了很多代码。

我们再继续改造版本,尝试不同的写法

public static void main(String[] args) {

    List<TreeEntity> list = new ArrayList<>();
    list.add(new TreeEntity(1L,"贵州省",0L));
    list.add(new TreeEntity(2L,"贵阳市",1L));
    list.add(new TreeEntity(3L,"四川省",0L));
    list.add(new TreeEntity(4L,"成都市",3L));
    list.add(new TreeEntity(5L,"云南省",0L));

    // 构建树结构数据返回
    List<TreeEntity> treeEntityList = null;
    if(list.size() > 0){
        treeEntityList =  buildTree(list,0L);
    }
    // 输出json树结构数据
    System.out.println(JSONUtil.toJsonStr(treeEntityList));
}

/**
 * 构建树结构数据
 */
public static List<TreeEntity> buildTree(List<TreeEntity> list, Long pid){
    // 1.查询所有的pid的子类
    List<TreeEntity> children = list.stream().filter(x -> x.getParentId().equals(pid)).collect(Collectors.toList());
    // 2.查询所有的非pid的子类
    List<TreeEntity> successor = list.stream().filter(x -> !x.getParentId().equals(pid)).collect(Collectors.toList());
    if(children.size() > 0){
        children.forEach(x -> {
            if(successor.size() > 0){
                buildTree(successor,x.getId()).forEach(
                        y -> x.getChildren().add(y)
                );
            }
        });
    }
    return children;
}

输出结果和上面一个还是一样的,这种方式我可以指定我们父节点的值。

3.3 使用MyBatis的递归循环

3.1.1 创建表

在这里插入图片描述

3.1.2 创建实例对象

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("t_tree_entity")
public class TreeEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private String name;

    private Long parentId;

    @TableField(exist = false)
    private List<TreeEntity> children = new ArrayList<>();

    public TreeEntity(Long id, String name, Long parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }
}

这里我使用了MybatisPlus快速生成我的业务代码:@TableField(exist = false) 如果你的是MyBatis该注解不用管。

3.1.3 编写API接口类

@RestController
@RequestMapping("/api/v1/tree")
public class TreeEntityController {

    @Autowired
    private TreeEntityMapper treeEntityMapper;

    @GetMapping("/getByPid")
    public List<TreeEntity> getByPid(@RequestParam(value = "id",required = false) Integer id){
        List<TreeEntity> list = treeEntityMapper.getByPid(id);
        return list;
    }
}

3.1.4 编写MyBatis数据层

<!--根据id获取其子节点信息-->
<resultMap id="BaseResultMap" type="com.dt.springbootdemo.entity.TreeEntity">
    <id column="id" property="id"/>
    <result column="parent_id" property="parentId"/>
    <result column="name" property="name"/>
</resultMap>

<resultMap id="ExtendResultMap" type="com.dt.springbootdemo.entity.TreeEntity" extends="BaseResultMap">
    <collection property="children" ofType="com.dt.springbootdemo.entity.TreeEntity"
                select="getByPid" column="id"/>
</resultMap>

<select id="getByPid" resultMap="ExtendResultMap">
    SELECT * FROM t_tree_entity WHERE parent_id = #{pId,jdbcType=INTEGER}
</select>

3.1.5 返回Json结果集

在这里插入图片描述

总结

递归算法的核心思想是通过将问题重复分解为同类的或其子问题的方式,从而可以使用统一的解决方式。很多编程语言支持方法或函数自我调用,简单的说,就是在函数或方法体内,自身可以再次调用自身的方法结构。