课程分类查询
界面原型
在新增课程基本信息界面中课程等级、课程类型、课程分类
三处信息需要用户选择
当我们点击新增课程
时,前端会请求内容管理服务中的content/course-category/tree-nodes
接口获取课程分类表中的课程分类信息
响应数据模型
课程分类表course_category
是一个树型结构表,通过parantid
字段将表中的所有记录组成一个树
在内容管理服务的model
工程中定义一个CourseCategoryTreeDto
类存储课程分类信息中的数据
package com.xuecheng.content.model.dto;
/**
* @description 课程分类树型结点dto
* @author Mr.M
* @date 2022/9/7 15:16
* @version 1.0
*/
@Data
public class CourseCategoryTreeDto extends CourseCategory implements Serializable {
// 存储当前节点(根节点的下属节点)的所有子节点
List<CourseCategoryTreeDto> childrenTreeNodes;
}
此接口要返回全部课程的分类信息并组成一个树型结构(JSON数组)
返回给前端,数组的每个元素是课程分类根节点的子节点
,每个子节点又有其对应的字节点
[
// 根节点的子节点
{
// 1-2节点的基本信息
"id" : "1-2",
"isLeaf" : null,
"isShow" : null,
"label" : "移动开发",
"name" : "移动开发",
"orderby" : 2,
"parentid" : "1",
// 1-2节点的所有子节点信息
"childrenTreeNodes" : [
{
// 1-2-1节点的子节点信息
"childrenTreeNodes" : null,
// 1-2-1节点的基本信息
"id" : "1-2-1",
"isLeaf" : null,
"isShow" : null,
"label" : "微信开发",
"name" : "微信开发",
"orderby" : 1,
"parentid" : "1-2"
},
//1-2节点的其他子节点........
]
},
{
// 1-1节点的基本信息
"id" : "1-1",
"isLeaf" : null,
"isShow" : null,
"label" : "前端开发",
"name" : "前端开发",
"orderby" : 1,
"parentid" : "1"
// 1-1节点的子节点
"childrenTreeNodes" : [
{
"childrenTreeNodes" : null,
"id" : "1-1-1",
"isLeaf" : null,
"isShow" : null,
"label" : "HTML/CSS",
"name" : "HTML/CSS",
"orderby" : 1,
"parentid" : "1-1"
},
//1-1节点的其他子节点........
]
},
//根节点的其他子节点........
]
接口定义(api工程)
从数据库中查询出根节点下
所有的课程分类信息,然后在Java程序中遍历查询到的数据组成一个树形结构对象并返回
package com.xuecheng.content.api;
/**
* <p>
* 数据字典 前端控制器
* </p>
*
* @author itcast
*/
@Slf4j
@RestController
@Api(value = "课程分类相关接口", tags = "课程分类相关接口")
public class CourseCategoryController {
@Resource
private CourseCategoryService courseCategoryService;
@ApiOperation("课程分类相关接口")
@GetMapping("/course-category/tree-nodes")
public List<CourseCategoryTreeDto> queryTreeNodes() {
return courseCategoryService.queryTreeNodes("1");
}
}
业务开发(service层)
第一步: 使用表的自连接查询,连接条件为two.parentid = one.id
(二级分类的父节点为一级分类),同时one.parentid = 1
(一级分类的父节点为根节点)
- 如果我们有三级分类的话,那么还得继续修改SQL语句
SELECT * FROM
course_category one
JOIN
course_category two
ON
two.parentid = one.id
WHERE
one.parentid = '1'
select
one.id one_id,
one.name one_name,
one.parentid one_parentid,
one.orderby one_orderby,
one.label one_label,
two.id two_id,
two.name two_name,
two.parentid two_parentid,
two.orderby two_orderby,
two.label two_label
from
course_category one
inner join
course_category two
on
one.id = two.parentid
where
one.parentid = 1 and one.is_show = 1 and two.is_show = 1
order by
one.orderby,two.orderby
如果当树的层级不固定时,使用with语法
底层是利用MySQL的递归实现,把查询出来的结果再次代入到查询子句中继续查询
WITH RECURSIVE t1 AS (
# 设置一个递归的初始值
SELECT 1 AS n
# UNION ALL 不断将每次递归得到的数据加入到表中
UNION ALL
# WHERE n < 5 则是终止条件
SELECT n + 1 FROM t1 WHERE n < 5
)
SELECCT * FROM t1;
# t1 相当于一个表名
WITH RECURSIVE t1 AS (
SELECT p.* FROM course_category p WHERE p.id = '1'
UNION ALL
SELECT c.* FROM course_category c JOIN t1 on c.parentid = t1.id
)
第二步:编写Mapper接口及其SQL映射文件
public interface CourseCategoryMapper extends BaseMapper<CourseCategory> {
List<CourseCategoryTreeDto> selectTreeNodes();
}
<select id="selectTreeNodes" parameterType="string" resultMap="com.xuecheng.content.model.dto.CourseCategoryTreeDto">
WITH RECURSIVE t1 AS (
SELECT p.* FROM course_category p WHERE p.id = #{id}
UNION ALL
SELECT c.* FROM course_category c JOIN t1 WHERE c.parentid = t1.id
)
SELECT * FROM t1;
</select>
第三步: 编写Service接口及其实现类,使用Java代码将查询到的课程分类数据组装成树形结构
public interface CourseCategoryService {
/**
* 课程分类查询
* @param id 根节点id
* @return 根节点下面的所有子节点
*/
List<CourseCategoryTreeDto> queryTreeNodes(String id);
}
@Slf4j
@Service
public class CourseCategoryServiceImpl implements CourseCategoryService {
@Autowired
private CourseCategoryMapper courseCategoryMapper;
@Override
public List<CourseCategoryTreeDto> queryTreeNodes(String id) {
// 获取所有的子节点
List<CourseCategoryTreeDto> categoryTreeDtos = courseCategoryMapper.selectTreeNodes(id);
// 定义一个List,作为最终返回的数据
List<CourseCategoryTreeDto> result = new ArrayList<>();
// 为了方便找子节点的父节点,这里定义一个HashMap,key是节点的id,value是节点本身
HashMap<String, CourseCategoryTreeDto> nodeMap = new HashMap<>();
// 将数据封装到List中,只包括根节点的下属节点(1-1、1-2 ···),这里遍历所有节点
categoryTreeDtos.stream().forEach(item -> {
// 这里寻找父节点的直接下属节点(1-1、1-2 ···)
if (item.getParentid().equals(id)) {
nodeMap.put(item.getId(), item);
result.add(item);
}
// 获取每个子节点的父节点,对于1-1的父节点1是不在Map集合当中的,所以不需要为父节点1设置ChildrenTreeNodes属性
String parentid = item.getParentid();
CourseCategoryTreeDto parentNode = nodeMap.get(parentid);
// 判断HashMap中是否存在该父节点(按理说必定存在,以防万一)
if (parentNode != null) {
// 为父节点设置子节点(将1-1-1设为1-1的子节点)
List childrenTreeNodes = parentNode.getChildrenTreeNodes();
// 如果子节点暂时为null,则初始化一下父节点的子节点(给个空集合就行)
if (childrenTreeNodes == null) {
parentNode.setChildrenTreeNodes(new ArrayList<CourseCategoryTreeDto>());
}
// 将子节点设置给父节点
parentNode.getChildrenTreeNodes().add(item);
}
});
// 返回根节点的直接下属节点(1-1、1-2 ···),下属节点已经设置了子节点属性
return result;
}
}