业务场景
在项目开发过程中,往往会遇到多级菜单、分类等多层级结构数据的查询。
for example:

请看上图,这是一个电商项目中常见的多级类目功能,如图所示,共分为一、二、三,共三级类目,每一个一级类目有各自的二级目录,每个二级目录有自己的三级目录

再看这个例子,下图是一个多级菜单的功能,和上面的例子类似

在这种场景下,通常我们要用递归进行处理。下面博主以电商项目的多级类目功能,通过MyBatis进行递归查询功能说明

开发环境

名称 版本
IntelliJ IDEA 2021.3.2
Spring Boot 2.6.4
mybatis-spring-boot-starter 2.2.2

数据库

表结构

CREATE TABLE `shopping_commodity_category`
(`id`                int          NOT NULL AUTO_INCREMENT,`name`              varchar(255) NOT NULL COMMENT 'category name',`picture`           varchar(255)          DEFAULT NULL COMMENT 'category picture id',`superior_category` int          NOT NULL DEFAULT 0 COMMENT 'superior category id',`sort_number`       int          NOT NULL COMMENT 'sort number',`create_time`       datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'creation time of this record',`update_time`       datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time of this record',PRIMARY KEY (`id`),UNIQUE KEY (`superior_category`, `name`),UNIQUE KEY (`superior_category`, `sort_number`)
);

字段解释:

字段名 含义
id 数据表中每条数据的唯一标识符
name 类目名称
picture 类目的预览图id(由于业务需要,这个地方保存的另一张表的文件的id,可根据需要直接存储图片地址),由于上级父分类没有预览图,所以可以为空
superior_category 上级类目的id,不能为空,如果是顶级类目,那么他的上级类目id就为0
sort_number 排序号,用于在同一级的目录下进行排序
create_time 数据的创建时间,无需关注,值是插入数据时自动通过当前时间戳填充
update_time 数据的更新时间,无需关注,值是在当这条记录被更新时自动以当前时间戳进行更新

约束解释:

类型 用途
主键约束(id) (显而易见,无需赘述)
联合唯一约束(UNIQUE KEY (superior_category, name)) 在同一级类目下面,限制不能有重复的类目名称
联合唯一约束(UNIQUE KEY (superior_category, sort_number)) 在同一级类目下,限制排序号不能重复

由于博主这个地方业务需要,用到了另一张表的数据,为了说明问题,博主将另一张表的结构也贴出来参考

CREATE TABLE `storage_multimedia_file`
(`id`            int      NOT NULL AUTO_INCREMENT,`absolute_path` text     NOT NULL COMMENT 'file absolute path',`create_time`   datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'creation time of this record',`update_time`   datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time of this record',PRIMARY KEY (`id`)
);

这个表很简单,就一个主要的字段,存储的这个文件的绝对路径,方便后续通过IO流读取

插入测试数据

shopping_commodity_category表

INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (1, '手机通讯', null, 0, 1, '2022-08-11 17:27:15', '2022-08-11 17:27:15');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (2, '手机配件', null, 1, 1, '2022-08-11 17:29:59', '2022-08-11 17:29:59');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (3, '手机耳机', '2', 2, 1, '2022-08-11 17:29:59', '2022-08-11 17:29:59');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (4, '蓝牙耳机', '3', 2, 2, '2022-08-11 17:31:26', '2022-08-11 17:31:26');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (5, '手机壳/保护壳', '4', 2, 3, '2022-08-11 17:32:51', '2022-08-11 17:32:51');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (6, '手机贴膜', '5', 2, 4, '2022-08-11 17:33:44', '2022-08-11 17:33:44');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (7, '运营商', null, 1, 2, '2022-08-11 17:34:44', '2022-08-11 17:34:44');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (8, '办号卡', '6', 7, 1, '2022-08-11 17:36:25', '2022-08-11 17:36:25');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (9, '家用电器', null, 0, 2, '2022-08-11 17:36:54', '2022-08-11 17:36:54');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (10, '生活电器', null, 9, 1, '2022-08-11 17:37:46', '2022-08-11 17:37:46');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (11, '吸尘器', '7', 10, 1, '2022-08-11 17:38:55', '2022-08-11 17:38:55');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (12, '厨房小电', null, 9, 2, '2022-08-11 17:40:04', '2022-08-11 17:40:04');
INSERT INTO shopping_commodity_category (id, name, picture, superior_category, sort_number, create_time, update_time) VALUES (13, '电饭煲', '8', 12, 1, '2022-08-11 17:41:17', '2022-08-11 17:41:17');

