首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 根據數據庫遞歸構建樹形 JSON:通用 TreeNode、構建算法與 FastJSON 序列化實戰

     編輯:Java 綜合教學

應用場景

  • 組織機構樹 (部門/崗位/人員)
  • 菜單權限樹 (多級路由)
  • 分類目錄樹 (電商 SKU 分類、CMS 欄目)
  • 地區行政區劃 (省/市/區/街道)

數據表結構設計

CREATE TABLE sys_dept (
    id VARCHAR(64) PRIMARY KEY COMMENT '主鍵',
    parent_id VARCHAR(64) COMMENT '父節點ID,頂級為 0 或 NULL',
    name VARCHAR(100) COMMENT '節點名稱',
    sort INT DEFAULT 0 COMMENT '同級排序',
    status TINYINT DEFAULT 1 COMMENT '1啟用 0禁用'
);

通用 TreeNode 實體 (支援任意業務數據泛型)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TreeNode<T> {
    private String id;                    // 節點唯一標識
    private String parentId;              // 父節點ID
    private String text;                  // 顯示文本
    private String state = "open";        // easyui/zTree 狀態: open/closed
    private boolean checked = false;      // 是否勾選
    private List<Map<String, Object>> attributes; // 擴展屬性
    private List<TreeNode<T>> children = new ArrayList<>(); // 子節點
    private boolean isParent = false;     // 是否父節點
    private boolean isChildren = false;   // 是否有子節點
    private T data;                       // 攜帶原始業務對象 (可選)
}

O(n) 時間複雜度構建算法 (HashMap 索引 + 單次遍歷)

public class TreeBuilder {
    public static <T> List<TreeNode<T>> build(List<TreeNode<T>> nodes, String rootPid) {
        if (nodes == null || nodes.isEmpty()) return Collections.emptyList();

        // 1. 建立 ID -> Node 映射 (O(n))
        Map<String, TreeNode<T>> nodeMap = new HashMap<>(nodes.size() * 2);
        nodes.forEach(node -> nodeMap.put(node.getId(), node));

        // 2. 掛載子節點 (O(n))
        List<TreeNode<T>> roots = new ArrayList<>();
        for (TreeNode<T> node : nodes) {
            String pid = node.getParentId();
            if (rootPid.equals(pid) || pid == null || pid.isEmpty()) {
                roots.add(node); // 頂級節點
            } else {
                TreeNode<T> parent = nodeMap.get(pid);
                if (parent != null) {
                    parent.getChildren().add(node);
                    parent.setParent(true);
                    node.setChildren(true); // 標記父子關係
                } else {
                    // 父節點不存在 (臟數據),視為根節點或拋異常
                    roots.add(node);
                }
            }
        }

        // 3. 遞歸排序子節點 (可選)
        roots.forEach(TreeBuilder::sortChildrenRecursively);
        return roots;
    }

    private static <T> void sortChildrenRecursively(TreeNode<T> node) {
        node.getChildren().sort(Comparator.comparingInt(n -> n.getData() != null ? ((Sortable) n.getData()).getSort() : 0));
        node.getChildren().forEach(TreeBuilder::sortChildrenRecursively);
    }
}

Service 層組裝調用

@Service
public class DeptServiceImpl implements DeptService {
    @Autowired private DeptMapper deptMapper;

    @Override
    public List<TreeNode<Dept> tree() {
        // 1. 查詢所有啟用部門
        List<Dept> depts = deptMapper.selectEnabled(); // SELECT * FROM sys_dept WHERE status=1

        // 2. 轉換為 TreeNode
        List<TreeNode<Dept>> nodes = depts.stream().map(d -> {
            TreeNode<Dept> node = new TreeNode<>();
            node.setId(d.getId());
            node.setParentId(d.getParentId());
            node.setText(d.getName());
            node.setData(d); // 攜帶原始對象,前端可能需要額外字段
            return node;
        }).collect(Collectors.toList());

        // 3. 構建樹
        return TreeBuilder.build(nodes, "0"); // 假設頂級 parent_id = '0'
    }
}

Controller 返回 JSON (FastJSON / Jackson 均可)

@RestController
@RequestMapping("/api/dept")
public class DeptController {
    @Autowired private DeptService deptService;

    @GetMapping("/tree")
    public List<TreeNode<Dept>> tree() {
        return deptService.tree(); // 直接返回 List,框架自動序列化為 JSON 數組
    }
}

前端渲染示例 (zTree / Ant Design Tree / Element Tree)

// zTree 配置
var setting = {
    data: { simpleData: { enable: true, idKey: "id", pIdKey: "parentId", rootPId: "0" } },
    callback: { onClick: function(e, treeId, treeNode) { console.log(treeNode.id); } }
};
$.fn.zTree.init($("#tree"), setting, treeData); // treeData 為後端返回的 List

輸出 JSON 結構示例

[
  {
    "id": "1",
    "parentId": "0",
    "text": "技術部",
    "state": "open",
    "checked": false,
    "isParent": true,
    "children": [
      {
        "id": "11",
        "parentId": "1",
        "text": "Java 組",
        "isParent": false,
        "children": []
      }
    ]
  },
  {
    "id": "2",
    "parentId": "0",
    "text": "產品部",
    "isParent": false,
    "children": []
  }
]

進階優化與避坑

  1. 大數據量分頁加載:前端按需加載 (lazy load),後端提供 /tree/children?parentId=xxx 接口。
  2. 循環引用檢測:構建前用 DFS 檢測是否存在環 (父子關係形成環),防止死循環/棧溢出。
  3. 權限過濾:構建樹時根據用戶角色過濾不可見節點,或前端按權限控制顯示。
  4. 序列化深度限制:Jackson 配置 MapperFeature.MAX_NESTING_DEPTH 防止極深樹導致 StackOverflowError。
  5. 數據庫遞歸查詢:MySQL 8.0+ 支持 WITH RECURSIVE CTE 單條 SQL 查詢全樹,避免多次 round-trip。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved