前言
日常开发中,树形结构的数据是比较常见的一种数据结构,比如系统菜单、组织机构、数据字典等,有时候需要后端把数据转成树形结构再返回给前端,对此特意封装通用树形结构工具类
封装了以下方法:
根据父id,递归获取所有子节点,转为树结构

根据子id,递归获取所有父节点,转为树结构

拼接 union sql脚本,根据查询查询条件、id字段名、pid字段名,拼接出sql

依赖hutool
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
完整代码
package cn.huanzi.qch.util;
import cn.hutool.core.bean.BeanUtil;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* 树形结构工具类
*/
public class TreeUtil{
/**
* 根据父id,递归获取所有子节点,转为树结构
*
* @param idFieldName id字段名称
* @param pIdFieldName pid字段名称
* @param childrenFieldName children字段名称
* @param pxFieldName px字段名称
* @param pId 父节点id
* @param allList 所有菜单列表
* @return 每个根节点下,所有子菜单列表
*/
public static <M> List<M> toTreeByParentId(String idFieldName, String pIdFieldName, String childrenFieldName, String pxFieldName,String pId, List<M> allList){
//子节点
List<M> childList = new ArrayList<>(allList.size());
for (int i = 0; i < allList.size(); i++) {
M model = allList.get(i);
//遍历所有节点将节点的父id与传过来的根节点的id比较
//父节点id字段,例如:pid
if (BeanUtil.getFieldValue(model,pIdFieldName).equals(pId)){
childList.add(model);
//删除,减少下次循环次数
allList.remove(i);
i--;
}
}
//递归
for (M model : childList) {
//主键字段,例如:id,子节点字段,例如:children
BeanUtil.setFieldValue(model,childrenFieldName,TreeUtil.toTreeByParentId(idFieldName,pIdFieldName,childrenFieldName,pxFieldName,String.valueOf(BeanUtil.getFieldValue(model,idFieldName)), allList));
}
//排序字段,例如:px,如果不需要排序可以注释
if(null != pxFieldName && !pxFieldName.isEmpty()){
childList.sort(Comparator.comparingInt(m -> Integer.parseInt(String.valueOf(BeanUtil.getFieldValue(m, pxFieldName))))); //排序
}
//底层节点的子节点赋空值,节省内存空间
if(childList.size() <= 0){
childList = null;
}
return childList;
}
public static <M> List<M> toTreeByParentId(String pId, List<M> allList){
//设置一下默认值
return TreeUtil.toTreeByParentId("id","pid","children","px",pId,allList);
}
/**
* 根据子id,递归获取所有父节点,转为树结构
*
* @param idFieldName id字段名称
* @param pIdFieldName pid字段名称
* @param childrenFieldName children字段名称
* @param cId 子节点id
* @param allList 所有菜单列表
* @return 每个根节点下,所有子菜单列表
*/
public static <M> M toTreeByChildrenId(String idFieldName, String pIdFieldName, String childrenFieldName,String cId, List<M> allList){
return TreeUtil.toTreeByChildrenId(idFieldName,pIdFieldName,childrenFieldName,null,cId,allList);
}
private static <M> M toTreeByChildrenId(String idFieldName, String pIdFieldName, String childrenFieldName,M parent,String cId, List<M> allList){
//父节点
M newParent = null;
for (int i = 0; i < allList.size(); i++) {
M model = allList.get(i);
// 相等说明:找出当前节点
if (BeanUtil.getFieldValue(model,idFieldName).equals(cId)){
newParent = model;
//设置子节点
if(parent != null){
ArrayList<M> childList = new ArrayList<>(1);
childList.add(parent);
BeanUtil.setFieldValue(newParent,childrenFieldName, childList);
}
//删除,减少下次循环次数
allList.remove(i);
i--;
break;
}
}
//父节点为空,则说明为顶层节点
String menuParentId = newParent == null ? "" : String.valueOf(BeanUtil.getFieldValue(newParent,pIdFieldName));
if("".equals(menuParentId)){
return parent;
}
//父节点递归
newParent = TreeUtil.toTreeByChildrenId(idFieldName,pIdFieldName,childrenFieldName,newParent,menuParentId,allList);
return newParent;
}
public static <M> M toTreeByChildrenId(String cId, List<M> allList){
//设置一下默认值
return TreeUtil.toTreeByChildrenId("id", "pid", "children",null,cId,allList);
}
/**
* 拼接 union sql脚本,根据查询查询条件、id字段名、pid字段名,拼接出sql
* @param select 查询字段,例如: select id
* @param tableName 表名,例如 sys_dept
* @param initWhere 查询条件,例如:pid = '-1'
* @param idField id字段名
* @param pidField pid字段名
* @param maxLevel 拼接最大层级
* @return union拼接好的sql脚本
*/
public static String getParentSql(String select, String tableName, String initWhere, String idField, String pidField, int maxLevel) {
return getUnionSql(select,tableName,initWhere,idField,pidField,maxLevel);
}
public static String getChildSql(String select, String tableName, String initWhere, String idField, String pidField, int maxLevel) {
return getUnionSql(select,tableName,initWhere,pidField,idField,maxLevel);
}
private static String getUnionSql(String select, String tableName, String initWhere, String whereIn, String selectIn, int maxLevel) {
StringBuilder stringBuilder = new StringBuilder(select);
stringBuilder.append(" from ").append(tableName).append(" where 1=1 ");
if (null != initWhere && !initWhere.isEmpty()) {
stringBuilder.append(" and ").append(initWhere);
}
String tmp = String.format("from %s where %s", tableName, initWhere);
for(int i = 0; i < maxLevel; ++i) {
tmp = String.format(" from %s where %s in ( select %s %s)", tableName, whereIn, selectIn, tmp);
stringBuilder.append(" union ").append(select).append(tmp);
}
return stringBuilder.toString();
}
}
TreeUtil
完整main测试
/**
* 测试
*/
public static void main(String[] args) {
ArrayList<Menu> list = new ArrayList<>();
list.add(new Menu("1","-1","系统管理",1));
list.add(new Menu("11","1","菜单维护",1));
list.add(new Menu("12","1","角色维护",2));
list.add(new Menu("13","1","系统安全",3));
list.add(new Menu("131","13","日志管理",1));
list.add(new Menu("12","-1","用户管理",2));
//备份list
List<Menu> list1 = BeanUtil.copyToList(list, Menu.class);
List<Menu> menus = TreeUtil.toTreeByParentId("-1", list);
System.out.println(menus);
Menu menu = TreeUtil.toTreeByChildrenId("131", list1);
System.out.println(menu);
String sql = TreeUtil.getChildSql("select id", "lp_sys_menu", "pid = '-1'", "id", "pid", 3);
System.out.println(sql);
}
/**
* 测试菜单类
*/
public static class Menu {
private String id; //节点id
private String pid; //父节点id
private List<Menu> children; //子节点
private int px; //排序字段
private String menuName; //菜单名称
public Menu() {
}
public Menu(String id, String pid, String name, int px) {
this.id = id;
this.pid = pid;
this.menuName = name;
this.px = px;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public List<Menu> getChildren() {
return children;
}
public void setChildren(List<Menu> children) {
this.children = children;
}
public int getPx() {
return px;
}
public void setPx(int px) {
this.px = px;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
@Override
public String toString() {
return "Menu{" +
"id='" + id + '\'' +
", pid='" + pid + '\'' +
", children=" + children +
", px=" + px +
", menuName='" + menuName + '\'' +
'}';
}
}
View Code
效果展示
[Menu{id='1', pid='-1', children=[Menu{id='11', pid='1', children=null, px=1, menuName='菜单维护'}, Menu{id='12', pid='1', children=null, px=2, menuName='角色维护'}, Menu{id='13', pid='1', children=[Menu{id='131', pid='13', children=null, px=1, menuName='日志管理'}], px=3, menuName='系统安全'}], px=1, menuName='系统管理'}, Menu{id='12', pid='-1', children=null, px=2, menuName='用户管理'}]
Menu{id='1', pid='-1', children=[Menu{id='13', pid='1', children=[Menu{id='131', pid='13', children=null, px=1, menuName='日志管理'}], px=3, menuName='系统安全'}], px=1, menuName='系统管理'}
select id from lp_sys_menu where 1=1 and pid = '-1' union select id from lp_sys_menu where pid in ( select id from lp_sys_menu where pid = '-1') union select id from lp_sys_menu where pid in ( select id from lp_sys_menu where pid in ( select id from lp_sys_menu where pid = '-1')) union select id from lp_sys_menu where pid in ( select id from lp_sys_menu where pid in ( select id from lp_sys_menu where pid in ( select id from lp_sys_menu where pid = '-1')))
原数据

根据父id,递归获取所有子节点,转为树结构

根据子id,递归获取所有父节点,转为树结构

拼接 union sql脚本,根据查询查询条件、id字段名、pid字段名,拼接出sql
SELECT
id
FROM
lp_sys_menu
WHERE
1 = 1
AND pid = '-1'
UNION
SELECT
id
FROM
lp_sys_menu
WHERE
pid IN (
SELECT
id
FROM
lp_sys_menu
WHERE
pid = '-1'
)
UNION
SELECT
id
FROM
lp_sys_menu
WHERE
pid IN (
SELECT
id
FROM
lp_sys_menu
WHERE
pid IN (
SELECT
id
FROM
lp_sys_menu
WHERE
pid = '-1'
)
)
UNION
SELECT
id
FROM
lp_sys_menu
WHERE
pid IN (
SELECT
id
FROM
lp_sys_menu
WHERE
pid IN (
SELECT
id
FROM
lp_sys_menu
WHERE
pid IN (
SELECT
id
FROM
lp_sys_menu
WHERE
pid = '-1'
)
)
)
后记
树形结构工具类暂时先记录到这,后续再进行补充