Web 應用中常見的樹狀選單(如系統功能表、分類目錄),資料通常儲存在關聯式資料庫的單一資料表中,採用遞歸關聯設計(父子關係)。本文提供一個完整的 JavaBean 實作,示範如何一次性讀取所有資料至記憶體集合,再以遞歸演算法輸出巢狀 HTML 結構,避免重複查詢資料庫,提升效能。
CREATE TABLE sp_sys_menu_item (
id VARCHAR(32) PRIMARY KEY,
name VARCHAR(100),
parent_id VARCHAR(32) -- '0' 表示根節點
);
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();
}
}
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);
}
}
}
}
<%@ page import="treeMenu.Tree" %>
<%@ page import="javax.servlet.jsp.JspWriter" %>
<%
Tree tree = new Tree();
tree.showTree(out);
%>
buildTreeInit() 只查詢一次資料庫,將所有節點存入 ArrayList,後續遞歸完全在記憶體操作,極大減少資料庫連線開銷。buildTree() 中使用 remove(index) 移除已輸出的節點,確保同層節點不會被重複處理,也隱含了「已拜訪」標記的功能。level 參數動態產生 |-- 前綴與 縮排,直觀呈現樹狀結構。DBConnection。本文展示的「一次性查詢 + 記憶體遞歸 + 就地移除」模式,是處理資料庫樹狀結構輸出的經典高效做法。程式碼結構清晰、易於維護擴展,適合中小型專案直接採用或作為學習範本。