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

遞歸讀取資料庫建構樹狀選單:JavaBean 完整實作指南

     編輯:Java 相關知識

需求背景

Web 應用中常見的樹狀選單(如系統功能表、分類目錄),資料通常儲存在關聯式資料庫的單一資料表中,採用遞歸關聯設計(父子關係)。本文提供一個完整的 JavaBean 實作,示範如何一次性讀取所有資料至記憶體集合,再以遞歸演算法輸出巢狀 HTML 結構,避免重複查詢資料庫,提升效能。

資料表結構範例

CREATE TABLE sp_sys_menu_item (
    id VARCHAR(32) PRIMARY KEY,
    name VARCHAR(100),
    parent_id VARCHAR(32)  -- '0' 表示根節點
);

資料庫連線工具類:DBConnection

package dbmanager;

import java.sql.*;

public class DBConnection {
    private String user = "sa";
    private String password = "sa";
    private String driver = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
    private String url = "jdbc:microsoft:sqlserver://172.16.204.10:1433;DatabaseName=HLSP_MIS";

    private Connection conn = null;
    private Statement stmt = null;
    private Statement stmtScroll = null;

    public DBConnection() throws Exception {
        Class.forName(driver);
        conn = DriverManager.getConnection(url, user, password);
        stmt = conn.createStatement();
        stmtScroll = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
    }

    public ResultSet executeQuery(String sql) throws SQLException {
        return stmtScroll.executeQuery(sql);
    }

    public boolean executeUpdate(String sql) throws SQLException {
        return stmt.executeUpdate(sql) > 0;
    }

    public void close() throws SQLException {
        if (stmt != null) stmt.close();
        if (stmtScroll != null) stmtScroll.close();
        if (conn != null) conn.close();
    }
}

樹狀選單 JavaBean:Tree 類別

package treeMenu;

import java.sql.*;
import java.util.*;
import dbmanager.DBConnection;
import javax.servlet.jsp.JspWriter;

public class Tree {
    private DBConnection conn;

    // 快取所有節點資料
    private ArrayList allIds = new ArrayList<>();
    private ArrayList allNames = new ArrayList<>();
    private ArrayList allParentIds = new ArrayList<>();
    // 快取第一層節點 ID
    private ArrayList rootIds = new ArrayList<>();

    public Tree() throws Exception {
        conn = new DBConnection();
    }

    /** 初始化:一次性載入所有選單項目至記憶體 */
    public void buildTreeInit() throws SQLException {
        ResultSet rs = conn.executeQuery("SELECT id, name, parent_id FROM sp_sys_menu_item");
        while (rs.next()) {
            String id = rs.getString("id");
            String name = rs.getString("name");
            String parentId = rs.getString("parent_id");

            allIds.add(id);
            allNames.add(name);
            allParentIds.add(parentId);

            if ("0".equals(parentId)) {
                rootIds.add(id);
            }
        }
        conn.close();
    }

    /** 遞歸輸出子樹
     * @param out JspWriter 輸出流
     * @param parentId 父節點 ID
     * @param level 階層深度(用於縮排顯示)
     */
    public void buildTree(JspWriter out, String parentId, int level) throws Exception {
        level++;
        String indent = "  ";
        String prefix = "|";
        for (int i = 0; i < level; i++) {
            prefix += "--";
            indent += "  ";
        }

        // 表格列模板
        String rowTpl = "" + indent + prefix + " "
                + " 修改 "
                + " 註冊下級選單 "
                + " "
                + "";

        int idx = allParentIds.indexOf(parentId);
        while (idx != -1) {
            String nodeName = allNames.get(idx);
            String nodeId = allIds.get(idx);

            // 從快取中移除已處理節點,避免重複遍歷
            allParentIds.remove(idx);
            allIds.remove(idx);
            allNames.remove(idx);

            boolean hasChild = hasChild(nodeId);
            String delBtn = hasChild ? "" : "刪除";

            out.print(rowTpl.replace("", nodeName)
                    .replace("", nodeId)
                    .replace("", delBtn));

            if (hasChild) {
                buildTree(out, nodeId, level); // 遞歸處理子節點
            }

            idx = allParentIds.indexOf(parentId); // 繼續尋找同層下一個
        }
    }

    /** 判斷節點是否有子節點 */
    private boolean hasChild(String parentId) {
        return allParentIds.indexOf(parentId) != -1;
    }

    /** 輸出完整樹狀結構(含第一層) */
    public void showTree(JspWriter out) throws Exception {
        buildTreeInit();

        String rootRowTpl = "  "
                + " 修改 "
                + " 註冊下級選單 "
                + " "
                + "";

        for (int i = 0; i < rootIds.size(); i++) {
            String rootId = rootIds.get(i);
            String rootName = allNames.get(allIds.indexOf(rootId));

            boolean hasChild = hasChild(rootId);
            String delBtn = hasChild ? "" : "刪除";

            out.print(rootRowTpl.replace("", rootName)
                    .replace("", rootId)
                    .replace("", delBtn));

            if (hasChild) {
                buildTree(out, rootId, 0);
            }
        }
    }
}

JSP 頁面調用範例

<%@ page import="treeMenu.Tree" %>
<%@ page import="javax.servlet.jsp.JspWriter" %>

<%
    Tree tree = new Tree();
    tree.showTree(out);
%>

關鍵設計重點解析

  1. 一次性載入、記憶體遞歸:buildTreeInit() 只查詢一次資料庫,將所有節點存入 ArrayList,後續遞歸完全在記憶體操作,極大減少資料庫連線開銷。
  2. 就地移除已處理節點:buildTree() 中使用 remove(index) 移除已輸出的節點,確保同層節點不會被重複處理,也隱含了「已拜訪」標記的功能。
  3. 階層縮排視覺化:透過 level 參數動態產生 |-- 前綴與   縮排,直觀呈現樹狀結構。
  4. 彈性操作連結:每個節點輸出修改、新增子節點、刪除(僅葉子節點)超連結,直接整合後續 CRUD 功能。

效能與擴展考量

  • 資料量較大時,可考慮分頁載入或採用 Nested Set Model(修改頻率低時)。
  • 若需支援拖曳排序、勾選狀態保持等豐富互動,建議改用前端 JS 樹狀元件(如 zTree、jstree),後端提供 JSON 格式資料。
  • 生產環境請務必使用連線池(如 DBCP、HikariCP)取代簡易的 DBConnection。

結語

本文展示的「一次性查詢 + 記憶體遞歸 + 就地移除」模式,是處理資料庫樹狀結構輸出的經典高效做法。程式碼結構清晰、易於維護擴展,適合中小型專案直接採用或作為學習範本。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved