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 }

总结

  1. 高频核心:@id(主键)、@default(默认值)、@unique(唯一约束)是单表定义的基础,几乎每个模型都会用到;
  2. 关联与便捷:@relation(表关联)、@updatedAt(自动更新时间)是业务开发中提升效率的关键语法糖;
  3. 适配类:@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();

使用方法: prisma.模型名.方法名

  • prisma.模型中的 “模型” 是 PrismaClient 根据schema.prisma的model自动生成的表操作入口属性;
  • 命名规则默认是 model 名称的小驼峰,与数据库实际表名(@map修改的)无关;
  • 这个 “模型属性” 包含对应表的所有 CRUD 方法,是操作单表的核心入口

  1. 常用查询用的关键字
    方法名功能描述示例说明代码示例
    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 }});
    findUniqueOrThrowfindUnique,但无匹配结果时抛出异常(避免空值)必须找到单条数据的场景(如根据ID查用户)查id=1的用户,不存在则抛异常const user = await prisma.user.findUniqueOrThrow({ where: { id: 1 }});
    findManyOrThrowfindMany,无匹配结果时抛出异常必须返回数据的列表查询// 查所有管理员用户,无结果则抛异常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();

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());

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仅支持单条创建。