节点树的检索方式

194 阅读2分钟

实际开发中肯定会遇到需要查询节点树的情况。今天就为大家介绍几种查询节点树的方式。

表结构简单设计如下:

CREATE TABLE `market`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT ,
  `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT ,
  `parent_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT ,

) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '市场表' ROW_FORMAT = DYNAMIC;

<方式一> SQL语句的方式

xml中的节点Tree查询方法如下:

<mapper namespace="xxx.mapper对应的包路径">

    <resultMap id="BaseResultMap"
               type="对应实体的包路径">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="code" property="code" jdbcType="VARCHAR"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="parent_code" property="parentCode" jdbcType="VARCHAR"/>
    </resultMap>

    <!--映射当前节点的子节点结果-->
    <resultMap id="childrenNodeTreeResult"
               type="xxx.xxx"
               extends="BaseResultMap">
        <collection property="childNode" column="code"
                    ofType="com.springboot.example.mysqltree.model.entity.TreeTable" javaType="java.util.ArrayList"
                    select="nextNodeTree">
        </collection>
    </resultMap>
    
    <sql id="Base_Column_List">
        id,
        code,
        name,
        parent_code
    </sql>
    
    <!--查找下一个子节点-->
    <select id="nextNodeTree" resultMap="childrenNodeTreeResult">
        SELECT
            <include refid="Base_Column_List"/>
        FROM market 
        WHERE parent_code = #{code} 
    </select>
    
    <select id="getNodeTree" resultMap="childrenNodeTreeResult">
        SELECT
        <include refid="Base_Column_List"/>
        FROM market
        WHERE parent_code = '0'  # 此处的0代表顶级节点的code (比如全部下挂着一级分组 二级分组类似这样)
    </select>
</mapper>

<方式二> 代码控制

先查询到最外层的父节点,遍历父节点,递归找到每个父节点下的子节点并封装(此方法不宜层级太多的情况,会产生性能问题)

#找到最外层父节点列表
List<Market> nodeTree = marketMapper.selectList(......参数自己写哦);

// 递归找到每个父节点下的子节点
nodeTree.forEach(this::findAllChild);

public void findAllChildNodes(Market market) {
        List<Market> resources = marketMapper
                .selectList(new LambdaQueryWrapper<Market>().eq(Market::getGroupId, market.getCode()));
                        
        market.setChildNode(resources);
        if (!CollectionUtils.isEmpty(resources)) {
            resources.forEach(this::findAllChild);
        }
    }


<方式三 -推荐> 在内存中做数据的组装

相比于上面两种方式,更推荐使用此方式。减少数据库资源浪费的同时,提升了性能。

image.png

如图所示,思路很简单,我们以顶级节点一1为例子(如果有多个顶级节点也是同样的道理)。 先找出顶级节点1下的所有子节点(不包含自己),实际我们是拿出了数据库每一行的记录。然后根据父节点分组得到一个Map<key,value>, key = 父节点id, value= 父节点为key的子节点结合。 然后遍历每一行的记录,将每个节点下的子节点挂靠上,就结束了。

`// 获取当前根节点下的所有子节点 List allChildrenNodeList = nodeRepository.list(new QueryWrapper().lambda() .eq(Node::getRootId, node.getId()) .ne(CommonEntity::getId, node.getId()));

           if (CollectionUtils.isEmpty(allChildrenNodeList)) {
               return;
           }


        Map<String, List<Node>> parentGroupList = allChildrenNodeList.stream()
                .filter(node ->
                        node.getParentId() != null)
                .collect(Collectors.groupingBy(Node::getParentCode));

        // 先挂靠父节点是根节点分组
        node.setChildren(parentGroupList.get(node.getId()));

        allChildrenNodeList.forEach(b ->
                b.setChildren(parentGroupList.get(b.getId())));`