storage_multimedia_file表

INSERT INTO trembling_bird.storage_multimedia_file (id, absolute_path, create_time, update_time) VALUES (1, 'D:\\files\\trembling-bird\\commodity-previews\\1.webp', '2022-08-11 16:18:49', '2022-08-11 16:18:49');
INSERT INTO trembling_bird.storage_multimedia_file (id, absolute_path, create_time, update_time) VALUES (2, 'D:\\files\\trembling-bird\\category-preview\\1.jpg', '2022-08-11 17:30:12', '2022-08-11 17:30:12');
INSERT INTO trembling_bird.storage_multimedia_file (id, absolute_path, create_time, update_time) VALUES (3, 'D:\\files\\trembling-bird\\category-preview\\2.jpg', '2022-08-11 17:31:07', '2022-08-11 17:31:07');
INSERT INTO trembling_bird.storage_multimedia_file (id, absolute_path, create_time, update_time) VALUES (4, 'D:\\files\\trembling-bird\\category-preview\\3.png', '2022-08-11 17:32:07', '2022-08-11 17:32:07');
INSERT INTO trembling_bird.storage_multimedia_file (id, absolute_path, create_time, update_time) VALUES (5, 'D:\\files\\trembling-bird\\category-preview\\4.jpg', '2022-08-11 17:33:36', '2022-08-11 17:33:36');
INSERT INTO trembling_bird.storage_multimedia_file (id, absolute_path, create_time, update_time) VALUES (6, 'D:\\files\\trembling-bird\\category-preview\\5.png', '2022-08-11 17:35:48', '2022-08-11 17:35:48');
INSERT INTO trembling_bird.storage_multimedia_file (id, absolute_path, create_time, update_time) VALUES (7, 'D:\\files\\trembling-bird\\category-preview\\6.jpg', '2022-08-11 17:38:22', '2022-08-11 17:38:22');
INSERT INTO trembling_bird.storage_multimedia_file (id, absolute_path, create_time, update_time) VALUES (8, 'D:\\files\\trembling-bird\\category-preview\\7.jpg', '2022-08-11 17:40:38', '2022-08-11 17:40:38');

关键代码

实体类代码

实体类的公共父类(因为数据库中每个表都有共同的字段,如idcreate_time,update_time,所以将这些共有的字段抽取出来放到公共的父类,让其他实体类继承此父类,就有了这些公共字段,降低代码冗余)

package com.fenzhimedia.commons.pojo;import lombok.Data;import java.io.Serializable;
import java.time.LocalDateTime;/*** @author Yi Dai 484201132@qq.com* @since 2022/3/21 13:55*/@Data
public abstract class BasePojo implements Serializable {/*** the unique identification of this record in the database table*/protected int id;/*** creation time of this record*/protected LocalDateTime createTime;/*** update time of this record*/protected LocalDateTime updateTime;
}

描述类目的实体类

package com.fenzhimedia.commons.shopping.pojo;import com.fenzhimedia.commons.pojo.BasePojo;
import com.fenzhimedia.commons.storage.pojo.MultimediaFile;
import lombok.Data;
import lombok.EqualsAndHashCode;import java.util.List;/*** @author Yi Dai 484201132@qq.com* @since 2022/4/23 10:43*/@Data
@EqualsAndHashCode(callSuper = true)
public class CommodityCategory extends BasePojo {/*** category name*/private String name;/*** entity class encapsulating picture information*/private MultimediaFile picture;/*** used to specify the order of categories,* which is only valid under the same level category*/private Integer sortNumber;/*** sub commodity category*/private List<CommodityCategory> subCommodityCategories;
}

