權限選單、組織架構、分類目錄等典型樹形數據:單表自關聯(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<>(); // 遞歸子節點
}
<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 |
ofType="PrivilegeNode" | 集合泛型類型(區別於單對象的 javaType) |
column="id" | 將當前行的 id 作為參數傳給子查詢 #{id} |
select="selectChildren" | 子查詢 statement ID |
selectRoots 獲取根節點列表collection 映射selectChildren,參數 parent_id = 根節點.idnodeMap,形成遞歸,直到葉子節點(無子查詢結果)上述方案會產生 1 + N + N² + ... 條 SQL(N = 節點數)。優化路徑:
<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、應用層可控。缺點:全表掃描、記憶體佔用大。
<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 讀取樹形數據三大模式:
根據數據量級、樹深度、更新頻率選型;中大型系統建議「單查詢 + 應用層組裝 + Redis 緩存」。