一、前言

我们在项目开发过程,经常会遇到有文章分类分栏,菜单分类,视频分类等多级分类

那么这种多级分类我们在数据中又是如何设计的呢?在mybatis查询过程中又是如何多级查询的呢?

别着急,今年我们来解决这个需求,

当然,解决的方法有很多,这里我只介绍我自己使用的一种,有更好的方法可以评论区评论大家一起探讨

本次用到了 Maven工程SwaggerRESTful接口风格MyBatis-Plus

整体目录结构如下
在这里插入图片描述

二、问题需求

以文章分类为例(这里以二级分类举例,三级、四级甚至多级会了二级的之后可以自行思考)

博客项目中少不了文章分类,如下图所示,那么在数据库如何只使用一张表、一条SQL语句就能完美实现多级目录结构的存储和查询
在这里插入图片描述

三、数据库设计

我们需要设计三个字段

字段名类型注释
idint这个不用说了吧,主键
namevarchar分类名称
parent_idint指向父级分类的ID,如果是父级分类则填0,如果是子分类则填父级分类的ID

接下来我们在mysql中创建出来
在这里插入图片描述

四、SQL多级分类查询

不知道大家都有没有用过 join 这个关键字呢,想必大家都挺少用到的吧,忘记的同学记得先去补补 join 的知识哦

采用 left join 左拼接的查询方式,完整的查询语句如下:

SELECT x.id AS parentId, x.name AS parentName, y.id AS childrenId ,y.name AS childrenName
FROM xy_category AS x
LEFT JOIN xy_category AS y ON y.parent_id = x.id
WHERE x.parent_id = 0

查询结果如下(所有分类结构):
在这里插入图片描述
当然你也可以指定某个父级分类,查询它所有的子分类,只需要改变一下 where 的条件就行了
在这里插入图片描述
是不是就满足了我们的需求了呢,接下来我们在项目MyBatis-Plus中实现多级分类查询

查询SQL语句我们已经写出来了,难的是如何存储这样的结构数据,别着急慢慢来

五、项目结构搭建

先把完整结构创建完成,这个大家应该都很熟悉了吧

我这里使用的MyBatis-Plus,操作都差不多,用MyBatis也可以

(1)创建实体类
/**
 * FileName:    Category
 * Author:      小袁
 * Date:        2022/4/15 10:42
 * Description: 分类实体
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true) // 调用Setting方法后 回传对象
public class Category {

    /**
     * 分类ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 分类栏目名称
     */
    private String name;

    /**
     * 父级栏目
     */
    private Integer parent_id;
}
(2)创建DAO
/**
 * FileName:    CategoryMapper
 * Author:      小袁
 * Date:        2022/4/15 10:52
 * Description: 分类DAO
 */
@Repository
public interface CategoryMapper extends BaseMapper<Category> {

    /**
     * 查询所有分类的目录结构
     * @return
     */
    List<CategoryParentVo> findCategoryList();

    /**
     * 通过某个父级分类的ID查询该父级的所有子分类
     * @param id
     * @return
     */
    CategoryParentVo getCategoryById(Integer id);
}
(3)创建业务层

我这里把完整的增删改查全部放出来了,这是我之前做的博客项目,可自行删减
R 这个类是统一结果返回类,前后端分析基本都是这样操作,网上也有很多模板

service – 接口
/**
 * FileName:    CategoryService
 * Author:      小袁
 * Date:        2022/4/15 10:52
 * Description: 分类栏目 Service
 */
public interface CategoryService extends IService<Category> {

    /**
     * 新增分类栏目数据
     * @param category
     * @return
     */
    R insert(Category category);

    /**
     * 根据分类栏目的ID进行修改数据
     * @param category
     * @return
     */
    R modify(Category category);

    /**
     * 根据分类栏目的ID进行删除
     * @param id
     * @return
     */
    R remove(Integer id);

    /**
     * 通过某个父级分类的ID查询该父级的所有子分类
     * @param id
     * @return
     */
    R getCategoryById(Integer id);

