实现多级树形结构查询 比如分类(父分类、子分类)
数据库表结构
CREATE TABLE `course_category` (
`id` varchar(20) NOT NULL COMMENT '主键',
`name` varchar(32) NOT NULL COMMENT '分类名称',
`label` varchar(32) DEFAULT NULL COMMENT '分类标签默认和名称一样',
`parentid` varchar(20) NOT NULL DEFAULT '0' COMMENT '父结点id(第一级的父节点是0,自关联字段id)',
`is_show` tinyint DEFAULT NULL COMMENT '是否显示',
`orderby` int DEFAULT NULL COMMENT '排序字段',
`is_leaf` tinyint DEFAULT NULL COMMENT '是否叶子',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='课程分类';
仅用于分类级别固定
比如固定两级或者三级可以用 表的自连接
SELECT
one.id one_id,
one.label one_label,
two.id two_id,
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;
递归查询方法mysql8支持(灵活)
递归并不会降低数据库的性能,但是为了避免无限递归 默认递归次数为1000次,可以通过设置cte_max_recursion_depth参数增加递归深度还可以设置max_execution_time限制执行时间,超过此时间也会终止递归操作。
根据子查到所有的父
WITH recursive t1 as(
select * from course_category where id ='1-1-1'
UNION all
select course_category.* from course_category INNER JOIN t1 on t1.parentid= course_category.id
)
SELECT * from t1 order by t1.id, t1.orderby;
根据父查到所有的子
WITH recursive t2 as(
select * from course_category where id ='1'
UNION all
select course_category.* from course_category INNER JOIN t2 on t2.id= course_category.parentid
)
SELECT * from t2 order by t2.id, t2.orderby;
java 代码实现
实体类po
package com.jhj.content.model.po;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
* <p>
* 课程分类
* </p>
*
* @author jhj
*/
@Data
@TableName("course_category")
public class CourseCategory implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private String id;
/**
* 分类名称
*/
private String name;
/**
* 分类标签默认和名称一样
*/
private String label;
/**
* 父结点id(第一级的父节点是0,自关联字段id)
*/
private String parentid;
/**
* 是否显示
*/
private Integer isShow;
/**
* 排序字段
*/
private Integer orderby;
/**
* 是否叶子
*/
private Integer isLeaf;
}
返回信息封装
package com.jhj.content.model.dto;
import com.jhj.content.model.po.CourseCategory;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.util.List;
/**
* @author jhj
* @version 1.0.0
* @ClassName CourseCategoryTreeDto.java
* @Description 课程分类树形结构
* @createTime 2024年05月22日 15:18:00
*/
@Data
@ToString
public class CourseCategoryTreeDto extends CourseCategory implements Serializable {
List<CourseCategoryTreeDto> childrenTreeNodes;
}
mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jhj.content.mapper.CourseCategoryMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.jhj.content.model.po.CourseCategory">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="label" property="label" />
<result column="parentid" property="parentid" />
<result column="is_show" property="isShow" />
<result column="orderby" property="orderby" />
<result column="is_leaf" property="isLeaf" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name, label, parentid, is_show, orderby, is_leaf
</sql>
<select id="selectTreeNode" parameterType="string" resultType="com.jhj.content.model.dto.CourseCategoryTreeDto">
WITH recursive t2 as(
select * from course_category where id =#{id}
UNION all
select course_category.* from course_category INNER JOIN t2 on t2.id= course_category.parentid
)
SELECT * from t2 order by t2.id, t2.orderby;
</select>
</mapper>
mapper
package com.jhj.content.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jhj.content.model.dto.CourseCategoryTreeDto;
import com.jhj.content.model.po.CourseCategory;
import java.util.List;
/**
* <p>
* 课程分类 Mapper 接口
* </p>
*
* @author jhj
*/
public interface CourseCategoryMapper extends BaseMapper<CourseCategory> {
//使用递归查询分类
public List<CourseCategoryTreeDto> selectTreeNode(String id);
}
service
package com.jhj.content.service;
import com.jhj.content.model.dto.CourseCategoryTreeDto;
import java.util.List;
/**
* @author jhj
* @version 1.0.0
* @ClassName CourseCategoryService.java
* @Description 课程分类实现接口
* @createTime 2024年05月22日 18:10:00
*/
public interface CourseCategoryService {
public List<CourseCategoryTreeDto> queryTreeNodes(String id);
}
serviceImpl
package com.jhj.content.service.impl;
import com.jhj.content.mapper.CourseCategoryMapper;
import com.jhj.content.model.dto.CourseCategoryTreeDto;
import com.jhj.content.service.CourseCategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author jhj
* @version 1.0.0
* @ClassName CourseCategoryServiceImpl.java
* @Description 课程分类实现类
* @createTime 2024年05月22日 18:10:00
*/
@Service
@Slf4j
public class CourseCategoryServiceImpl implements CourseCategoryService {
@Autowired
CourseCategoryMapper courseCategoryMapper;
@Override
public List<CourseCategoryTreeDto> queryTreeNodes(String id) {
List<CourseCategoryTreeDto> res=new ArrayList<>();
//调用mapper 递归查询出分类信息
List<CourseCategoryTreeDto> courseCategoryTreeDtos = courseCategoryMapper.selectTreeNode(id);
//找到每个节点的子节点,最终封装成List<CourseCategoryTreeDto>
//先将list 转换为map key为节点id,value为对象 目的是方便从map 获取节点 filter(item->!id.equals(item.getId()))排除根节点
Map<String, CourseCategoryTreeDto> map = courseCategoryTreeDtos.stream().filter(item->!id.equals(item.getId())).collect(Collectors.toMap(key -> key.getId(), value -> value, (key1, key2) -> key2));
courseCategoryTreeDtos.stream().filter(item->!id.equals(item.getId())).forEach(item->{
if(item.getParentid().equals(id)){
res.add(item);
}
//找到父节点
CourseCategoryTreeDto courseCategoryTreeDto = map.get(item.getParentid());
if(courseCategoryTreeDto!=null) {
//如果子为空 则new一个
if (courseCategoryTreeDto.getChildrenTreeNodes() == null) {
courseCategoryTreeDto.setChildrenTreeNodes(new ArrayList<CourseCategoryTreeDto>());
}
//往子里放
courseCategoryTreeDto.getChildrenTreeNodes().add(item);
}
});
//从头遍历list 一边遍历一边查找子节点放在父节点的childrenTreeNodes中
return res;
}
}
controller
package com.jhj.content.api;
import com.jhj.content.model.dto.CourseCategoryTreeDto;
import com.jhj.content.service.CourseCategoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author jhj
* @version 1.0.0
* @ClassName CourseCategoryController.java
* @Description 课程分类接口
* @createTime 2024年05月13日 01:00:00
*/
@Tag(name = "课程分类管理接口",description = "课程分类管理接口")
@RestController
public class CourseCategoryController {
@Autowired
CourseCategoryService courseCategoryService;
@Operation(summary = "课程分类树形数据")
@GetMapping("/course-category/tree-nodes")
public List<CourseCategoryTreeDto> queryTreeNodes(){
return courseCategoryService.queryTreeNodes("1");
}
}
接口响应结果
[
{
"id": "1-1",
"name": "前端开发",
"label": "前端开发",
"parentid": "1",
"isShow": 1,
"orderby": 1,
"isLeaf": 0,
"childrenTreeNodes": [
{
"id": "1-1-1",
"name": "HTML/CSS",
"label": "HTML/CSS",
"parentid": "1-1",
"isShow": 1,
"orderby": 1,
"isLeaf": 1,
"childrenTreeNodes": null
},
{
"id": "1-1-10",
"name": "其它",
"label": "其它",
"parentid": "1-1",
"isShow": 1,
"orderby": 10,
"isLeaf": 1,
"childrenTreeNodes": null
},
{
"id": "1-1-2",
"name": "JavaScript",
"label": "JavaScript",
"parentid": "1-1",
"isShow": 1,
"orderby": 2,
"isLeaf": 1,
"childrenTreeNodes": null
},
{
"id": "1-1-3",
"name": "jQuery",
"label": "jQuery",
"parentid": "1-1",
"isShow": 1,
"orderby": 3,
"isLeaf": 1,
"childrenTreeNodes": null
},