由于项目前端之前要做树结构的展示,后端为了展示树结构碰到一些问题,现特做记录,
问题一: 当数据库字段和树结构不匹配时怎么处理?
问题二:当传递根节点 为多节点怎么处理?
问题三:传递节点是否为叶子节点的处理
。。。 等等等等 不一一列表,下面用详细代码解释:
话不多说,先看结果,为了方便查看,都打印的JSON格式: 单根节点的处理
多根节点的处理:
public class TestTree {
/*测试一个根节点树结构的入口*/
@Test
public void testOneRootTree() {
//根节点ID
Integer parentId = 1;
/*初始化数据*/
List<DeptDO> deptList = getDeptList();
//实体类转换成树结构
List<DeptTree> bodyList = deptList.stream().map(dept -> {
DeptTree deptTree = new DeptTree();
BeanUtils.copyBean(dept, deptTree);
return deptTree;
}).collect(Collectors.toList());
//取root
Optional<DeptTree> first = bodyList.stream()
.filter(tree -> tree.getId().equals(parentId)).findFirst();
DeptTree rootTree = null;
if (first.isPresent()) {
rootTree = first.get();
}
/*树处理*/
TreeToolUtils treeToolUtils = new TreeToolUtils(rootTree, bodyList);
List<DeptTree> tree = treeToolUtils.getTree();
//return Objects.nonNull(tree)?tree.get(0):null;
/*返回值*/
System.out.println(JSON.toJSONString(tree.get(0)));
}
/*测试多根节点入口*/
@Test
public void testMultiRoot(){
List<DeptDO> deptRootList = getRootList();
//实体类转换成树结构
List<DeptTree> rootList = deptRootList.stream().map(dept -> {
DeptTree deptTree = new DeptTree();
BeanUtils.copyBean(dept, deptTree);
return deptTree;
}).collect(Collectors.toList());
/*初始化数据*/
List<DeptDO> deptList = getDeptList();
//实体类转换成树结构
List<DeptTree> bodyList = deptList.stream().map(dept -> {
DeptTree deptTree = new DeptTree();
BeanUtils.copyBean(dept, deptTree);
return deptTree;
}).collect(Collectors.toList());
/*树处理*/
TreeToolUtils treeToolUtils = new TreeToolUtils(rootList, bodyList);
List<DeptTree> tree = treeToolUtils.getTree();
//返回值
System.out.println(JSON.toJSONString(tree));
}
private List<DeptDO> getDeptList() {
List<DeptDO> deptDOList = Lists.newArrayList();
DeptDO deptDO1 = new DeptDO();
deptDO1.setId(1);
deptDO1.setParentId(0);
deptDO1.setDeptName("主部门1");
DeptDO deptDO2 = new DeptDO();
deptDO2.setId(2);
deptDO2.setParentId(1);
deptDO2.setDeptName("子部门2");
DeptDO deptDO3 = new DeptDO();
deptDO3.setId(3);
deptDO3.setParentId(1);
deptDO3.setDeptName("子部门3");
DeptDO deptDO4 = new DeptDO();
deptDO4.setId(4);
deptDO4.setParentId(2);
deptDO4.setDeptName("子子部门4");
DeptDO deptDO5 = new DeptDO();
deptDO5.setId(5);
deptDO5.setParentId(3);
deptDO5.setDeptName("子子部门5");
deptDOList.add(deptDO1);
deptDOList.add(deptDO2);
deptDOList.add(deptDO3);
deptDOList.add(deptDO4);
deptDOList.add(deptDO5);
return deptDOList;
}
private List<DeptDO> getRootList() {
List<DeptDO> deptDOList = Lists.newArrayList();
DeptDO deptDO1 = new DeptDO();
deptDO1.setId(2);
deptDO1.setParentId(1);
deptDO1.setDeptName("主部门2");
DeptDO deptDO2 = new DeptDO();
deptDO2.setId(3);
deptDO2.setParentId(1);
deptDO2.setDeptName("主部门3");
deptDOList.add(deptDO1);
deptDOList.add(deptDO2);
return deptDOList;
}
}
@Data
public class DeptDO {
/*部门id*/
@CopyField(targetName = "id")
private Integer id;
/*父部门的ID*/
@CopyField(targetName = "pid")
private Integer parentId;
/*部门名称*/
@CopyField(targetName = "title")
private String deptName;
}
@Data
@ApiModel("基础树模型")
public class BaseTree<T extends BaseTree> {
/**
* 节点ID
*/
@ApiModelProperty("节点ID")
private Integer id;
/**
* 节点父ID
*/
@ApiModelProperty("节点父ID")
private Integer pid;
/**
* 节点标题
*/
@ApiModelProperty("节点标题")
private String title;
/**
* 是否没有叶子
*/
@ApiModelProperty("是否叶子")
private Boolean leaf = false;
/**
* 子级
*/
@ApiModelProperty("子级")
private List<T> children;
}
/*部门树,这里可扩展部门的明细字段*/
public class DeptTree extends BaseTree {
}
@Slf4j
public class BeanUtils extends org.springframework.beans.BeanUtils {
/**
* <h3>拷贝一个对象的属性至另一个对象</h3>
* <p>
* 支持两个对象之间不同属性名称进行拷贝,使用注解{@link CopyField}
* </p>
* @param originBean 源对象 使用注解{@link CopyField#targetName()}
* @param targetBean 目标对象 使用注解{@link CopyField#originName()}
*/
public static void copyBean(Object originBean, Object targetBean) {
Map<String, Object> originFieldKeyWithValueMap = new HashMap<>(16);
PropertyDescriptor propertyDescriptor = null;
//生成源bean的属性及其值的字典
generateOriginFieldWithValue(propertyDescriptor, originBean, originFieldKeyWithValueMap, originBean.getClass());
//设置目标bean的属性值
settingTargetFieldWithValue(propertyDescriptor, targetBean, originFieldKeyWithValueMap, targetBean.getClass());
}
/**
* 生成需要被拷贝的属性字典 属性-属性值<br/>
* 递归取父类属性值
* @param propertyDescriptor 属性描述器,可以获取bean中的属性及方法
* @param originBean 待拷贝的bean
* @param originFieldKeyWithValueMap 存放待拷贝的属性和属性值
* @param beanClass 待拷贝的class[可能是超类的class]
*/
private static void generateOriginFieldWithValue(PropertyDescriptor propertyDescriptor, Object originBean, Map<String, Object> originFieldKeyWithValueMap, Class<?> beanClass) {
/**如果不存在超类,那么跳出循环*/
if (beanClass.getSuperclass() == null) {
return;
}
Field[] originFieldList = beanClass.getDeclaredFields();
for (Field field : originFieldList) {
try {
/*获取属性上的注解。如果不存在,使用属性名,如果存在使用注解名*/
CopyField annotation = field.getAnnotation(CopyField.class);
String targetName = "";
if (annotation != null) {
targetName = annotation.targetName();
} else {
targetName = field.getName();
}
/** 过滤serialVersionUID */
if ("serialVersionUID".equals(targetName)) {
continue;
}
//初始化
propertyDescriptor = new PropertyDescriptor(field.getName(), beanClass);
//获取当前属性的get方法
Method method = propertyDescriptor.getReadMethod();
//设置值
Object value = method.invoke(originBean);
//设置值
originFieldKeyWithValueMap.put(targetName, value);
} catch (IntrospectionException e) {
log.warn("【源对象】异常:" + field.getName() + "不存在对应的get方法,无法参与拷贝!");
} catch (IllegalAccessException e) {
log.warn("【源对象】异常:" + field.getName() + "的get方法执行失败!");
} catch (InvocationTargetException e) {
log.warn("【源对象】异常:" + field.getName() + "的get方法执行失败!");
}
}
//生成超类 属性-value
generateOriginFieldWithValue(propertyDescriptor, originBean, originFieldKeyWithValueMap, beanClass.getSuperclass());
}
/**
* 设置目标对象属性
* @param propertyDescriptor 属性描述器,获取当前传入属性的(getter/setter)方法
* @param targetBean 目标容器bean
* @param originFieldKeyWithValueMap 待拷贝的属性和属性值
* @param beanClass 待设置的class[可能是超类的class]
*/
private static void settingTargetFieldWithValue(PropertyDescriptor propertyDescriptor, Object targetBean, Map<String, Object> originFieldKeyWithValueMap, Class<?> beanClass) {
/**如果不存在超类,那么跳出循环*/
if (beanClass.getSuperclass() == null) {
return;
}
Field[] targetFieldList = beanClass.getDeclaredFields();
for (Field field : targetFieldList) {
try {
/*获取属性上的注解。如果不存在,使用属性名,如果存在使用注解名*/
CopyField annotation = field.getAnnotation(CopyField.class);
String originName = "";
if (annotation != null) {
originName = annotation.originName();
} else {
originName = field.getName();
}
/** 过滤serialVersionUID */
if ("serialVersionUID".equals(originName) || !originFieldKeyWithValueMap.containsKey(originName)) {
continue;
}
//初始化当前属性的描述器
propertyDescriptor = new PropertyDescriptor(field.getName(), beanClass);
//获取当前属性的set方法
Method method = propertyDescriptor.getWriteMethod();
method.invoke(targetBean, originFieldKeyWithValueMap.get(originName));
} catch (IntrospectionException e) {
log.warn("【目标对象】异常:" + field.getName() + "不存在对应的set方法,无法参与拷贝!");
} catch (IllegalAccessException e) {
log.warn("【目标对象】异常:" + field.getName() + "的set方法执行失败!");
} catch (InvocationTargetException e) {
log.warn("【目标对象】异常:" + field.getName() + "的set方法执行失败!");
}
}
//设置超类属性
settingTargetFieldWithValue(propertyDescriptor, targetBean, originFieldKeyWithValueMap, beanClass.getSuperclass());
}
}
/*树的工具类*/
public class TreeToolUtils<T extends BaseTree> {
/**
* 根节点对象存放到这里
*/
private List<T> rootList;
/**
* 其他节点存放到这里,可以包含根节点
*/
private List<T> bodyList;
public TreeToolUtils(List<T> rootList, List<T> bodyList) {
this.rootList = rootList;
this.bodyList = bodyList;
}
public TreeToolUtils(T root, List<T> bodyList) {
rootList = Lists.newArrayList();
rootList.add(root);
this.bodyList = bodyList;
}
public List<T> getTree() { //调用的方法入口
if (bodyList != null && !bodyList.isEmpty()) {
//声明一个map,用来过滤已操作过的数据
Map<Integer, Integer> map = Maps.newHashMapWithExpectedSize(bodyList.size());
//传递根对象和一个空map
rootList.forEach(beanTree -> getChild(beanTree, map));
return rootList;
}
return null;
}
public void getChild(BaseTree beanTree, Map<Integer, Integer> map) {
List<T> childList = Lists.newArrayList();
bodyList.stream()
//map内不包含子节点的code
.filter(c -> !map.containsKey(c.getId()))
//子节点的父id==根节点的code 继续循环
.filter(c -> c.getPid().equals(beanTree.getId()))
.forEach(c -> {
map.put(c.getId(), c.getPid());//当前节点code和父节点id
getChild(c, map);//递归调用
childList.add(c);
});
beanTree.setLeaf(childList.isEmpty());
beanTree.setChildren(childList);
}
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CopyField {
/**
* 在即将被拷贝的属性上面,设置目标属性名
*/
String targetName() default "";
/**
* 在即将拷贝至改属性上面,设置源属性名
*/
String originName() default "";
}
不知道有没有复制完,有些是其他博客找的,源头找不到了,好用给个赞吧, 哈哈~~~