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

MyBatis 實現樹形結構數據讀取:遞歸關聯映射實戰

     編輯:Java 相關知識

業務場景

權限選單、組織架構、分類目錄等典型樹形數據:單表自關聯(parent_id 指向同表 id),需一次查詢構建完整樹。

表結構

CREATE TABLE lscrm_function_privilege (
  id INT PRIMARY KEY AUTO_INCREMENT,
  code VARCHAR(50),           -- 編碼:WEB.PERMISSION.USER-MGMT.ADD
  function_name VARCHAR(50),  -- 顯示名
  parent_id INT DEFAULT 0,    -- 父節點 ID,0 為根
  is_leaf_node TINYINT,       -- 1=葉子,0=父節點
  sub_system_id INT,          -- 子系統
  is_hidden TINYINT           -- UI 隱藏
);

實體類設計

class PrivilegeNode {
    int id, parentId, isLeafNode, subSystemId, isHidden;
    String code, functionName;
    List children = new ArrayList<>(); // 遞歸子節點
}

MyBatis 映射核心:resultMap 遞歸引用

<resultMap id="nodeMap" type="PrivilegeNode">
  <id property="id" column="id"/>
  <result property="code" column="code"/>
  <result property="functionName" column="function_name"/>
  <result property="parentId" column="parent_id"/>
  <result property="isLeafNode" column="is_leaf_node"/>
  <!-- 其他屬性... -->
  
  <!-- 關鍵:collection 遞歸引用自身 resultMap -->
  <collection property="children" ofType="PrivilegeNode" 
              column="id" select="selectChildren"/>
</resultMap>

<select id="selectRoots" resultMap="nodeMap">
  SELECT * FROM lscrm_function_privilege WHERE parent_id = 0
  <if test="subSystemId > 0"> AND sub_system_id = #{subSystemId} </if>
</select>

<select id="selectChildren" resultMap="nodeMap">
  SELECT * FROM lscrm_function_privilege WHERE parent_id = #{id}
</select>

關鍵屬性解析

屬性作用
property="children"映射到 Java 的 List children
ofType="PrivilegeNode"集合泛型類型(區別於單對象的 javaType)
column="id"將當前行的 id 作為參數傳給子查詢 #{id}
select="selectChildren"子查詢 statement ID

執行流程

  1. 調用 selectRoots 獲取根節點列表
  2. 對每個根節點,MyBatis 檢測 collection 映射
  3. 執行 selectChildren,參數 parent_id = 根節點.id
  4. 子查詢同樣使用 nodeMap,形成遞歸,直到葉子節點(無子查詢結果)
  5. 自動組裝完成樹結構返回

效能優化:N+1 問題與解決方案

上述方案會產生 1 + N + N² + ... 條 SQL(N = 節點數)。優化路徑:

方案一:單 SQL + 應用層組裝(推薦中小數據量)

<select id="selectAll" resultType="PrivilegeNode">
  SELECT * FROM lscrm_function_privilege 
  WHERE sub_system_id = #{subSystemId} ORDER BY parent_id, id
</select>
// Service 層組裝樹
public List buildTree(List all) {
    Map map = all.stream()
        .collect(Collectors.toMap(PrivilegeNode::getId, n -> n));
    List roots = new ArrayList<>();
    for (PrivilegeNode node : all) {
        if (node.getParentId() == 0) {
            roots.add(node);
        } else {
            PrivilegeNode parent = map.get(node.getParentId());
            if (parent != null) parent.getChildren().add(node);
        }
    }
    return roots;
}

優點:單查詢、無 N+1、應用層可控。缺點:全表掃描、記憶體佔用大。

方案二:MyBatis 嵌套結果映射(單 SQL 聯表)

<resultMap id="treeMap" type="PrivilegeNode">
  <id property="id" column="id"/>
  <result property="functionName" column="function_name"/>
  <collection property="children" ofType="PrivilegeNode" 
              resultMap="treeMap" /> <!-- 直接引用自身,無 select -->
</resultMap>

<select id="selectTree" resultMap="treeMap">
  SELECT p.id, p.function_name, c.id AS c_id, c.function_name AS c_function_name
  FROM lscrm_function_privilege p
  LEFT JOIN lscrm_function_privilege c ON p.id = c.parent_id
  WHERE p.parent_id = 0
</select>

僅支援兩層,多層需多次 LEFT JOIN,SQL 複雜度指數增長。

緩存策略

<cache eviction="FIFO" flushInterval="60000" size="1024" readOnly="true"/>
  • 樹形選單讀多寫少,極適合二級緩存
  • readOnly="true" 返回共享實例,效能最高(不可修改)

總結

MyBatis 讀取樹形數據三大模式:

  1. 遞歸子查詢:配置簡單、代碼少,但 N+1 問題嚴重,適合節點少、深度淺
  2. 單查詢 + 應用層組裝:效能最優、最靈活,推薦生產環境首選
  3. 嵌套結果映射:單 SQL 但僅適合固定淺層級

根據數據量級、樹深度、更新頻率選型;中大型系統建議「單查詢 + 應用層組裝 + Redis 緩存」。

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