树形结构递归查询,删除等封装处理

一. 背景

   项目中往往会用到大量的树形结构查询以及删除,通过几个树形结构的操作之后,发现这部分的代码是可以抽象,部分接口是可以做到通过,做好这些,使得类似的操作可以得到简化,下面将讲解具体的操作过程。

二. 树查询

    这里树分为干和枝,代码中相应的就是Node和TreeNode。

/**
 * <p>
 * 节点
 * </p>
 *
 * @author yuyi (1060771195@qq.com)
 */
@Data
public class Node implements Serializable {
    private static final long serialVersionUID = -3514061958234921653L;

    /**
     * id
     */
    private Long id;
    
    /**
     * 名称
     */
    private String nm;
    
}
/**
 * <p>
 * 树形接口节点
 * </p>
 *
 * @author yuyi (1060771195@qq.com)
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class TreeNode<T> extends Node {
    private static final long serialVersionUID = -8071463452448530550L;

    /**
     * 父id
     */
    private Long pid;
    
    /**
     * 排序
     */
    private int seq;
    
    /**
     * 层级
     */
    private int level;
    
    /**
     * 子项集合
     */
    private List<T> children;
    
    public void addChild(T node) {
        if (null == children) {
            children = new ArrayList<>();
        }
        children.add(node);
    }
    
}

    以上两个类的树形和,基本就是我们需要的一些基本属性,如果需要其他属性,新建一个类继承TreeNode。比如RscoTreeNode该类就是继承TreeNode

/**
 * <p>
 * 授权资源树形结构类
 * </p>
 *
 * @author yuyi (1060771195@qq.com)
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class RscoTreeNode extends TreeNode<RscoTreeNode> {
    private static final long serialVersionUID = -8188319099997222199L;

    private String url;                               //资源路径                                 
    private String perm;                              //权限标识                                 
    private String icon;                              //图标                                 
    private String rmks;                              //备注                                 
    private String catCd;                             //分类编码                                 
    private String catNm;                             //分类                                 
    private String statCd;                            //状态编码                                 
    private String statNm;                            //状态       
}

  有了这些代码相当于我们已经建了一棵树的模型,接下来就是怎么通过这些模型来建设树的过程。

  新建一个TreeNodeUtils工具类,这里都是对dto进行操作,如果对dto不是很了解的查阅这个:https://blog.csdn.net/ssyujay/article/details/89025503 查询结果dto,vo讲解

/**
 * <p>
 * 树形结构生成工具
 * </p>
 *
 * @author yuyi (1060771195@qq.com)
 */
public class TreeNodeUtils {

    /**
     * 树形结构生成处理, vo对象将有Dto通过名称规范生成
     */
    public static <T extends TreeNode<T>, D> List<T> dtoListHandle(List<D> dtoList, Class<T> nodeClass) {
        return dtoListHandle(dtoList, null, nodeClass, null);
    }
    
    
    /**
     * 树形结构生成处理
     */
    public static <T extends TreeNode<T>, D> List<T> dtoListHandle(List<D> dtoList, Class<?> voClass, Class<T> nodeClass) {
        return dtoListHandle(dtoList, voClass, nodeClass, null);
    }
    
    /**
     * 树形结构生成处理, vo对象将有Dto通过名称规范生成
     */
    public static <T extends TreeNode<T>, D> List<T> dtoListHandle(List<D> dtoList, Class<T> nodeClass, Long pid) {
        return dtoListHandle(dtoList, null, nodeClass, pid);
    }
    
    /**
     * 树形结构生成处理
     */
    public static <T extends TreeNode<T>, D> List<T> dtoListHandle(List<D> dtoList, Class<?> voClass, Class<T> nodeClass, Long pid) {
        if (CollectionUtils.isEmpty(dtoList)) {
            return Collections.emptyList();
        }
        
        List<T> nodeList = new ArrayList<>();
        for (D dto : dtoList) {
            T node = ConvertUtils.convert(getVoByDto(dto, voClass), nodeClass);
            nodeList.add(node);
        }
        return treeNodeHandle(nodeList, pid);
    }
    
    /**
     * 树形结构生成处理
     */
    public static <T extends TreeNode<T>> List<T> treeNodeHandle(List<T> nodeList) {
        return treeNodeHandle(nodeList, null);
    }
    
