-
《專案實戰(影片)》:從書中學,往事上“練” -
《網際網路高頻面試題》:面朝簡歷學習,春暖花開 -
《架構 x 系統設計》:摧枯拉朽,掌控面試高頻場景題 -
《精進 Java 學習指南》:系統學習,網際網路主流技術棧 -
《必讀 Java 原始碼專欄》:知其然,知其所以然

-
Boot 倉庫:https://gitee.com/zhijiantianya/ruoyi-vue-pro -
Cloud 倉庫:https://gitee.com/zhijiantianya/yudao-cloud -
影片教程:https://doc.iocoder.cn

一、介紹
-
專案地址:https://github.com/YunaiV/ruoyi-vue-pro -
影片教程:https://doc.iocoder.cn/video/
介紹資料庫欄位設計
tree_path
欄位,這個需要根據設計者來定。-
如果你對資料的讀取操作比較頻繁,而且需要快速查詢某個節點的所有子節點或父節點,那麼使用 tree_path
欄位可以提高查詢效率。 -
tree_path
欄位可以使用路徑字串表示節點的層級關係,例如使用逗號分隔的節點ID列表。這樣,可以透過模糊匹配tree_path
欄位來查詢某個節點的所有子節點或父節點,而無需進行遞迴查詢。 -
你可以使用模糊匹配的方式,找到所有以該節點的 tree_path
開頭的子節點,並將它們刪除。而無需進行遞迴刪除。
-
每次插入時,需要更新tree_path 欄位,這可能會導致效能下降。 -
tree_path 欄位的長度可能會隨著樹的深度增加而增加,可能會佔用更多的儲存空間。
tree_path
欄位。如果你更關注寫入操作的效率和資料一致性,並且樹的深度不會很大,那麼使用父評論ID欄位來實現多級評論可能更簡單和高效。-
專案地址:https://github.com/YunaiV/yudao-cloud -
影片教程:https://doc.iocoder.cn/video/
二、統一工具類具體實現
1. 定義介面,統一規範
/**
*
@Description
: 固定屬性結構屬性
*
@Author
: yiFei
*/
publicinterfaceITreeNode<T>
{
/**
*
@return
獲取當前元素Id
*/
Object getId()
;
/**
*
@return
獲取父元素Id
*/
Object getParentId()
;
/**
*
@return
獲取當前元素的 children 屬性
*/
List<T> getChildren()
;
/**
* ( 如果資料庫設計有tree_path欄位可覆蓋此方法來生成tree_path路徑 )
*
*
@return
獲取樹路徑
*/
default Object getTreePath()
{
return""
; }
}
2. 編寫工具類TreeNodeUtil
tree_path
欄位
/**
*
@Description
: 樹形結構工具類
*
@Author
: yiFei
*/
publicclassTreeNodeUtil
{
privatestaticfinal
Logger log = LoggerFactory.getLogger(TreeNodeUtil
.class)
;
publicstaticfinal
String PARENT_NAME =
"parent"
;
publicstaticfinal
String CHILDREN_NAME =
"children"
;
publicstaticfinal
List<Object> IDS = Collections.singletonList(
0L
);
publicstatic
<T extends ITreeNode>
List<T> buildTree(List<T> dataList)
{
return
buildTree(dataList, IDS, (data) -> data, (item) ->
true
);
}
publicstatic
<T extends ITreeNode>
List<T> buildTree(List<T> dataList, Function<T, T> map)
{
return
buildTree(dataList, IDS, map, (item) ->
true
);
}
publicstatic
<T extends ITreeNode>
List<T> buildTree(List<T> dataList, Function<T, T> map, Predicate<T> filter)
{
return
buildTree(dataList, IDS, map, filter);
}
publicstatic
<T extends ITreeNode>
List<T> buildTree(List<T> dataList, List<Object> ids)
{
return
buildTree(dataList, ids, (data) -> data, (item) ->
true
);
}
publicstatic
<T extends ITreeNode>
List<T> buildTree(List<T> dataList, List<Object> ids, Function<T, T> map)
{
return
buildTree(dataList, ids, map, (item) ->
true
);
}
/**
* 資料集合構建成樹形結構 ( 注: 如果最開始的 ids 不在 dataList 中,不會進行任何處理 )
*
*
@param
dataList 資料集合
*
@param
ids 父元素的 Id 集合
*
@param
map 呼叫者提供 Function<T, T> 由呼叫著決定資料最終呈現形勢
*
@param
filter 呼叫者提供 Predicate<T> false 表示過濾 ( 注: 如果將父元素過濾掉等於剪枝 )
*
@param
<T> extends ITreeNode
*
@return
*/
publicstatic
<T extends ITreeNode>
List<T> buildTree(List<T> dataList, List<Object> ids, Function<T, T> map, Predicate<T> filter)
{
if
(CollectionUtils.isEmpty(ids)) {
return
Collections.emptyList();
}
// 1. 將資料分為 父子結構
Map<String, List<T>> nodeMap = dataList.stream()
.filter(filter)
.collect(Collectors.groupingBy(item -> ids.contains(item.getParentId()) ? PARENT_NAME : CHILDREN_NAME));
List<T> parent = nodeMap.getOrDefault(PARENT_NAME, Collections.emptyList());
List<T> children = nodeMap.getOrDefault(CHILDREN_NAME, Collections.emptyList());
// 1.1 如果未分出或過濾了父元素則將子元素返回
if
(parent.size() ==
0
) {
return
children;
}
// 2. 使用有序集合儲存下一次變數的 ids
List<Object> nextIds =
new
ArrayList<>(dataList.size());
// 3. 遍歷父元素 以及修改父元素內容
List<T> collectParent = parent.stream().map(map).collect(Collectors.toList());
for
(T parentItem : collectParent) {
// 3.1 如果子元素已經加完,直接進入下一輪迴圈
if
(nextIds.size() == children.size()) {
break
;
}
// 3.2 過濾出 parent.id == children.parentId 的元素
children.stream()
.filter(childrenItem -> parentItem.getId().equals(childrenItem.getParentId()))
.forEach(childrenItem -> {
// 3.3 這次的子元素為下一次的父元素
nextIds.add(childrenItem.getParentId());
// 3.4 新增子元素到 parentItem.children 中
try
{
parentItem.getChildren().add(childrenItem);
}
catch
(Exception e) {
log.warn(
"TreeNodeUtil 發生錯誤, 傳入引數中 children 不能為 null,解決方法: \n"
+
"方法一、在map(推薦)或filter中初始化 \n"
+
"方法二、List<T> children = new ArrayList<>() \n"
+
"方法三、初始化塊對屬性賦初值\n"
+
"方法四、構造時對屬性賦初值"
);
}
});
}
buildTree(children, nextIds, map, filter);
return
parent;
}
/**
* 生成路徑 treePath 路徑
*
*
@param
currentId 當前元素的 id
*
@param
getById 使用者返回一個 T
*
@param
<T>
*
@return
*/
publicstatic
<T extends ITreeNode>
String generateTreePath(Serializable currentId, Function<Serializable, T> getById)
{
StringBuffer treePath =
new
StringBuffer();
if
(SystemConstants.ROOT_NODE_ID.equals(currentId)) {
// 1. 如果當前節點是父節點直接返回
treePath.append(currentId);
}
else
{
// 2. 呼叫者將當前元素的父元素查出來,方便後續拼接
T byId = getById.apply(currentId);
// 3. 父元素的 treePath + "," + 父元素的id
if
(!ObjectUtils.isEmpty(byId)) {
treePath.append(byId.getTreePath()).append(
","
).append(byId.getId());
}
}
return
treePath.toString();
}
}
TreeNodeUtil
統一工具類,首先我們將元素分為父子兩類,讓其構建出一個小型樹,然後我們將構建的子元素和下次遍歷的父節點傳入,遞迴的不斷進行,這樣就構建出了我們最終的想要實現的效果。三、測試
/**
*
@Description
: 測試子元素工具類
*
@Author
: yiFei
*/
@Data
@EqualsAndHashCode
(callSuper =
false
)
@Accessors
(chain =
true
)
@AllArgsConstructor
publicclassTestChildrenimplementsITreeNode<TestChildren>
{
private
Long id;
private
String name;
private
String treePath;
private
Long parentId;
publicTestChildren(Long id, String name, String treePath, Long parentId)
{
this
.id = id;
this
.name = name;
this
.treePath = treePath;
this
.parentId = parentId;
}
@TableField
(exist =
false
)
private
List<TestChildren> children =
new
ArrayList<>();
}
publicstaticvoidmain(String[] args)
{
List<TestChildren> testChildren =
new
ArrayList<>();
testChildren.add(
new
TestChildren(
1L
,
"父元素"
,
""
,
0L
));
testChildren.add(
new
TestChildren(
2L
,
"子元素1"
,
"1"
,
1L
));
testChildren.add(
new
TestChildren(
3L
,
"子元素2"
,
"1"
,
1L
));
testChildren.add(
new
TestChildren(
4L
,
"子元素2的孫子元素"
,
"1,3"
,
3L
));
testChildren = TreeNodeUtil.buildTree(testChildren);
System.out.println(JSONUtil.toJsonStr(Result.success(testChildren)));
}
{
"code"
:
"00000"
,
"msg"
:
"操作成功"
,
"data"
: [{
"id"
:
1
,
"name"
:
"父元素"
,
"treePath"
:
""
,
"parentId"
:
0
,
"children"
: [{
"id"
:
2
,
"name"
:
"子元素1"
,
"treePath"
:
"1"
,
"parentId"
:
1
,
"children"
: []
}, {
"id"
:
3
,
"name"
:
"子元素2"
,
"treePath"
:
"1"
,
"parentId"
:
1
,
"children"
: [{
"id"
:
4
,
"name"
:
"子元素2的孫子元素"
,
"treePath"
:
"1,3"
,
"parentId"
:
3
,
"children"
: []
}]
}]
}]
}
publicstaticvoidmain(String[] args)
{
List<TestChildren> testChildren =
new
ArrayList<>();
testChildren.add(
new
TestChildren(
1L
,
"父元素"
,
""
,
0L
));
testChildren.add(
new
TestChildren(
2L
,
"子元素1"
,
"1"
,
1L
));
testChildren.add(
new
TestChildren(
3L
,
"子元素2"
,
"1"
,
1L
));
testChildren.add(
new
TestChildren(
4L
,
"子元素2的孫子元素"
,
"1,3"
,
3L
));
testChildren = TreeNodeUtil.buildTree(testChildren);
System.out.println(JSONUtil.toJsonStr(Result.success(testChildren)));
}
{
"code"
:
"00000"
,
"msg"
:
"操作成功"
,
"data"
: [{
"id"
:
1
,
"name"
:
"父元素"
,
"treePath"
:
""
,
"parentId"
:
0
,
"children"
: [{
"id"
:
2
,
"name"
:
"子元素1"
,
"treePath"
:
"1"
,
"parentId"
:
1
,
"children"
: []
}, {
"id"
:
3
,
"name"
:
"子元素2"
,
"treePath"
:
"1"
,
"parentId"
:
1
,
"children"
: [{
"id"
:
4
,
"name"
:
"子元素2的孫子元素"
,
"treePath"
:
"1,3"
,
"parentId"
:
3
,
"children"
: []
}]
}]
}]
}
// 對 3L 進行剪枝,對 1L 進行修改
testChildren = TreeNodeUtil.buildTree(testChildren, (item) -> {
if
(item.getId().equals(
1L
)) {
item.setName(
"更改了 Id 為 1L 的資料名稱"
);
}
return
item;
}, (item) -> item.getId().equals(
3L
));
{
"code"
:
"00000"
,
"msg"
:
"操作成功"
,
"data"
: [{
"id"
:
1
,
"name"
:
"更改了 Id 為 1L 的資料名稱"
,
"treePath"
:
""
,
"parentId"
:
0
,
"children"
: [{
"id"
:
2
,
"name"
:
"子元素1"
,
"treePath"
:
"1"
,
"parentId"
:
1
,
"children"
: []
}]
}]
}
-
返回傳入的 testChildren
-
返回傳入的 testChildren
-
給出提示,不構建樹
-
返回路徑





