引言:
在实际的开发过程,经常会使用到树形结构,我的数据库表设计一般都会有这三个字段
id(主键) parent_id(父节点编号) leaf(是否为叶子节点)
由于经常使用,故封装了一系列,全套式解决方法的工具类
构建树形结构
可以选择反射工具类hutool.ReflectUtil
import cn.hutool.core.collection.CollUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 构建树形结构工具类
*
* @author xzx
* @date 2022/11/20
*/
public class TreeUtil {
/**
* 根据父节点的ID获取所有子节点
*
* @param list 平级的节点列表
* @param parentId 传入的父节点ID 根节点默认0
* @return String
*/
public static <T> List<T> getChildPerms(List<T> list, int parentId) {
List<T> returnList = new ArrayList<>();
//遍历每一个菜单
for (T t : list) {
// 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
Integer pid = ReflectUtil.getFieldValue(t, "parentId");
if (pid != null && pid.equals(parentId)) {
//获取子菜单
recursionFn(list, t);
returnList.add(t);//添加一级菜单
}
}
return returnList;
}
/**
* 递归列表
*
* @param list 平级列表
* @param t 当前节点
*/
private static <T> void recursionFn(List<T> list, T t) {
// 得到子节点列表
List<T> childList = getChildList(list, t);
if (CollUtil.isNotEmpty(childList)) {
ReflectUtil.invokeSetter(t, "children", childList);
}
//遍历子节点,构建子节点的树状结构
for (T tChild : childList) {
//判断该节点是否还有子节点
if (hasChild(list, tChild)) {
recursionFn(list, tChild);
}
}
}
/**
* 得到子节点列表
* 判断list中的每一个节点的 parentId == t.id
*
* @param list 平级列表
* @param t 父节点
* @param <T> 泛型
* @return t的子节点列表
*/
private static <T> List<T> getChildList(List<T> list, T t) {
List<T> tList = new ArrayList<>();
Integer id = ReflectUtil.getFieldValue(t, "id");
for (T n : list) {
Integer parentId = ReflectUtil.getFieldValue(n, "parentId");
if (Objects.equals(parentId, id)) {
tList.add(n);
}
}
return tList;
}
/**
* 判断是否有子节点
*
* @param list 平级列表
* @param t 父节点
* @param <T> 泛型
* @return
*/
private static <T> boolean hasChild(List<T> list, T t) {
return getChildList(list, t).size() > 0;
}
}
【使用例子】
@Override
public ResponseResult getTreeList() {
return ResponseResult.okResult(TreeUtil.getChildPerms(list(), 1));
}
增删改
然后再实际开发过程中,关于树形结构的节点的增删改,都可能影响到它的前驱节点的leaf【是否为叶子节点】,故又统一再服务层封装工具类
为了能够动态的调用Mabatis-Plus的save、removeById、queryWrapper,大量的运用了【hutool.ReflectUtil】反射工具类。
【要点】
- 在更新leaf的值时采用类似【CAS】的操作
- 校验一个节点是否拥有子节点【hasChildren】,sql语句类似“select xx from xxx where parentId == curNodeId limit 1”,重点在于【limit 1】操作,在MP的getOne()的实质是getList().get(0),而One()是需要保证唯一性,都不符合我们的要求,
则使用last(“limit 1”), 将“limit 1”提取为常量
- 开启事务
package com.sztu.server.service;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.sztu.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* 处理树状结构的逻辑
*
* @author xzx
* @date 2022/11/24
*/
@Component
@Slf4j
public class TreeService {
/**
* 添加一个节点
*
* @param node 节点
* @param beanClass 类型
* @param serviceImpl 业务实现类
* @param <T> 泛型
* @return boolean
*/
@Transactional
public <T> boolean addTreeNode(T node, Class<T> beanClass, Object serviceImpl) {
// 1.save
Boolean save = ReflectUtil.invoke(serviceImpl, "save", node);
if (BooleanUtil.isFalse(save)) {
return false;
}
// 2.parentNode has children : leaf [true] -> [false]
return updateNodeLeaf(getParentId(node), serviceImpl, true, false);
}
/**
* 移除一个节点
*
* @param node
* @param beanClass
* @param serviceImpl
* @param <T>
* @return
*/
@Transactional
public <T> boolean removeTreeNode(T node, Class<T> beanClass, Object serviceImpl) {
// 0.judge
if (hasChildren(node, serviceImpl)) {
log.info("该节点存在孩子节点,不可以删除");
return false;
}
// 1.remove
boolean removeById = ReflectUtil.invoke(serviceImpl, "removeById", getId(node));
if (BooleanUtil.isFalse(removeById)) {
return false;
}
// 2.check node's parentNode hasChildren?
if (BooleanUtil.isTrue(parentNodeHasChildren(node, serviceImpl))) {
return true;
}
// noChildren : leaf [false] -> [true]
return updateNodeLeaf(getParentId(node), serviceImpl, false, true);
}
/**
* 更新一个节点
*
* @param node
* @param <T>
*/
@Transactional
public <T> boolean updateTreeNode(T node, Class<T> beanClass, Object serviceImpl) {
// 1.get parentId form database
T oldNode = ReflectUtil.invoke(serviceImpl, "getById", getId(node));
if (ObjUtil.isNull(oldNode)) {
log.info("该节点不存在");
return false;
}
int oldParentId = getParentId(oldNode);
int newParentId = getParentId(node);
// 2.update
boolean updateById = ReflectUtil.invoke(serviceImpl, "updateById", node);
if (BooleanUtil.isFalse(updateById)) {
return false;
}
// 3.check
if (oldParentId == newParentId) {
return true;
}
// oldNode maybe become noChildren
if (BooleanUtil.isFalse(hasChildren(serviceImpl, oldParentId))) {
// set noChildren where hasChildren
updateNodeLeaf(oldParentId, serviceImpl, false, true);
}
// set hasChildren where noChildren
return updateNodeLeaf(newParentId, serviceImpl, true, false);
}
/**
* 获取字段【id】的值
*
* @param node
* @param <T>
* @return
*/
@Nullable
private <T> int getId(T node) {
return (int) ReflectUtil.getFieldValue(node, "id");
}
/**
* 更新编号为id的节点的leaf属性
*
* @param id
* @param serviceImpl
* @param <T>
* @return
*/
private <T> boolean updateNodeLeaf(int id, Object serviceImpl, boolean oldValue, boolean newValue) {
UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", id)
.eq("leaf", oldValue) // 是叶子节点
.set("leaf", newValue);
return ReflectUtil.invoke(serviceImpl, "update", updateWrapper);
}
/**
* 获取字段【parentId]值
*
* @param node
* @param <T>
* @return
*/
@Nullable
private <T> int getParentId(T node) {
return (int) ReflectUtil.getFieldValue(node, "parentId");
}
/**
* 判断当前节点的父节点是否还有孩子
*
* @param node
* @param serviceImpl
* @param <T>
* @return
*/
private <T> boolean parentNodeHasChildren(T node, Object serviceImpl) {
return hasChildren(serviceImpl, getParentId(node));
}
/**
* 判断当前节点是否还有孩子
*
* @param node
* @param serviceImpl
* @param <T>
* @return
*/
private <T> boolean hasChildren(T node, Object serviceImpl) {
return hasChildren(serviceImpl, getId(node));
}
/**
* 判断编号为id的节点是否还有孩子
*
* @param serviceImpl
* @param id
* @param <T>
* @return
*/
private <T> boolean hasChildren(Object serviceImpl, int id) {
// wrapper.eq("id", id).last("limit 1");
// this.getOne(wrapper);
QueryWrapper<T> wrapper = new QueryWrapper<>();
wrapper = ReflectUtil.invoke(wrapper, "eq", "parent_id", id);
wrapper = ReflectUtil.invoke(wrapper, "last", Constants.LIMIT_ONE);
T one = ReflectUtil.invoke(serviceImpl, "getOne", wrapper);
return ObjUtil.isNotNull(one);
}
}
【同样给出使用的例子】
@Override
public ResponseResult add(Menu menu) {
return ResponseResult.okResult(treeService.addTreeNode(menu, Menu.class, this));
}
@Override
@Transactional
public ResponseResult delete(Integer[] ids) {
for (Integer id : ids) {
if (BooleanUtil.isFalse(treeService.removeTreeNode(getById(id), Menu.class, this))) {
return ResponseResult.errorResult(666, "存在关联数据");
}
}
return ResponseResult.okResult(true);
}
@Override
public ResponseResult edit(Menu menu) {
return ResponseResult.okResult(treeService.updateTreeNode(menu, Menu.class, this));
}