树形结构是典型的递归结构,常见于代码中,但是代码比较通用,所以记之备忘。实现方式有多种,此仅仅是一种比较好理解的方式,不适合于数据量太大的情况,如果数据量太大,请使用分批次查询的方式。
形成一个树形结构大部分情况下是基于一张表,然后通过一个字段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));
}