前言
多级分类在实战应用场景会出现很多,就比如你登录某个京东平台,你就会看到有商品分类。
红色方框内的都是各级分类的名称,所以分类在一个项目中是很有必要存在的,它可以提高用户的使用体验。所以怎么设计多级分类表就是一个关键因素了,我当时第一次接触这个多级分类的时候,我脑海的第一个想法是建表建无数个表,但是我少考虑的因素就是,这个创建分类是前端的事,前端难道创建一个分类就去创建一个表吗,这不可能的事所以要把分类抽象的看成一个字段就行了,创建分类目录,就相当于添加一条记录。
正文
有了这种思路,我们就可以创建一个最简单的分类表了。我把它命名为递归查询分类表。
实际上我只是不知道他叫什么名字,所以才这样取名字的。
先说一下,递归查询分类表有什么优缺点吧。
优点:就是他太过于普通普通到可以适用于任何数据库。
缺点:因为用到递归这个算法,所以当分类无限的分配下去,也就是说它的根部无限的远的情况下,各种性能问题也就伴随而来。
递归查询分类表如何建表
id | 分类名称 | 父级id | 分类等级 | 分类内容 |
1 | 一级文件夹 | 0 | 1 | |
2 | 二级文件夹(手机) | 1 | 2 | 手机配件 |
3 | 二级文件夹(手机) | 1 | 2 | 手机通讯 |
4 | 三级文件夹(手机通讯) | 3 | 3 | 游戏手机 |
5 | 三级文件夹(手机通讯) | 3 | 3 | 二手手机 |
根据上面京东的页面我可以建立出这样的表。
画一个该表的原型大概如下
这样就是一个简单的分类表了,但是分类表建立起来是容易使用起来就需要一定的方式,就像开篇就说了,这种分类表的建立查询所有分类的时候就需要用到递归的方式来获取,如果根部很深,那么你的查询就会超时,就会报错,然后数据库就会崩掉,但是话又说回来了,如果分类等级可以确定在三级内,这种方式反而更方便,因为他的逻辑没有太大的难度。
下面我会给出递归查询返回树形结构的核心代码:
@Override
public List<Category> findAllCategories() {
// 1. 查询所有分类
List<Category> allCategories = (List<Category>) categoryRepository.findAll();
// 2. 创建两个Map,一个用于存储分类ID到分类对象的映射,另一个用于存储父分类ID到子分类列表的映射
Map<Integer, Category> categoriesById = new HashMap<>();
Map<Integer, List<Category>> childrenByParentId = new HashMap<>();
// 3. 遍历所有分类,填充两个Map
for (Category category : allCategories) {
categoriesById.put(category.getId(), category); // 存储分类ID到分类对象的映射
if (!childrenByParentId.containsKey(category.getParentId())) {
childrenByParentId.put(category.getParentId(), new ArrayList<>()); // 如果父分类ID对应的子分类列表不存在,则创建一个新的列表
}
childrenByParentId.get(category.getParentId()).add(category); // 将当前分类添加到其父分类的子分类列表中
}
// 4. 获取所有根分类(父ID为0的分类)
List<Category> rootCategories = new ArrayList<>(childrenByParentId.get(0));
// 5. 调用递归方法构建树形结构
buildTree(rootCategories, childrenByParentId);
// 6. 返回根分类列表
return rootCategories;
}
/**
* 递归构建树形结构。
*
* @param categories 当前层级的分类列表
* @param childrenByParentId 父分类ID到子分类列表的映射
*/
private void buildTree(List<Category> categories, Map<Integer, List<Category>> childrenByParentId) {
for (Category category : categories) {
// 获取当前分类的所有子分类
List<Category> children = childrenByParentId.get(category.getId());
if (children != null) {
category.setChildren(children); // 设置当前分类的子分类列表
buildTree(children, childrenByParentId); // 递归调用,继续构建子分类的子分类
}
}
}
}
返回的格式
[
{
"id": 1,
"name": "电子产品",
"children": [
{
"id": 3,
"name": "智能手机",
"children": []
},
{
"id": 4,
"name": "笔记本电脑",
"children": [
{
"id": 6,
"name": "游戏本",
"children": []
},
{
"id": 7,
"name": "轻薄本",
"children": [
{
"id": 8,
"name": "超轻薄本",
"children": []
}
]
}
]
},
{
"id": 5,
"name": "智能穿戴设备",
"children": []
}
]
},
{
"id": 2,
"name": "服装",
"children": []
}
]
这就是递归分类表,当然除了递归分类表还有其他的方式来建表,
比如:
路径记录法:
优点就是:查询速度快。
缺点:更新时需要更新所有子节点的路劲信息。
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
parent_id INT DEFAULT 0,
path VARCHAR(255),
description TEXT,
sort_order INT DEFAULT 0,
FOREIGN KEY (parent_id) REFERENCES categories(id)
);
嵌套集模型
使用两个字段来表示分类的位置,如left_value和right_value。
优点:是查询速度快。
缺点:是插入和删除操作较为复杂。
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
parent_id INT DEFAULT 0,
path VARCHAR(255),
description TEXT,
sort_order INT DEFAULT 0,
FOREIGN KEY (parent_id) REFERENCES categories(id)
);
闭包表法:
使用额外一张表来记录父子关系,适合于复杂的多级关系。
优点:是查询速度。
缺点:是需要额外的存储空间
CREATE TABLE category_closure (
id INT AUTO_INCREMENT PRIMARY KEY,
parent_id INT NOT NULL,
child_id INT NOT NULL,
FOREIGN KEY (parent_id) REFERENCES categories(id),
FOREIGN KEY (child_id) REFERENCES categories(id)
);
缓存机制:对于频繁访问的分类数据,可以使用缓存来提高性能。
索引优化:为parent_id、left_value和right_value等字段创建索引。
后面我会分享其他分类表如何快速获取树形结果。感兴趣的小伙伴可以持续关注一下。