描述文件的实体类

package com.fenzhimedia.commons.storage.pojo;import com.fenzhimedia.commons.pojo.BasePojo;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** @author Yi Dai 484201132@qq.com* @since 2022/8/11 16:03*/@Data
@EqualsAndHashCode(callSuper = true)
public class MultimediaFile extends BasePojo {private String absolutePath;}

MyBatis的mapper接口代码

package com.fenzhimedia.shopping.mapper;import com.fenzhimedia.commons.shopping.pojo.CommodityCategory;import java.util.List;/*** @author Yi Dai 484201132@qq.com* @since 2022/8/11 16:46*/public interface CommodityCategoryMapper {List<CommodityCategory> queryCommodityCategories(int superiorCategoryId);}

mapper映射文件代码

<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fenzhimedia.shopping.mapper.CommodityCategoryMapper"><select id="queryCommodityCategories" resultMap="commodityCategoryMap">select `shopping_commodity_category`.`id`                as `shopping_commodity_category_id`,`shopping_commodity_category`.`name`              as `shopping_commodity_category_name`,`shopping_commodity_category`.`picture`           as `shopping_commodity_category_picture`,`shopping_commodity_category`.`superior_category` as `shopping_commodity_category_superior_category`,`storage_multimedia_file`.`id`                    as `storage_multimedia_file_id`,`storage_multimedia_file`.`absolute_path`         as `storage_multimedia_file_absolute_path`from `shopping_commodity_category`left join `storage_multimedia_file`on `shopping_commodity_category`.`picture` = `storage_multimedia_file`.`id`where `superior_category` = #{categoryId}order by `shopping_commodity_category`.`sort_number`</select><resultMap id="commodityCategoryMap" type="commodityCategory"><id property="id" column="shopping_commodity_category_id"/><result property="name" column="shopping_commodity_category_name"/><association property="picture" javaType="multimediaFile"><id property="id" column="storage_multimedia_file_id"/><result property="absolutePath" column="storage_multimedia_file_absolute_path"/></association><collection property="subCommodityCategories"ofType="commodityCategory"column="shopping_commodity_category_id"select="queryCommodityCategories"/></resultMap></mapper>

代码解释:
可以看到,queryCommodityCategories是一个类目表和文件表的左连接查询,然后分别起了别名,接收一个int型的参数,为上级类目的id,当然也就是0,然后声明一个resultMap ,将字段映射起来,都是基本操作,唯一值得注意的就是
subCommodityCategories作为一个集合,它有通过select直接调用了queryCommodityCategories这个查询,而参数正是由column属性传递过去的,参数的值就是当前类目的id(shopping_commodity_category_id)(有点绕),其实这样就递归查询起来了

那么有的小伙伴可能会疑问,既然是已知是0,为何不直接写道xml中,而是大费周章的,在接口上声明一个参数,然后传递进来?其实这是因为后面的递归查询的时候需要传入上级分类的id,一旦写死了,就只能查询上级分类id为0的,很显然,达不到想要的效果

既然如此,那么就需要业务代码中来传递上级类目id这个参数,很显然我们直接在代码中写死不是那么的优雅。博主这里为了更灵活,我想要实现一个如果前台传递了上级分类id,那么就帮他查询指定上级类目的子级类目,如果没有传递,那么就查询所有的类目及其子类目。所以博主是这么处理的:

 @GetMapping("/queryCommodityCategories")public ResponseBody queryCommodityCategories(@RequestParam(required = false, defaultValue = "0") int superiorCategoryId) {return commodityCategoryService.queryCommodityCategories(superiorCategoryId);}

查询测试

返回结果

statusCodemessage是通用返回实体类的结构,无需关注,查询的数据在data

