前言
日常开发中,树形结构的数据是比较常见的一种数据结构,比如系统菜单、组织机构、数据字典等,有时候需要后端把数据转成树形结构再返回给前端,对此特意封装通用树形结构工具类
封装了以下方法:
根据父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' ) ) )
后记
树形结构工具类暂时先记录到这,后续再进行补充