    /**
     * 树形结构生成处理
     */
    public static <T extends TreeNode<T>> List<T> treeNodeHandle(List<T> nodeList, Long pid) {
        if (CollectionUtils.isEmpty(nodeList)) {
            return Collections.emptyList();
        }
        
        seqHandle(nodeList);
        childFindParentHandler(nodeList);
        
        List<T> resultList = new ArrayList<>();
        if (null == pid) {
            keepTrunk(resultList, nodeList, 1);
        } else {
            keepTrunk(resultList, nodeList, 1, pid);
        }
        return resultList;
    }
    
    /**
     * 保留树形结构主干
     */
    private static <T extends TreeNode<T>> List<T> keepTrunk(List<T> resultList, 
            List<T> nodeList, int level, long pid) {
        for (T node : nodeList) {
            if (null != node.getPid() && (pid == node.getPid().longValue() || level > 1)) {
                if (pid == node.getPid().longValue()) {
                    resultList.add(node);
                }
                node.setLevel(level);
                
                if (CollectionUtils.isNotEmpty(node.getChildren())) {
                    keepTrunk(resultList, node.getChildren(), level + 1, pid);
                }
            }
        }
        return resultList;
    }
    
    /**
     * 保留树形结构主干
     */
    private static <T extends TreeNode<T>> List<T> keepTrunk(List<T> resultList, 
            List<T> nodeList, int level) {
        for (T node : nodeList) {
            if (null == node.getPid() || level > 1) {
                if (null == node.getPid()) {
                    resultList.add(node);
                }
                node.setLevel(level);
                
                if (CollectionUtils.isNotEmpty(node.getChildren())) {
                    keepTrunk(resultList, node.getChildren(), level + 1);
                }
            }
        }
        return resultList;
    }
    
    /**
     * 子项关联到父项,就关联到父项下面
     */
    private static <T extends TreeNode<T>> void childFindParentHandler(List<T> nodeList) {
        for (T node : nodeList) {
            if (null != node.getPid()) {
                T pNode = getTreeNode(nodeList, node.getPid());
                if (null != pNode) {
                    pNode.addChild(node);
                }
            }
        }
    }
    
    /**
     * 获取项
     */
    private static <T extends TreeNode<T>> T getTreeNode(List<T> nodeList, long id) {
        for (T node : nodeList) {
            if (id == node.getId().longValue()) {
                return node;
            }
        }
        return null;
    }
    
    /**
     * 排序
     */
    private static <T extends TreeNode<T>> List<T> seqHandle(List<T> nodeList) {
        Collections.sort(nodeList, new Comparator<T>() {
            @Override
            public int compare(T o1, T o2) {
                if (o1.getSeq() > o2.getSeq()) {
                    return 1;
                }
                return -1;
            }
        });
        return nodeList;
    }
    
    /**
     * 获取vo对象
     */
    private static <D> Object getVoByDto(D dto, Class<?> voClass) {
        String voName = null;
        if (null != voClass) {
            voName = voClass.getSimpleName();
        } else {
            Class<? extends Object> dtoClass = dto.getClass();
            String simpleName = dtoClass.getSimpleName();
            voName = simpleName.substring(0, simpleName.length() - 3) + "Vo";
        }
        
        Method method = ClassUtils.getMethod(dto.getClass(), "get"+ voName);
        try {
            return method.invoke(dto);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

    下面将对TreeNodeUtils工具类中的一些方法使用进行讲解。(这里查询的对象都是dto,dto里面包含数据库映射对象实体vo)

    

/**
     * 树形结构生成处理, vo对象将有Dto通过名称规范生成
     */
    public static <T extends TreeNode<T>, D> List<T> dtoListHandle(List<D> dtoList, Class<T> nodeClass) {
        return dtoListHandle(dtoList, null, nodeClass, null);
    }
    
    /**
     * 树形结构生成处理
     */
    public static <T extends TreeNode<T>, D> List<T> dtoListHandle(List<D> dtoList, Class<?> voClass, Class<T> nodeClass) {
        return dtoListHandle(dtoList, voClass, nodeClass, null);
    }

这两个方法表示对查询dto列表进行属性结构处理,这里可以不传voClass,通过对dtoClass规则生成voClass。这里主要是对数据库对应的实体中id,pid,nm,seq等都与Node和TreeNode吻合可以使用该方法。且这里的顶级pid都是null。

/**
     * 树形结构生成处理
     */
    public static <T extends TreeNode<T>, D> List<T> dtoListHandle(List<D> dtoList, Class<T> nodeClass, Long pid) {
        return dtoListHandle(dtoList, null, nodeClass, pid);
    }
    
    /**
     * 树形结构生成处理, vo对象将有Dto通过名称规范生成
     */
    public static <T extends TreeNode<T>, D> List<T> dtoListHandle(List<D> dtoList, Class<?> voClass, Class<T> nodeClass, Long pid) {
        if (CollectionUtils.isEmpty(dtoList)) {
            return Collections.emptyList();
        }
        
        List<T> nodeList = new ArrayList<>();
        for (D dto : dtoList) {
            T node = ConvertUtils.convert(getVoByDto(dto, voClass), nodeClass);
            nodeList.add(node);
        }
        return treeNodeHandle(nodeList, pid);
    }

这两个方法顶级父级pid不是null,所以需要把pid传进去进行过滤。

/**
     * 树形结构生成处理
     */
    public static <T extends TreeNode<T>> List<T> treeNodeHandle(List<T> nodeList) {
        return treeNodeHandle(nodeList, null);
    }
    
    /**
     * 树形结构生成处理
     */
    public static <T extends TreeNode<T>> List<T> treeNodeHandle(List<T> nodeList, Long pid) {
        if (CollectionUtils.isEmpty(nodeList)) {
            return Collections.emptyList();
        }
        
        seqHandle(nodeList);
        childFindParentHandler(nodeList);
        
        List<T> resultList = new ArrayList<>();
        if (null == pid) {
            keepTrunk(resultList, nodeList, 1);
        } else {
            keepTrunk(resultList, nodeList, 1, pid);
        }
        return resultList;
    }

如果直接调用这两个方法,表示实体对象的dto和treeNode类中的属性不一致,需要程序额外处理在进行树形结构处理。比如如下:

@Override
    public List<WxMenuItmTreeNode> listTreeNode(Long menuId) {
        FindWrapper<SysWxMenuItmVo> fw = new FindWrapper<>();
        fw.eq("menu_id", menuId);
        fw.orderByAsc("seq");
        List<SysWxMenuItmDto> list = this.list(fw);
        
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        
        List<WxMenuItmTreeNode> nodeList = new ArrayList<>();
        for (SysWxMenuItmDto dto : list) {
            WxMenuItmTreeNode node = ConvertUtils.convert(dto.getSysWxMenuItmVo(), WxMenuItmTreeNode.class);
            node.setNm(dto.getSysWxMenuItmVo().getName());
            node.setKey(dto.getSysWxMenuItmVo().getKe());
            nodeList.add(node);
        }
        
        return TreeNodeUtils.treeNodeHandle(nodeList);
    }

对于属性一致的可以直接使用公共接口进行查询(建表的时候,建议使用treeNode相同的属性)

@Override
    public <T extends TreeNode<T>> List<T> listTreeNode(Wrapper<V> wrapper, Class<T> treeNodeClass) {
        List<D> list = this.list(wrapper);
        return TreeNodeUtils.dtoListHandle(list, treeNodeClass);
    }

TreeNodeUtils中的其他方法就是对树形结构属性对象进行属性结构处理,使其成为一课树。

 

三. 树删除

/**
     * <p>
     * 删除(根据ID 删除树形结构数据)
     * </p>
     *
     * @param id 主键ID
     * @param pidColumn 父节点主键
     */
@Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteTreeById(Serializable id, String pidColumn) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(currentModelClass());
        
        Map<String, Object> columnMap = new HashMap<>();
        columnMap.put(pidColumn, id);
        List<Map<String, Object>> listMaps = this.listMapsByMap(columnMap);
        if (CollectionUtils.isNotEmpty(listMaps)) {
            for (Map<String, Object> map : listMaps) {
                deleteTreeById((Serializable) map.get(tableInfo.getKeyColumn()), pidColumn);
            }
        }
        
        this.deleteById(id);
    }

    /**
     * <p>
     * 删除(根据ID 批量删除树形结构数据)
     * </p>
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     * @param pidColumn 父节点主键
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteTreeByIds(Collection<? extends Serializable> idList, String pidColumn) {
        for (Serializable id : idList) {
            deleteTreeById(id, pidColumn);
        }
    }

使用通用方法进行树形节点删除,pidColumn表示数据库父类主键值。

 

 

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值