{"statusCode": 200, "message": null, "data": [{"id": 1, "createTime": null, "updateTime": null, "name": "手机通讯", "picture": null, "sortNumber": null, "subCommodityCategories": [{"id": 2, "createTime": null, "updateTime": null, "name": "手机配件", "picture": null, "sortNumber": null, "subCommodityCategories": [{"id": 3, "createTime": null, "updateTime": null, "name": "手机耳机", "picture": {"id": 2, "createTime": null, "updateTime": null, "absolutePath": "D:\\files\\trembling-bird\\category-preview\\1.jpg"}, "sortNumber": null, "subCommodityCategories": [ ]}, {"id": 4, "createTime": null, "updateTime": null, "name": "蓝牙耳机", "picture": {"id": 3, "createTime": null, "updateTime": null, "absolutePath": "D:\\files\\trembling-bird\\category-preview\\2.jpg"}, "sortNumber": null, "subCommodityCategories": [ ]}, {"id": 5, "createTime": null, "updateTime": null, "name": "手机壳/保护壳", "picture": {"id": 4, "createTime": null, "updateTime": null, "absolutePath": "D:\\files\\trembling-bird\\category-preview\\3.png"}, "sortNumber": null, "subCommodityCategories": [ ]}, {"id": 6, "createTime": null, "updateTime": null, "name": "手机贴膜", "picture": {"id": 5, "createTime": null, "updateTime": null, "absolutePath": "D:\\files\\trembling-bird\\category-preview\\4.jpg"}, "sortNumber": null, "subCommodityCategories": [ ]}]}, {"id": 7, "createTime": null, "updateTime": null, "name": "运营商", "picture": null, "sortNumber": null, "subCommodityCategories": [{"id": 8, "createTime": null, "updateTime": null, "name": "办号卡", "picture": {"id": 6, "createTime": null, "updateTime": null, "absolutePath": "D:\\files\\trembling-bird\\category-preview\\5.png"}, "sortNumber": null, "subCommodityCategories": [ ]}]}]}, {"id": 9, "createTime": null, "updateTime": null, "name": "家用电器", "picture": null, "sortNumber": null, "subCommodityCategories": [{"id": 10, "createTime": null, "updateTime": null, "name": "生活电器", "picture": null, "sortNumber": null, "subCommodityCategories": [{"id": 11, "createTime": null, "updateTime": null, "name": "吸尘器", "picture": {"id": 7, "createTime": null, "updateTime": null, "absolutePath": "D:\\files\\trembling-bird\\category-preview\\6.jpg"}, "sortNumber": null, "subCommodityCategories": [ ]}]}, {"id": 12, "createTime": null, "updateTime": null, "name": "厨房小电", "picture": null, "sortNumber": null, "subCommodityCategories": [{"id": 13, "createTime": null, "updateTime": null, "name": "电饭煲", "picture": {"id": 8, "createTime": null, "updateTime": null, "absolutePath": "D:\\files\\trembling-bird\\category-preview\\7.jpg"}, "sortNumber": null, "subCommodityCategories": [ ]}]}]}]
}

完结

如有编辑错误或其他问题,欢迎联系博主:18380924397(微信同号)

