关于树形结构、扁平化、子级的通用做法

树形结构是典型的递归结构,常见于代码中,但是代码比较通用,所以记之备忘。实现方式有多种,此仅仅是一种比较好理解的方式,不适合于数据量太大的情况,如果数据量太大,请使用分批次查询的方式。

形成一个树形结构大部分情况下是基于一张表,然后通过一个字段parent_id指向其父级id即可。

CREATE TABLE `some_model` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(32) NOT NULL COMMENT '名字',
  `level` int(11) NOT NULL DEFAULT '1' COMMENT '层级,方便前端展示',
  `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8 COMMENT='具备层级关系';

对应的JPA 的Entity如下,其中设置一个displayName方便前端形成层级效果。

@Entity
@Table(name = "some_model")
public class SomeModel implements Serializable {
    private static final String PREFIX = "┣";
    private static final String SEPERATOR = "  ";
    public static final int LEVEL_1 = 1;
    public static final int LEVEL_2 = 2;
    public static final int LEVEL_3 = 3;
    public static final int LEVEL_4 = 4;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;

    /**
     * 展示方便形成层级的名字,就是前缀+name
     */
    @Transient
    private String displayName;

    /**
     * 层级,最高级为1
     */
    private int level;

    /**
     * 所属上级
     */
    @Column(name = "parent_id")
    private int parentId;

    @Transient
    public List<SomeModel> children;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDisplayName() {
        StringBuilder builder = new StringBuilder();
        int l = level<=1 ? 0 : level;
        for (int i = 0; i < l; i++) {
            builder.append(SEPERATOR);
        }
        return builder.append(PREFIX).append(name).toString();
    }




  .....省略getter、setter
}

首先,设置每个model的children,可以是个森林(可以包含多个顶级节点)

    public List<SomeModel> tree() {
        List<SomeModel> all = someModelRepository.findAll();
        if(CollectionUtil.isEmpty(all)){
            return Collections.emptyList();
        }
        final Map<Integer, List<SomeModel>> map = childrenGroupingBy(all);
        ///List<SomeModel> topList = someModelRepository.findAllByLevelEquals(1);
        ///List<SomeModel> topList = someModelRepository.findAllByParentIdEquals(0);
        ///parentId=0的就是顶级的
        List<SomeModel> topList = map.get(0);
        if (CollectionUtil.notEmpty(topList)) {
            topList.forEach(o -> setChildren(o, map));
        }
        return null == topList ? Collections.emptyList() : topList;
    }

    /**
     * 所有的形成id和下级之间的映射,通过 parentId
     */
    public Map<Integer, List<SomeModel>> childrenGroupingBy(List<SomeModel> all) {
        return all.stream().collect(Collectors.groupingBy(SomeModel::getParentId));
    }

    private void setChildren(SomeModel someModel, final Map<Integer, List<SomeModel>> map) {
        List<SomeModel> childList = map.get(someModel.getId());
        if (CollectionUtil.notEmpty(childList)) {
            someModel.setChildren(childList);
            childList.forEach(o -> setChildren(o, map));
        }
    }

然后,将该树扁平化,页面循环此列表展示displayName即可

/**
     * 树形结构扁平化,页面展示形成树形结构{@link SomeModel#getDisplayName()}
     * 先展示自己,再展示自己的下级
     * @param tree 具备children的一个列表
     */
    public List<SomeModel> flatten(List<SomeModel> tree){
        List<SomeModel> list = new LinkedList<>();
        for (SomeModel someModel : tree) {
            list.add(someModel);
            //孩子扁平化
            List<SomeModel> children = someModel.getChildren();
            if(CollectionUtil.isEmpty(children)){
                continue;
            }
            List<SomeModel> flatten = flatten(children);
            list.addAll(flatten);
            someModel.setChildren(null);
        }
        return list;
    }

效果如下:

另外,提供获取某个的下级,子子孙孙的方法。

/**
     * 获取我所有的下级,不包括自己,儿子、孙子、无穷尽
     * @param someModel 哪一个
     * @param includeMySelf 是否包含自己
     */
    public List<SomeModel> getMyChildren(SomeModel someModel, boolean includeMySelf) {
        if(null == someModel){
            return Collections.emptyList();
        }
        List<SomeModel> all = someModelRepository.findAll();
        Map<Integer, List<SomeModel>> integerListMap = childrenGroupingBy(all);
        LinkedList<SomeModel> someModels = new LinkedList<>();
        if(includeMySelf){
            someModels.add(someModel);
        }
        fillMyChildren(someModels , someModel , integerListMap);
        return someModels;
    }
    /**
     * @param result 装结果的list,一般传入一个初始化的list
     * 获取我所有的下级,不包括自己,儿子、孙子、无穷尽
     */
    private void fillMyChildren(List<SomeModel> result , SomeModel someModel, Map<Integer, List<SomeModel>> map){
        if(null == someModel){
            return;
        }

        List<SomeModel> myChild = map.get(someModel.getId());
        if(CollectionUtil.isEmpty(myChild)){
            return;
        }

        result.addAll(myChild);
        myChild.forEach(o-> fillMyChildren(result, o, map));
    }

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值