在Java应用中处理树形结构数据,特别是使用PostgreSQL数据库进行树形数据查询和组织的方法。为Java开发者提供了处理树形数据的实用解决方案,特别适合需要在应用中展示和操作树形结构的场景。

主要内容

  1. 数据结构设计

    • 定义了SysOrg类,包含id、name、parentId等字段
    • 添加了children字段用于存储子节点,hasChild表示是否有子节点
  2. SQL查询实现

    • 使用PostgreSQL的递归查询功能(WITH RECURSIVE)
    • 从指定组织ID开始,递归查询所有子节点
    • 支持按名称进行模糊查询
  3. 树形结构组装

    • 通过Map高效建立节点索引和子节点映射
    • 遍历所有节点,为每个节点设置children和hasChild属性
    • 采用O(n)时间复杂度的算法,避免递归嵌套
  4. 测试实现

    • 提供了Spring Boot的测试代码
    • 展示了如何查询组织树并组装成树形结构

应用场景

该方法适用于需要处理树形结构数据的场景,如:

  • 组织架构管理
  • 分类目录系统
  • 地理行政区划
  • 产品分类

优势

  • 使用PostgreSQL的递归查询,避免了在Java中进行递归查询的性能问题
  • 采用Map高效组装树形结构,时间复杂度为O(n)
  • 代码结构清晰,易于维护和扩展

一、数据结构

@Data
@Accessors(chain = true)
@TableName(value = "sys_org", autoResultMap = true)
public class SysOrg implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键id
     */
    private Long id;

    /**
     * 组织名称
     */
    private String name;

    /**
     * 父级id
     */
    private Long parentId;

    /**
     * 树层级 默认从1开始 表示根组织
     */
    private Integer treeLevel;

    /**
     * 树code
     */
    private String treeCode;

    /**
     * 排序  默认从1开始 表示本层级第一个
     */
    private Integer sort;

    /**
     * 层级
     */
    private Integer regionLevel;

    /**
     * 区划code
     */
    private String regionCode;

    /**
     * gis平台的系统id
     */
    private String gisSysId;

    /**
     * 创建人
     */
    private Long createUserId;

    /**
     * 创建时间
     */
    private LocalDateTime createDateTime;

    /**
     * 更新人
     */
    private Long updateUserId;

    /**
     * 更新时间
     */
    private LocalDateTime updateDateTime;

    @TableField(exist = false)
    private List<SysOrg> children;

    /**
     * 是否有子机构
     */
    @TableField(exist = false)
    private Boolean hasChild = false;
}

二、SQL

<select id="selectTreeNodes" resultType="com.dkchy.domain.entity.sys.SysOrg">
    WITH RECURSIVE org_tree AS (
    SELECT
    *
    FROM sys_org
    WHERE id = #{orgId}
    UNION ALL
    SELECT
    t.*
    FROM sys_org t
    INNER JOIN org_tree ot ON t.parent_id = ot.id
    <!-- 新增:name 模糊查询条件 -->
    <if test="name != null and name != ''">
        WHERE t.name LIKE #{name}
    </if>
    )
    SELECT * FROM org_tree
</select>

三、组装为树型

/**
  * 获取用户所在行政区划树(当前层级以及所有下级)
  *
  * @param req
  * @return
  */
@Override
public Result<SysOrg> orgTree(SysOrgPageReq req, LoginUserVO user) {
    if (ObjectUtils.isEmpty(req.getOrgId())) {
        Long userOrgId = sysOrgUserService.getUserOrgId(user.getUserId());
        if (ObjectUtils.isEmpty(userOrgId)) {
            return Result.paramError("当前用户所在单位未知");
        }
        SysOrg sysOrg = getById(userOrgId);
        if (ObjectUtils.isEmpty(sysOrg)) {
            return Result.paramError("当前用户所在单位不存在");
        }
        req.setOrgId(sysOrg.getId());
    }
    //TODO 实现查询组织树
    // 1. 构建 name 模糊查询条件
    String namePattern = null;
    if (StringUtils.isNotBlank(req.getName())) {
        namePattern = "%" + req.getName() + "%";
    }
    // 2. 查询组织树(递归查询 + name 模糊查询)
    List<SysOrg> allNodes = baseMapper.selectTreeNodes(req.getOrgId(), namePattern);
    if (CollectionUtils.isEmpty(allNodes)) {
        return Result.success(new SysOrg());
    }
    // 3. 构建树形结构(使用Map高效组装)
    Map<Long, SysOrg> nodeMap = new HashMap<>();
    Map<Long, List<SysOrg>> childrenMap = new HashMap<>();
    // 3.1 建立节点索引和子节点映射
    for (SysOrg node : allNodes) {
        nodeMap.put(node.getId(), node);
        childrenMap.computeIfAbsent(node.getParentId(), k -> new ArrayList<>()).add(node);
    }
    // 3.2 为每个节点设置children和hasChild
    for (SysOrg node : allNodes) {
        List<SysOrg> children = childrenMap.getOrDefault(node.getId(), Collections.emptyList());
        node.setChildren(children);
        node.setHasChild(!children.isEmpty());
    }
    // 获取根节点(必须包含在结果中)
    SysOrg root = nodeMap.get(req.getOrgId());
    if (root == null) {
        return Result.paramError(req.getOrgId() + "根节点不存在");
    }
    return Result.success(root);
}

四、测试

@SpringBootTest(classes = {DkNjDigitalStatistic.class})
@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("dev")
@Slf4j
public class OrgTest {

    @Resource
    private ISysOrgService sysOrgService;

    //@Test
    public void test001() {
        SysOrgPageReq req = new SysOrgPageReq();
        //req.setOrgId(530000L);
        //req.setOrgId(2014223650349527041L);
        req.setOrgId(2014223940687638530L);
        Result<SysOrg> orged = sysOrgService.orgTree(req, null);
        System.out.println("orged = " + orged);
        System.out.println("orged = " + orged);
        System.out.println("orged = " + orged);
        System.out.println("orged = " + orged);
    }
}