MyBatis递归查询相关推荐

  1. 使用mybatis实现递归查询,mybatis递归查询

    由于部门的层级不可控,因此如果我想要获取所有部门的完整json的话,就要采用递归调用,这里的递归调用我们可以利用MyBatis的ResultMap中的collection实现,核心代码如下 <r ...

  2. MySQL递归查询,Oracle递归查询,MyBatis+MySQL实现递归查询

    递归查询用于查询树形结构的列表,比如行政区列表.包括向下递归查询:根据父级查询子级:向上查询:根据子级查询父级.mysql需要使用存储函数,oracle可以使用connect by语句直接查询. My ...

  3. mybatis框架实现一对多、多对多关系查询,以及递归查询(单表多级分类:省市区三级地址查询)

    mybatis框架练习 mybatis框架中,包括实体类(这些实体类与数据库中的字段属性相对应),mybatis的配置文件(即mybatis-config.xml,这个配置文件用于连接实体类和orm( ...

  4. Mybatis通过colliection属性递归获取菜单树

    1.现有商品分类数据表category结构如下,三个字段都为varchar类型 2.创建商品分类对应的数据Bean /*** */ package com.xdw.dao;import java.ut ...

  5. mysql+mybatis递归调用

    递归调用的应用场景常常出现在多级嵌套的情况,比如树形的菜单.下面通过一个简单的例子来实现mysql+mybatis的递归. 数据模型 private Integer categoryId;privat ...

  6. MyBatis 核心对象,工作原理及源码解读

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) Mybatis工作原理 InputStream inputStream = Resources.getResourceAsStream(reso ...

  7. sql递归查询上级_递归的实际业务场景之MySQL 递归查询

    喜欢就点个赞呗! 源码<--请点击此处查看 引入 当我看到一些评论时,例如下面的样子.我挺好奇这个功能是怎么样做出来的.进过查阅资料,发现这其实是 MySQL 的递归操作.下面就让我操作一下怎么 ...

  8. SQL递归查询树型分类数据

    目录 前言 1.准备分类数据 2.递归原理 3.实现 4.结合mybatis查询 总结 前言 相信大家在处理业务的时候经常会遇到分类数据,当面对这种情况时该如何处理呢?在这里我使用了两种方式解决:一种 ...

  9. java递归查询分类及分类下所有子分类

    该案例是实际开发中运用,java递归查询分类及分类下所有子分类. 代码走起: 1.jsp页面布局样式这里不再介绍,js业务逻辑展示分类树形结构如下: /*** 商品分类操作*//*** 初始化*/ $ ...

  10. 数据递归查询的两种实现方法

    在业务代码当中,经常需要递归查询有等级结构的数据. 现在是两种实现方法. 第一种在oracle当中,使用 start with connect by prior 递归查询 附递归查询用法 https: ...

最新文章

  1. 费用节省 50%,函数计算 FC 助力分众传媒降本增效
  2. FICO 最常用配置表
  3. linux 基础知识及命令总结
  4. Jmeter入门3 http请求—content-type与参数
  5. Win32EXE.tpl 代码详解
  6. select 和epoll模型区别
  7. E. The Child and Binary Tree(生成函数 + 多项式)
  8. windows 服务部署管理
  9. 论文浅尝 | 基于常识知识图谱感知和图注意力机制的对话生成
  10. java 刽子手游戏_java基础(九):容器
  11. 一个IT经理眼中的RTX、Simba2013与Lync
  12. nginx优化(经典)
  13. Mac下使用tree命令
  14. 数据3分钟丨​PingCAP DevCon 2021回顾;openGauss社区颁发首张OGCA认证证书
  15. [SCOI2010]连续攻击游戏
  16. 6.旋转数组的最小数字
  17. 解决黑苹果(bigsur)ALC255声卡声音发虚问题
  18. 600集Python从入门到精通教程(懂中文就能学会)
  19. 艺术科技杂志艺术科技杂志社艺术科技编辑部2022年第4期目录
  20. 山西民生云大同员认证在什么网_山西民生云app下载-山西民生云大同app认证下载手机版 v2.2-91优手机网...

热门文章

  1. MAVEN专题之九、多环境构建,作为核心开发,这个玩不转有点说不过去!
  2. intellij idea编辑器好看炫酷主题配色方案推荐
  3. java 8 中文字体_jdk安装中文字体,解决Can't read the embedded font LNUHUF+SimSun
  4. loadrunner11压力测试设置
  5. minium环境配置——微信开发者工具
  6. app生成(免费自制app软件)
  7. gradle下载很慢
  8. C专家编程 五 声明的优先级规则
  9. 监狱干警定位管理系统
  10. 【PMP】工作分解结构WBS详解