nodejs 中使用ORM-prisma
ORM-prisma
prisma 介绍
prisma 是一个 ORM 工具,它可以帮助我们在 nodejs 中操作数据库。
它支持多种数据库,包括 MySQL、PostgreSQL、SQLite、Microsoft SQL Server 等。
它的主要功能包括:
- 数据库迁移
- 数据库查询
- 数据库事务
- 数据库种子数据
prisma 安装
安装prisma cli 和 prisma client
npm install prisma --save-dev
npm install @prisma/client初始化prisma
npx prisma init生成文件结构
prisma/
schema.prisma # 模型定义文件
.env # 数据库连接配置在.env文件中配置数据库连接
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"定义模型
在schema.prisma文件中定义模型
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}关键字对应
| 关键字 | 功能描述 | 示例说明 | 代码示例 |
|---|---|---|---|
@default | 为字段设置默认值(支持固定值/内置函数) | 常用内置函数:now()(当前时间)、autoincrement()(自增)、uuid()(唯一ID) | model Post { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) isPublished Boolean @default(false) } |
@unique | 约束字段值在数据库表中唯一不重复 | 适用于邮箱、手机号、用户名等需唯一的字段 | model User { id Int @id @default(autoincrement()) email String @unique } |
@updatedAt | 自动记录字段最后修改时间(仅适配DateTime类型) | Prisma语法糖,无需手动更新,常用于记录数据更新时间 | model Post { id Int @id @default(autoincrement()) title String updatedAt DateTime @updatedAt } |
@relation | 定义模型间的关联关系(一对一/一对多/多对多) | 必传fields(外键字段)和references(关联模型主键) | model Post { id Int @id @default(autoincrement()) authorId Int author User @relation(fields: [authorId], references: [id]) } |
@map | 映射Prisma字段/模型名到数据库实际列名/表名 | 解决Prisma驼峰命名与数据库下划线命名的规范差异 | model User @map("users") { userId Int @id @default(autoincrement()) @map("user_id") userName String @map("user_name") } |
@ignore | 标记字段仅在Prisma中使用,不同步到数据库表 | 适用于临时计算字段、无需持久化的辅助字段 | model User { id Int @id @default(autoincrement()) tempRemark String @ignore } |
@db.xxx | 指定字段对应的数据库原生数据类型(覆盖Prisma默认映射) | 如@db.VarChar(255)、@db.Int64、@db.Text等,适配数据库专属类型 | model User { id Int @id @default(autoincrement()) name String @db.VarChar(100) phone BigInt @db.Int64 } |
总结
- 高频核心:
@id(主键)、@default(默认值)、@unique(唯一约束)是单表定义的基础,几乎每个模型都会用到; - 关联与便捷:
@relation(表关联)、@updatedAt(自动更新时间)是业务开发中提升效率的关键语法糖; - 适配类:
@map(命名映射)、@db.xxx(原生类型)用于适配数据库规范,@ignore用于非持久化字段。
注意
数据库查询语句时,使用的是Prisma模型定义的字段名,而不是数据库实际列名。
数据库迁移
执行数据库迁移命令,将模型定义同步到数据库
npx prisma migrate dev --name init重新生成prisma client
npx prisma generate操作数据库
数据库表查询
使用prisma client查询数据库
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();使用方法: prisma.模型名.方法名
- prisma.模型中的 “模型” 是 PrismaClient 根据schema.prisma的model自动生成的表操作入口属性;
- 命名规则默认是 model 名称的小驼峰,与数据库实际表名(@map修改的)无关;
- 这个 “模型属性” 包含对应表的所有 CRUD 方法,是操作单表的核心入口
- 常用查询用的关键字
方法名 功能描述 示例说明 代码示例 findFirst查询第一条匹配的记录(可基于任意条件) 按非唯一条件查第一条数据(如查首个未发布帖子) const post = await prisma.post.findFirst({where: { isPublished: false }});findMany查询所有匹配的多条记录(默认返回全部,可分页/筛选) 列表查询(如查所有用户、分页查帖子) 查所有已发布的帖子,分页(跳过前10条,取10条)const posts = await prisma.post.findMany({where: { isPublished: true },skip: 10,take: 10});count查询匹配条件的记录总数 统计数量(如统计已发布帖子数) 统计已发布的帖子数量 const publishedCount = await prisma.post.count({ where: { isPublished: true }});findUniqueOrThrow同 findUnique,但无匹配结果时抛出异常(避免空值)必须找到单条数据的场景(如根据ID查用户) 查id=1的用户,不存在则抛异常const user = await prisma.user.findUniqueOrThrow({ where: { id: 1 }});findManyOrThrow同 findMany,无匹配结果时抛出异常必须返回数据的列表查询 // 查所有管理员用户,无结果则抛异常const admins = await prisma.user.findManyOrThrow({where: { role: 'admin' }});aggregate对查询结果进行聚合计算(如求和、计数、平均值等) 统计分析(如计算所有用户的平均年龄), _avg(平均)、_sum(总和)、_count(数量)、_min(最小值)、_max(最大值)等// 计算所有用户的平均年龄const avgAgeResult = await prisma.user.aggregate({ _avg: { age: true } }); // 返回 { _avg: { age: 28 } }const avgAge = avgAgeResult._avg.age; // 获取平均值 // 同时计算多个字段的总和const sumResult = await prisma.user.aggregate({ _sum: { viewCount: true, downloadCount: true } }); // 返回 { _sum: { viewCount: 1000, downloadCount: 500 } }// 分开获取不同字段的总和值const totalViews = sumResult._sum.viewCount; // 获取viewCount总和const totalDownloads = sumResult._sum.downloadCount; // 获取downloadCount总和
2.筛选条件关键字(where 内的核心参数)where 是所有查询方法的核心筛选参数,内部可使用以下关键字定义等值、范围、模糊匹配等条件。
| 关键字 | 核心含义 | 使用场景 | 示例代码(where 内) |
|---|---|---|---|
equals | 精确匹配(默认省略,直接写值等价于equals) | 等值查询(如查name=”张三”) | 两种写法等价:查name等于"张三"的用户 where: { name: "张三" } // 等价于 where: { name: { equals: "张三" } } |
not | 取反条件(排除指定值) | 排除查询(如查name≠”张三”) | ```查name不等于”张三”的用户 where: { name: { not: “张三” } }` |
in/notIn | 匹配/不匹配数组中的任意值 | 批量查询/排除(如查id为1/2/3的用户) | 查id在[1,2,3]中的用户 where: { id: { in: [1,2,3] } } // 查id不在[1,2,3]中的用户 where: { id: { notIn: [1,2,3] } } |
gt/gte | 大于(gt)/大于等于(gte) | 数值/时间范围(如查年龄>18) | // 查年龄大于18的用户 where: { age: { gt: 18 } } // 查创建时间>=2025-01-01的帖子 where: { createdAt: { gte: new Date("2025-01-01") } } |
lt/lte | 小于(lt)/小于等于(lte) | 数值/时间范围(如查年龄<30) | // 查年龄小于30的用户 where: { age: { lt: 30 } } |
contains | 模糊匹配(包含指定字符串,大小写敏感) | 关键词搜索(如标题含”Prisma”) | // 查标题包含"Prisma"的帖子 where: { title: { contains: "Prisma" } } |
containsInsensitive | 模糊匹配(大小写不敏感) | 不区分大小写的搜索 | // 查标题包含"prisma"(不区分大小写)的帖子 where: { title: { containsInsensitive: "prisma" } } |
AND/OR/NOT | 多条件组合(AND:同时满足;OR:满足其一;NOT:不满足) | 复杂条件查询 | // 查年龄>18 且 name包含"张" 的用户 where: { AND: [ { age: { gt: 18 } }, { name: { contains: "张" } }]} |
3.关联查询关键字(查询关联模型数据)
用于查询主模型时,同时获取其关联模型的数据(如查用户时同时查其发布的帖子)。
| 关键字 | 核心含义 | 使用场景 | 示例代码 |
|---|---|---|---|
include | 包含关联模型的所有字段(快速获取完整关联数据) | 需完整关联数据(如查用户+所有帖子) | // 查id=1的用户,同时包含其发布的所有帖子const user = await prisma.user.findUnique({ where: { id: 1 }, include: { posts: true } // posts是User模型中定义的关联字段}); |
select | 自定义返回字段(含关联模型的指定字段) | 按需获取字段(减少数据传输) | // 查用户仅返回name,关联帖子仅返回title和创建时间 const user = await prisma.user.findUnique({ where: { id: 1 }, select: { name: true, posts: { select: { title: true, createdAt: true } } }}); |
nestedWhere | 关联查询时筛选关联数据(仅获取满足条件的关联记录) | 精准筛选关联数据(如仅查用户的已发布帖子) | // 查用户,仅包含其已发布的帖子 const user = await prisma.user.findUnique({ where: { id: 1 }, include: { posts: { where: { isPublished: true } }}}); |
4.结果处理关键字(排序/分页/去重)
用于对查询结果进行排序、分页、去重等二次处理,是列表查询的常用辅助。
| 关键字 | 核心含义 | 使用场景 | 示例代码 |
|---|---|---|---|
orderBy | 对结果排序(asc升序/desc降序) | 按指定字段排序(如按创建时间降序),返回排序后的结果 | // 查所有帖子,按创建时间降序排列const posts = await prisma.post.findMany({ orderBy: { createdAt: "desc" }}); |
skip | 跳过指定数量的记录 | 分页查询(如第2页,每页10条) | // 分页:第2页,每页10条(跳过前10条,取10条)const posts = await prisma.post.findMany({ skip: 10, take: 10 }); |
take | 获取指定数量的记录(限制结果条数) | 分页/取前N条数据 | // 仅查前5条已发布的帖子const posts = await prisma.post.findMany({ where: { isPublished: true }, take: 5 }); |
distinct | 按指定字段去重返回 | 去重查询(如查所有不重复的角色) | // 查所有不重复的用户角色 const roles = await prisma.user.findMany({ distinct: ["role"], // 按role字段去重 select: { role: true }}); |
数据库表修改
- 关键字
upsert
upsert 是 update + insert 的组合词,中文可理解为「更新或插入」,核心逻辑是:
先根据指定条件查询数据库记录 → 若找到记录,则执行更新操作 → 若未找到记录,则执行插入操作。
它专门解决「有则改,无则加」的业务场景(比如用户签到积分更新、配置项同步、唯一数据兜底创建等),无需手动写 “先查、再判断、再更新 / 插入” 的冗余逻辑。
upsert 必须传入 3 个参数,缺一不可:
| 参数名 | 作用 注意事项 |
| —— | ————– | ——– |
| where | 查找记录的条件(决定 “是否存在”)| 必须基于主键或唯一索引字段(和findUnique要求一致) |
| update | 找到记录时,要更新的字段及值 | 支持 Prisma 的更新语法(如递增、赋值等) |
| create | 未找到记录时,要插入的完整记录(需包含模型的必填字段,如主键 / 非空字段)| 需满足模型的字段约束(如主键自增可省略) |
upsert 执行后会返回最终的记录(更新后的记录 或 新插入的记录),方便后续业务处理。
model UserRecord {
id Int @id @default(autoincrement())
userId Int @unique // 唯一索引(确保一个用户只有一条积分记录)
score Int @default(0) @map("points") // 映射到数据库字段 points(默认值0)
updatedAt DateTime @updatedAt
}查询插入示例
// 引入Prisma Client(假设已初始化)
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// 核心逻辑:更新/插入用户积分
async function updateUserScore(userId) {
const result = await prisma.userRecord.upsert({
// 1. 查找条件:基于唯一索引userId
where: { userId: userId },
// 2. 找到记录:积分+10
update: { score: { increment: 10 } }, // increment是Prisma的递增语法
// 3. 未找到记录:创建新记录(userId必填,score初始为10)
create: { userId: userId, score: 10 }
});
console.log('最终积分记录:', result);
return result;
}
// 调用示例
updateUserScore(123)
.catch(console.error)
.finally(async () => await prisma.$disconnect());若userId=123的记录已存在:积分会从原有值 + 10,返回更新后的记录;
若userId=123的记录不存在:插入一条userId=123、score=10的新记录,返回这条新记录。
- 关键字update,updateMany
关键字(方法) 核心含义 匹配记录数 where条件要求 返回值 典型使用场景 update 精准更新单条记录 必须匹配 1 条 基于主键 / 唯一索引字段(确保唯一匹配) 更新后的完整单条记录 按 ID 改用户信息、改单条订单状态 updateMany 批量更新多条记录 0/1 / 多条(无限制) 任意筛选条件(非唯一也可,如按状态 / 角色) { count: 受影响的记录条数 } 批量改帖子状态、批量调整用户积分
update:精准更新单条记录
核心规则
- where条件:必须基于主键或唯一索引字段(和findUnique要求一致),确保只匹配 1 条记录;若匹配 0 条 / 多条,直接抛错。
- data参数:指定要更新的字段及值,支持 Prisma 的高级更新语法(如increment递增、decrement递减等)。
- 返回值:更新后的完整记录(可通过select自定义返回字段)
model User {
id Int @id @default(autoincrement()) // 主键
name String // 用户名
email String @unique // 唯一索引
age Int? // 可选年龄
score Int @default(0) // 积分(默认0)
}
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// 按ID更新单条用户信息
async function updateSingleUser() {
try {
const updatedUser = await prisma.user.update({
// 核心:where基于主键(唯一匹配)
where: { id: 1 },
// 要更新的字段(支持递增/赋值)
data: {
name: "张三-更新后", // 直接赋值
age: 26, // 可选字段更新
score: { increment: 10 } // 积分+10(Prisma内置语法)
},
// 可选:自定义返回字段(只返回id、name、score)
select: {
id: true,
name: true,
score: true
}
});
console.log("更新后的用户:", updatedUser);
// 输出示例:{ id: 1, name: '张三-更新后', score: 10 }
return updatedUser;
} catch (error) {
// 常见错误:id=1不存在 → 抛"No record found for update"
// 若where匹配多条 → 抛"Too many records found for update"
console.error("更新失败:", error.message);
} finally {
await prisma.$disconnect();
}
}
updateSingleUser();
model User {
id Int @id @default(autoincrement()) // 主键
name String // 用户名
email String @unique // 唯一索引
age Int? // 可选年龄
score Int @default(0) // 积分(默认0)
}const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// 按ID更新单条用户信息
async function updateSingleUser() {
try {
const updatedUser = await prisma.user.update({
// 核心:where基于主键(唯一匹配)
where: { id: 1 },
// 要更新的字段(支持递增/赋值)
data: {
name: "张三-更新后", // 直接赋值
age: 26, // 可选字段更新
score: { increment: 10 } // 积分+10(Prisma内置语法)
},
// 可选:自定义返回字段(只返回id、name、score)
select: {
id: true,
name: true,
score: true
}
});
console.log("更新后的用户:", updatedUser);
// 输出示例:{ id: 1, name: '张三-更新后', score: 10 }
return updatedUser;
} catch (error) {
// 常见错误:id=1不存在 → 抛"No record found for update"
// 若where匹配多条 → 抛"Too many records found for update"
console.error("更新失败:", error.message);
} finally {
await prisma.$disconnect();
}
}
updateSingleUser();updateMany:批量更新多条记录
核心规则
- where条件:任意筛选条件(可非唯一,如按authorId、isPublished筛选),匹配多少条就更新多少条;匹配 0 条也不会抛错(返回count: 0)。
- data参数:指定要批量更新的字段及值,支持基础赋值、数值递增 / 递减等语法(不支持嵌套关联更新)。
- 返回值:固定返回{ count: 数字 },仅表示受影响的记录条数(无法返回更新后的具体记录)。
model Post {
id Int @id @default(autoincrement())
title String
isPublished Boolean @default(false) // 发布状态
authorId Int // 关联用户ID
}
// 批量更新authorId=1的所有帖子为“已发布”
async function updateManyPosts() {
const updateResult = await prisma.post.updateMany({
// where:任意筛选条件(匹配多条)
where: { authorId: 1 },
// 批量更新的字段
data: {
isPublished: true,
// 也支持数值递增(比如阅读量+1)
// viewCount: { increment: 1 }
}
});
console.log("批量更新结果:", updateResult);
// 输出示例:{ count: 3 }(表示更新了3条记录)
return updateResult;
}
updateManyPosts()
.catch(console.error)
.finally(() => prisma.$disconnect());
model Post {
id Int @id @default(autoincrement())
title String
isPublished Boolean @default(false) // 发布状态
authorId Int // 关联用户ID
}// 批量更新authorId=1的所有帖子为“已发布”
async function updateManyPosts() {
const updateResult = await prisma.post.updateMany({
// where:任意筛选条件(匹配多条)
where: { authorId: 1 },
// 批量更新的字段
data: {
isPublished: true,
// 也支持数值递增(比如阅读量+1)
// viewCount: { increment: 1 }
}
});
console.log("批量更新结果:", updateResult);
// 输出示例:{ count: 3 }(表示更新了3条记录)
return updateResult;
}
updateManyPosts()
.catch(console.error)
.finally(() => prisma.$disconnect());update vs upsert 核心区别
| 维度 | update(仅更新) | upsert(更新或插入) |
|---|---|---|
| 核心逻辑 | 仅更新已存在的记录(“只改不插”) | 先查记录 → 存在则更、不存在则插(“有则改,无则加”) |
| 无匹配记录时的行为 | 直接抛出错误(No record found for update) | 执行create参数中的插入逻辑,创建新记录 |
| where条件要求 | 必须基于主键 / 唯一索引(确保匹配 1 条) | 必须基于主键 / 唯一索引(和findUnique要求一致) |
| 必传参数 | where + data(更新条件 + 更新内容) | where + update + create(查条件 + 更内容 + 插内容) |
| 返回值 | 更新后的单条记录 | 更新后的记录 / 新插入的记录(二选一) |
| 适用场景 | 确定记录一定存在的更新场景 | 不确定记录是否存在,需要 “兜底创建” 的场景 |
数据库表增加
Prisma 中是插入单条新数据库记录的核心语法:
- 既可以作为独立查询方法(prisma.模型名.create()),直接创建单条记录;
- 也可以作为子参数(如upsert.create、关联创建时的create),在其他操作中嵌套创建关联记录。
它是新增数据的基础操作,替代原生 SQL 的INSERT语句,无需手动拼接 SQL,且会严格校验模型字段约束(如必填、类型、唯一等)
独立使用(最常用)—— 直接创建单条记录
这是create最基础的用法,通过prisma.模型名.create()直接插入新记录,核心参数是data(必传),用于指定要插入的字段和值。
| 参数名 | 作用 | 注意事项 |
|---|---|---|
| data | 要插入的字段及值 | 必须包含模型的必填字段(无默认值的字段),可选字段可省略(会用默认值或 NULL) |
| select/include(可选) | 自定义返回字段 | 不填则返回新记录的所有字段 |
model User {
id Int @id @default(autoincrement()) // 自增主键(可选,创建时可省略)
name String // 必填字段(无默认值,创建时必须传)
email String @unique // 必填+唯一约束
age Int? // 可选字段(可省略)
createdAt DateTime @default(now()) // 有默认值,创建时可省略
}const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// 核心:创建单条用户记录
async function createUser() {
// 执行create操作
const newUser = await prisma.user.create({
// 必传:data参数,指定要插入的字段
data: {
name: "张三", // 必填字段(无默认值)
email: "zhangsan@example.com", // 必填+唯一字段
age: 25 // 可选字段(可省略)
// createdAt无需传,会用默认值now()
},
// 可选:自定义返回字段(不填则返回所有字段)
select: {
id: true,
name: true,
email: true
}
});
console.log("新增的用户记录:", newUser);
return newUser;
}
// 调用执行
createUser()
.catch(console.error) // 捕获约束错误(如邮箱重复)
.finally(async () => await prisma.$disconnect());关键注意事项
- 必填字段校验:data中必须包含模型的所有无默认值的必填字段(如示例中的name/email),否则 Prisma 会直接抛出错误;
- 约束校验:若字段有@unique(唯一)、@relation(关联)等约束,create会自动校验(比如邮箱重复会报错),无需手动判断;
- 默认值生效:有@default的字段(如createdAt),创建时可省略,Prisma 会自动填充默认值;
- 返回值:create执行成功后,默认返回新记录的所有字段,也可通过select/include自定义返回内容;
- 批量创建:若要批量插入多条记录,需用createMany(而非create),create仅支持单条创建。