    /**
     * 查询所有分类的目录结构
     * @return
     */
    R listCategory();
}
servieImpl – 实现类
/**
 * FileName:    CategoryServiceImpl
 * Author:      小袁
 * Date:        2022/4/15 10:54
 * Description: 分类栏目的实现类
 */
@Service
@Transactional
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    @Autowired
    private CategoryMapper categoryMapper;

    @Override
    public R insert(Category category) {
        return categoryMapper.insert(category) == 0 ? R.error() : R.ok();
    }

    @Override
    public R modify(Category category) {
        return categoryMapper.updateById(category) == 0 ? R.error() : R.ok();
    }

    @Override
    public R remove(Integer id) {
        QueryWrapper<Category> wrapper = new QueryWrapper<>();
        wrapper.eq("id", id);
        wrapper.or();
        wrapper.eq("parent_id", id);
        return categoryMapper.delete(wrapper) == 0 ? R.error() : R.ok();
    }

    @Override
    public R getCategoryById(Integer id) {
        CategoryParentVo categoryParentVo = categoryMapper.getCategoryById(id);
        return categoryParentVo == null ? R.error() : R.ok().data("category", categoryParentVo);
    }

    @Override
    public R listCategory() {
        return R.ok().data("categoryList", categoryMapper.findCategoryList());
    }
}
(4)控制层

这里我也是将所有的代码贴出来,自行需要删除

采用RESTful的接口风格,@Api相关的注解是 Swagger 的相关配置,用于接口测试,你用其他测试方法可以把这个删掉

/**
 * FileName:    CategoryController
 * Author:      小袁
 * Date:        2022/4/15 10:54
 * Description: 分类栏目的控制层
 */
@RestController
@RequestMapping("/category")
@Api(tags = "分类栏目控制层")
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

    /**
     * 新增分类栏目数据
     * @param category
     * @return
     */
    @PostMapping
    public R insertCategory(@RequestBody Category category) {
        return categoryService.insert(category);
    }

    /**
     * 根据ID删除分类栏目
     * @param id
     * @return
     */
    @DeleteMapping("{id}")
    @ApiOperation(value = "根据ID删除所有子分类栏目(包括父级分类如果有)")
    public R removeCategoryById(@PathVariable(value = "id") Integer id) {
        return categoryService.remove(id);
    }

    @PutMapping
    public R modifyCategoryById(@RequestBody Category category) {
        return categoryService.modify(category);
    }

    /**
     * 查询所有分类的目录结构
     * @return
     */
    @GetMapping
    @ApiOperation(value = "查询所有分类的目录结构")
    public R getCategoryList() {
        return categoryService.listCategory();
    }

    /**
     * 根据ID获取对象
     * @param id
     * @return
     */
    @GetMapping("{id}")
    @ApiOperation(value = "根据ID获取对象")
    public R getCategoryById(@PathVariable(value = "id") Integer id) {
        return categoryService.getCategoryById(id);
    }
}

六、Vo对象

一会接收SQL多级查询结果要用到的,也叫视图对象(View Object),返回给前端看的

父级分类

/**
 * FileName:    CategoryVo
 * Author:      小袁
 * Date:        2022/4/15 14:16
 * Description:
 */
@Data
public class CategoryParentVo {

    // 父级分类编号ID
    private Integer parentId;

    // 父级分类名称
    private String parentName;

    // 子分类
    private List<CategoryChildrenVo> childrenCategory;
}

子分类

/**
 * FileName:    CategoryChildrenVo
 * Author:      小袁
 * Date:        2022/4/15 14:18
 * Description: 子分类
 */
@Data
public class CategoryChildrenVo {

    // ID编号
    private Integer childrenId;

    // 分类栏目名称
    private String childrenName;
}

七、MyBatis多级分类查询

(1)在resources目录下创建CategoryMapper.xml静态文件

用来写SQL语句的

