一、什么是 Primsa 自引用?
"自引用" 通常指的是一个数据结构或对象引用自身的情况。
在数据库模型中,自引用可能指的是在一个表中的某一列引用了同一表中的其他行的数据,通常用来表示层次结构。
二、自引用解决了哪些问题?
- 菜单(多级菜单、动态菜单、权限管理)
- 评论与回复
- 组织结构
- 目录结构
- 分类系统
- 社交关系
- 有向图
三、菜单示例
本示例一 sqlite 为例, 包含一个菜单项目的 Schema 文件
schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model MenuItem {
id Int @id @default(autoincrement())
label String
parent MenuItem? @relation("ChildToParent", fields: [parentId], references: [id])
parentId Int?
children MenuItem[] @relation("ChildToParent")
}
四、解析
此 Schema 只有一个 label
是实际字段,假设它保存就是 路由路径
。
- children 字段关联
MenuItem
类型也就是它自己 - parent 字段通过 parentId 与自己关联
由此形成了一个简单的 自引用关系
,其中包含了 父与子
是一对多的关系,而 子与父
是多对一的关系。
五、制作模型工具
ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
prisma.$connect().then((err) => {
console.log(err);
});
export { prisma };
六、创建菜单根路由
ts
const createMenuItemRoot = async () => {
const menuItem = await prisma.menuItem.create({
data: {
label: '/',
},
});
console.log('created menu items ', menuItem);
};
createMenuItemRoot()
在数据库中插入一条路由的顶层数据,也就是根路由。
七、创建子路由
ts
const createMenuItem = async (label, parentId) => {
const menuItem = await prisma.menuItem.create({
data: {
label,
parentId,
},
});
console.log('created menu items ', menuItem);
};
八、创建子路由示例
创建三个一级菜单:
ts
createMenuItem('/dashboard', 1); // id 2
createMenuItem('/analysis', 1);
createMenuItem('/monitor', 1);
给 dashboard 路由创建子菜单
ts
createMenuItem('/analysis/p1', 2);
createMenuItem('/analysis/p2', 2);
得到的表的结构:
九、获取列表
ts
const findAll = async () => {
const menus = await prisma.menuItem.findMany();
console.log(menus);
};
十、深层次查找
使用 prisma 进行深层次查找(也就是查找 children):
ts
const findChildren = async () => {
const menus = await prisma.menuItem.findMany({
where: {
id: 1,
},
include: {
children: {
where: {
parentId: 1,
},
include: {
children: {
where: {
parentId: 2,
},
},
},
},
},
});
console.log(menus[0].children);
};
十一、根据 id 查询所有的子菜单
使用
递归
的方式,查询菜单的所有菜单。
ts
async function getMenus(menuId) {
const node = await prisma.menuItem.findUnique({
where: { id: menuId },
include: {
children: {
where: {
parentId: menuId,
},
},
},
});
if (!node) {
return {};
}
if (node.children.length > 0) {
for (const child of node.children) {
const childNodes = await getMenus(child.id);
child.children = childNodes;
}
}
return node;
}
十二、获取子引用表中的菜单
ts
getMenus(1) // 顶级
当能够方便的查询到树状结构的数据的时候,我们就能方便的展示在管理系统的权限管理中。
十三、小结
本文主要讲解Prsima 的自引用的模型以及关于菜单使用方式。自引用解决了类似树状数据结构问题,以菜单为例,当我们需要查询一个角色的对应的菜单的时候,此时自引用数据结构就会变得非常有用。