本文章主要描述创建树结构的构造思维
创建泛型接口,以便获取到主键id、以及parentId值,以及parentList亲族链。
/**
* <p> 树节点接口 </p>
*
*/
public interface IEmTree<PK extends Serializable> {
/**
* 获取主键id
*
* @return PK
*/
PK getId();
void setId(PK id);
/**
* 获取父节点id
*
* @return PK
*/
PK getParentId();
void setParentId(PK id);
/**
* 获取亲族链
*
* @return String
*/
String getParentList();
/**
* 设置亲族链
*
* @param parentList 亲族链
*/
void setParentList(String parentList);
/**
* 获取深度
*
* @return Integer
*/
Integer getDepth();
/**
* 设置深度
*
* @param depth 深度
*/
void setDepth(Integer depth);
/**
* 获取是否为叶子节点
*
* @return 是否是叶子节点[1为叶子节点,0为非叶子节点]
*/
Integer getLeaf();
/**
* 设置是否为叶子节点
*
* @param leaf 是否是叶子节点[1为叶子节点,0为非叶子节点]
*/
void setLeaf(Integer leaf);
}
创建返回结构类型(拥有子集结构返回值)
public class AcceptanceTypeTreeNodeDTO extends AcceptanceTypeDTO {
/**
* 子集
*/
@ApiModelProperty("子集")
private List<AcceptanceTypeTreeNodeDTO> children;
public AcceptanceTypeTreeNodeDTO(){
children = Lists.newArrayList();
}
/**
* 添加子集
*
* @param children 子集
*/
public void addChildren(AcceptanceTypeTreeNodeDTO children){
this.children.add(children);
}
}
保存树结构代码逻辑
public class EmTreeUtils {
/**
* 文件分组层级码分隔符
*/
public static final String ELEMENT_FIRST_SEPARATOR = "-";
/**
* 空字符串
*/
public static final String EMPTY = "";
/**
* 默认根节点parentList
*/
public static final String DEFAULT_PARENT_LIST = "1-";
/**
* 文件分组顶级parentId
*/
public static final Long TOP_PARENT_ID = 0L;
/**
* 文件分组顶级parentId
*/
public static final Integer TOP_DEPTH = 1;
/**
* 文件分组顶级parentId
*/
public static final Integer NOT_LEAF = 0;
/**
* 亲族链字段
*/
private static final String P_COLUMN = "parent_list";
private static final String PARENT_ID = "parent_id";
/**
* 新增或修改树节点
*
* @param param 参数
* @param service 服务层接口
* @param doSupplier DO Supplier
* @param dtoSupplier DTO Supplier
* @param <T> 参数类型
* @param <DO> DO类型DTO
* @param <DTO> DTO类型
* @return
*/
@SuppressWarnings("unchecked" )
public static <T,DO extends IEmTree,DTO> DTO saveOrUpdate(T param, IService<DO> service,
Supplier<DO> doSupplier, Supplier<DTO> dtoSupplier) {
// 转换对象类型(属性相同)
DO typeDO = EsDoParser.param2Do(param,doSupplier);
//设置默认为叶子节点
typeDO.setLeaf(1);
//处理层级及父级的亲族链
String parentList = EMPTY;
int depth = TOP_DEPTH;
DO parent = null;
if (!Objects.isNull(typeDO.getParentId()) && !TOP_PARENT_ID
.equals(typeDO.getParentId())) {
parent = service.getById(typeDO.getParentId());
if (Objects.isNull(parent)) {
throw EsBusinessExceptionWrapper.newEsBusinessException(EsErrorCode.USER_SAVE_FAIL,
String.format("未找到对应的父级,parentId:%s", typeDO.getParentId()));
}
if(!NOT_LEAF.equals(parent.getLeaf())){
parent.setLeaf(NOT_LEAF);
service.updateById(parent);
}
parentList = parent.getParentList();
depth += parentList.split(ELEMENT_FIRST_SEPARATOR).length;
} else {
typeDO.setParentList(DEFAULT_PARENT_LIST);
typeDO.setParentId(TOP_PARENT_ID);
}
//编辑时,入参不全时,空数据,不覆盖原有数据
boolean isAdd = true;
if (!Objects.isNull(typeDO.getId())) {
if(typeDO.getId().equals(typeDO.getParentId())) {
throw EsBusinessExceptionWrapper.newEsBusinessException(EsErrorCode.USER_SAVE_FAIL,"父级不应为自己");
}
isAdd = false;
String newParentList = parentList + typeDO.getId() + ELEMENT_FIRST_SEPARATOR;
//更新时修改子级亲族链(新父级不能为原子级)
DO entity = service.getById(typeDO.getId());
List<DO> children = service.list(
new QueryWrapper<DO>()
.likeRight(P_COLUMN, entity.getParentList()));
if(!CollectionUtils.isEmpty(children)){
children = children.stream().filter(e ->!entity.getId().equals(e.getId())).collect(Collectors.toList());
}
if(!CollectionUtils.isEmpty(children) && children.contains(parent)){
throw EsBusinessExceptionWrapper.newEsBusinessException(EsErrorCode.USER_SAVE_FAIL,
String.format("父级不能为子级节点,parentId:%s", typeDO.getParentId()));
}
if(!CollectionUtils.isEmpty(children) && !children.contains(parent)){
children.forEach(e ->{
e.setParentList(e.getParentList().replace(entity.getParentList(),newParentList));
e.setDepth(e.getParentList().split(ELEMENT_FIRST_SEPARATOR).length);
});
}
service.updateBatchById(children);
typeDO.setParentList(newParentList);
int count = service.count(
new QueryWrapper<DO>()
.likeRight(P_COLUMN, typeDO.getParentList()));
if (count > 1) {
typeDO.setLeaf(NOT_LEAF);
}
}
typeDO.setDepth(depth);
//新增或保存
BeanUtils.copyProperties(typeDO,param);
service.saveOrUpdate(typeDO);
DTO dto = EsDoParser.do2Dto(typeDO,dtoSupplier);
//新增需更新 亲族链
if (isAdd) {
DO newDO = doSupplier.get();
BeanUtils.copyProperties(dto, newDO);
newDO.setParentList(parentList +
newDO.getId() + ELEMENT_FIRST_SEPARATOR);
service.updateById(newDO);
return EsDoParser.do2Dto(newDO, dtoSupplier);
}
return dto;
}
/**
* 获取可以作为当前树节点的所有父节点列表
* @param columnName 字段名
* @param id 编辑树节点id
* @param service 服务类
* @param dtoSupplier DTO
* @param <DO> DO
* @param <S> id类型
* @param <DTO> 返回值类型
* @return 可以编辑为传入id节点的所有父节点
*/
public static<DO extends IEmTree<Long>,DTO , S extends Serializable> List<DTO> getParent(String columnName, S id, IService<DO> service, Supplier<DTO> dtoSupplier) {
if (Objects.isNull(id)) {
return new ArrayList<>();
}
DO entity = service.getById(id);
QueryWrapper<DO> queryWrapper = new QueryWrapper<>();
queryWrapper.ne(columnName, id);
List<DO> list = service.list(queryWrapper);
List<DO> children = service.list(
new QueryWrapper<DO>()
.likeRight(P_COLUMN, entity.getParentList()));
if (CollectionUtils.isEmpty(children) || CollectionUtils.isEmpty(list)) {
return EsDoParser.dos2DtoList(list, dtoSupplier);
}
List<Long> collect = children.stream().map(DO::getId).collect(Collectors.toList());
List<DO> doList = list.stream().filter(e -> !collect.contains(e.getId())).collect(Collectors.toList());
return EsDoParser.dos2DtoList(doList, dtoSupplier);
}
}
生成树工具类
public final class TreeUtils {
private TreeUtils() {
}
public static class LinkedPair<P, C> {
P p;
C c;
public LinkedPair(P p, C c) {
this.p = p;
this.c = c;
}
public C getC() {
return c;
}
}
/**
* 建立树。
* <p>请保证linker生成的结果中每个元素中的p与c同parents与children中元素hashcode相同(后续处理中会将p作为key生成map,并遍历parents中元素从map中获取值以生成最终结果),否则可能导致结果不正确</p>
*
* @param parents 上级
* @param children 下级
* @param linker 连接器。如何从上级与下级集合中,找到对应的上下级。
* @param parser P的转换器。并在此处将上级和下级关联(parent.addAll)
* @param <P> 上级泛型
* @param <C> 下级泛型
* @param <NP> 最终结果泛型
* @return 最终的上级集合
*/
public static <P, C, NP> List<NP> buildTree(Collection<P> parents, Collection<C> children,
BiFunction<Collection<P>, Collection<C>, Collection<LinkedPair<P, C>>> linker,
BiFunction<P, List<C>, NP> parser) {
Objects.requireNonNull(linker);
Objects.requireNonNull(parser);
if (parents == null) {
return Collections.emptyList();
}
if (parents.isEmpty() || CollectionUtils.isEmpty(children)) {
return parents.stream().map(item -> parser.apply(item, Collections.emptyList()))
.collect(Collectors.toList());
}
Collection<LinkedPair<P, C>> apply = linker.apply(parents, children);
Map<P, List<C>> collect = apply.stream()
.collect(Collectors
.groupingBy(item -> item.p, Collectors.mapping(LinkedPair::getC, Collectors.toList())));
return parents.stream()
.map(item -> parser.apply(item, collect.get(item)))
.collect(Collectors.toList());
}
/**
* 生成树。用于两种不同结构存在层级关系的时候。
* <p/>
* 如公司与部门之间
*
* @param parents 顶层数据
* @param children 子孙数据
* @param getPk 获取顶层数据 key
* @param getParentId 获取子孙数据的上级的key
* @param addChild 给顶层数据添加子节点
* @param <P> 顶层数据泛型
* @param <PK> 顶层数据key泛型,即子孙数据的parentId泛型
* @param <C> 子孙数据泛型
* @return 顶层数据列表
*/
public static <P, PK, C> List<P> buildTree(List<P> parents, List<C> children,
Function<P, PK> getPk, Function<C, PK> getParentId, BiConsumer<P, C> addChild) {
if (parents == null
|| children == null) {
return Collections.emptyList();
}
if (parents.isEmpty()) {
return parents;
}
Objects.requireNonNull(getPk);
Objects.requireNonNull(getParentId);
Objects.requireNonNull(addChild);
return buildTree(parents, children, (listP, listC) -> {
List<LinkedPair<P, C>> result = new LinkedList<>();
Map<PK, P> map = listP.stream().collect(Collectors.toMap(getPk, p -> p));
listC.forEach(c -> {
P p = map.get(getParentId.apply(c));
if (Objects.nonNull(p)) {
result.add(new LinkedPair<>(p, c));
}
});
return result;
}, (p, cList) -> {
if (!CollectionUtils.isEmpty(cList)) {
cList.forEach(item -> addChild.accept(p, item));
}
return p;
});
}
/**
* 生成树。自生成树。
* <p/>
* 如公司列表中,包含父公司与子公司
* <p/>
* 若parentId为null,则为顶层元素。若parentId不为null,但未找到父节点,则丢弃子节点。
*
* @param items 数据列表
* @param getPk 获取id的方法
* @param getParentId 获取上级id的方法
* @param addChild 新增子节点的方法
* @param <P> 数据泛型
* @param <PK> id泛型
* @return 顶层节点列表
*/
public static <P, PK> List<P> buildTree(List<P> items, Function<P, PK> getPk,
Function<P, PK> getParentId, BiConsumer<P, P> addChild) {
if (CollectionUtils.isEmpty(items)) {
return items;
}
Objects.requireNonNull(getPk);
Objects.requireNonNull(getParentId);
Objects.requireNonNull(addChild);
Set<PK> pkSet = items.stream().map(getPk).collect(Collectors.toSet());
List<P> children = new LinkedList<>();
List<P> top = new LinkedList<>();
items.forEach(item -> {
if (pkSet.contains(getParentId.apply(item))) {
children.add(item);
} else {
top.add(item);
}
});
List<P> result = buildTree(items, children, getPk, getParentId, addChild);
return HmCollectionUtils.takeIntersectionByPk(getPk, top, result);
}
/**
* 将树状结构实体转换为新的扁平的list
*
* @param toFlat 待转换的树状实体
* @param subListGetter 获取某个树状实体的下级实体列表的方法
* @param <T> 实体类型
* @return 树状结构的扁平数据列表
*/
public static <T> List<T> flatToList(List<T> toFlat, Function<T, List<T>> subListGetter) {
Objects.requireNonNull(subListGetter);
if (toFlat == null) {
return Collections.emptyList();
}
List<T> result = new ArrayList<>(toFlat);
List<T> tmpResult = result;
while (true) {
tmpResult = tmpResult.stream().flatMap(item -> subListGetter.apply(item).stream())
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(tmpResult)) {
break;
}
result.addAll(tmpResult);
}
return result;
}
/**
* 是否顶层节点
*
* @param parent 父级节点id
* @param <PK> 主键泛型
* @return true 是顶层节点, false 不是顶层节点
*/
public static <PK extends Serializable> boolean isTop(PK parent) {
if (parent instanceof String) {
Long parentLong = NumberParser.tryParseLong((String) parent);
return Objects.equals(parentLong, ParentListTreeConstant.PARENT_OF_TOP_LEVEL_LONG);
} else {
return Objects.equals(parent, ParentListTreeConstant.PARENT_OF_TOP_LEVEL_LONG);
}
}
/**
* 拼接 levelCode
*
* @param parentLevelCode 上级节点的 levelCode
* @param id 自己的主键
* @param <PK> 主键泛型
* @return 自己的 levelCode
*/
public static <PK extends Serializable> String concatLevelCode(String parentLevelCode,
PK id) {
return parentLevelCode + id + ParentListTreeConstant.LEVEL_CODE_SPLITTER;
}
}
HmCollectionUtils
public final class HmCollectionUtils {
private HmCollectionUtils() {}
/**
* 将容器中的元素指定field收集到新容器
*
* @param entities 元素容器
* @param fieldGetter field提供器
* @param <EN> 元素泛型
* @param <FD> field泛型
* @return field_collection
*/
public static <EN, FD> Set<FD> collectFields(
Collection<EN> entities, Function<EN, FD> fieldGetter) {
return (Set<FD>) collectFields(entities, fieldGetter, HashSet::new);
}
/**
* 将容器中的元素指定field收集到新容器(去重)
*
* @param entities 元素容器
* @param fieldGetter field提供器
* @param <EN> 元素泛型
* @param <FD> field泛型
* @return field_collection
*/
public static <EN, FD> Collection<FD> collectFields(
Collection<EN> entities, Function<EN, FD> fieldGetter, Supplier<Collection<FD>> supplier) {
Objects.requireNonNull(fieldGetter);
if (CollectionUtils.isEmpty(entities)) {
return supplier.get();
}
return entities.stream().map(fieldGetter).collect(Collectors.toCollection(supplier));
}
/**
* 根据指定字段查找两个容器内的数据交集
*
* @param pkGetter pkGetter
* @param c1 c1
* @param c2 返回集合基础集合。在c2中去除c1中不包含的,再返回c2副本
* @param <E> 实体泛型
* @param <PK> ID泛型
* @return 以c2为基础的交集数据
*/
public static <E, PK> List<E> takeIntersectionByPk(
Function<E, PK> pkGetter, Collection<E> c1, Collection<E> c2) {
return takeIntersectionByPkReturnSectionCollectionItems(c1, pkGetter, c2, pkGetter);
}
/**
* 通过指定字段判定,查找不同数据类型中的数据交集,返回第二个集合中的交集数据
*
* @param pkCollection collection1
* @param pkGetter1 pkGetter
* @param entityCollection 返回集合基础集合。在entityCollection中去除pkCollection中不包含的,再返回c2副本
* @param pkGetter2 pkGetter
* @param <E> 实体泛型1
* @param <G> 实体泛型2
* @param <PK> 主键泛型
*/
public static <E, G, PK> List<G> takeIntersectionByPkReturnSectionCollectionItems(
Collection<E> pkCollection,
Function<E, PK> pkGetter1,
Collection<G> entityCollection,
Function<G, PK> pkGetter2) {
if (CollectionUtils.isEmpty(pkCollection) || CollectionUtils.isEmpty(entityCollection)) {
return Collections.emptyList();
}
Set<PK> pksOfC1 = pkCollection.stream().map(pkGetter1).collect(Collectors.toSet());
return entityCollection.stream()
.filter(m -> pksOfC1.contains(pkGetter2.apply(m)))
.collect(Collectors.toList());
}
/**
* @param entities 集合
* @param keyGetter key
* @param valueGetter value
* @param <EN> 集合元素类型
* @param <K> 键类型
* @param <V> 值类型
* @return 结果集map
* @author guxu
*/
public static <EN, K, V> Map<K, V> collectFieldsToMap(
Collection<EN> entities, Function<EN, K> keyGetter, Function<EN, V> valueGetter) {
Objects.requireNonNull(keyGetter);
Objects.requireNonNull(valueGetter);
if (CollectionUtils.isEmpty(entities)) {
return Collections.emptyMap();
}
return entities.stream().collect(Collectors.toMap(keyGetter, valueGetter, (a, b) -> a));
}
}