别忘了在 application 加上mapper映射路径
在这里插入图片描述

<?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.xiaoyuan.back.db.dao.CategoryMapper">
	<!-- SQL语句 -->
</mapper>
(2)设计模式

回顾一下刚刚的数据格式,有点像什么?细心的同学已经发现了,没错,是不是和 JSON 数据格式有点类似?

将父级分类(Java、实战项目教学等)对应一个CategoryParentVo类

将每个父级分类的所有子分类对应一个List集合

每个CategoryParentVo有一个字分类的集合属性变量

看懂上面三句话就说明你已经掌握了,看不懂的结合创建Vo类看一下

在这里插入图片描述

(3)自定义返回类型模板(重点)

MyBatis的知识哦,不知道大伙忘了没?

<?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.xiaoyuan.back.db.dao.CategoryMapper">

    <!-- 自定义返回数据类型模板 -->
    <resultMap id="categoryMap" type="com.xiaoyuan.back.vo.CategoryParentVo">
        <!-- 父级分类(ID、分类名称) 单个 对象 -->
        <id property="parentId" column="parentId"></id>
        <result property="parentName" column="parentName"></result>
        <!-- 子分类(ID、分类名称) 多个 List集合 -->
        <collection property="childrenCategory" ofType="com.xiaoyuan.back.vo.CategoryChildrenVo" javaType="list">
            <id property="childrenId" column="childrenId"></id>
            <result property="childrenName" column="childrenName"></result>
        </collection>
    </resultMap>
</mapper>
(4)编写SQL

完整的CategoryMapper.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.xiaoyuan.back.db.dao.CategoryMapper">

    <!-- 自定义返回数据类型模板 -->
    <resultMap id="categoryMap" type="com.xiaoyuan.back.vo.CategoryParentVo">
        <id property="parentId" column="parentId"></id>
        <result property="parentName" column="parentName"></result>
        <collection property="childrenCategory" ofType="com.xiaoyuan.back.vo.CategoryChildrenVo" javaType="list">
            <id property="childrenId" column="childrenId"></id>
            <result property="childrenName" column="childrenName"></result>
        </collection>
    </resultMap>
    <!--
     查询所有分类的目录结构
     父级分类 ==>> 子分类
     -->
    <select id="findCategoryList" resultMap="categoryMap">
        select x.id as parentId, x.name as parentName, y.id as childrenId ,y.name as childrenName
        from xy_category x
        left join xy_category as y on y.parent_id = x.id
        where x.parent_id = 0
    </select>

    <!--
    通过某个父级分类的ID查询该父级的所有子分类
    父级分类 ==>> 子分类
    -->
    <select id="getCategoryById" parameterType="int" resultMap="categoryMap">
        select x.id as parentId, x.name as parentName, y.id as childrenId ,y.name as childrenName
        from xy_category x
        left join xy_category as y on y.parent_id = x.id
        where x.id = #{id}
    </select>
</mapper>

八、Swagger接口测试

我这个项目整合了 Swagger 进行接口测试,网上很多整合的教程,只需要加一个配置类就搞定了,一分钟就行,我这里附上吧

依赖

<!-- Swagger -->
<dependency>
	<groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>
/**
 * FileName:    SwaggerConfig
 * Author:      小袁
 * Date:        2022/3/11 19:09
 * Description:
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket webApiConfig(){

        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();

    }

    private ApiInfo webApiInfo(){

        return new ApiInfoBuilder()
                .title("Swagger接口测试")
                .description("小袁同学")
                .version("1.0")
                .contact(new Contact("Helen", "http://www.baidu.com", "1971788445@qq.com"))
                .build();
    }
}

打开网页进行测试
在这里插入图片描述
执行查询所有分类的接口,测试结果如下,ok完美获取数据
在这里插入图片描述在这里插入图片描述
或者直接在浏览器访问请求路径测试
在这里插入图片描述

  • 都看到这里啦,点点赞呀😋
  • 感谢阅读😘
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