關於樹形結構、扁平化、子級的通用做法

樹形結構是典型的遞歸結構,常見於代碼中,但是代碼比較通用,所以記之備忘。實現方式有多種,此僅僅是一種比較好理解的方式,不適合於數據量太大的情況,如果數據量太大,請使用分批次查詢的方式。

形成一個樹形結構大部分情況下是基於一張表,然後通過一個字段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));
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章