积累:07-Prisma

60 阅读3分钟

初始化

  1. 新建项目文件夹
  2. npm初始化一个Typescript项目
npm init -y  
npm install typescript tsx @types/node --save-dev
  • 创建 package.json、
  • 初始化 TypeScript npx tsc --init
  • 在项目中将 Prisma CLI 安装为开发依赖 npm install prisma --save-dev
  • 使用 Prisma CLI 的 init 命令设置 Prisma ORM
npx prisma init --datasource-provider sqlite --output ../generated/prisma

## 在 Prisma 模式中建模数据

  • prisma/schema.prisma
  • 用途
    1. 它在 prisma/migrations 目录中为此次迁移创建了一个新的 SQL 迁移文件。
    2. 它针对数据库执行了 SQL 迁移文件。
    3. 它在后台运行了 prisma generate(这会安装 @prisma/client 包并根据您的模型生成定制的 Prisma Client API)。

## 运行迁移以使用 Prisma Migrate 创建数据库表

  • 加入有一个Prisma模式,但是还没有数据库,执行以下命令以创建表
  • npx prisma migrate dev --name init
  • 以上命令干了三件事
    • 它在prisma/migrations

##  探索如何使用 Prisma Client 向数据库发送查询

要开始使用 Prisma Client,您需要安装 @prisma/client 包

npm install @prisma/client

Prisma核心命令

安装
  • npm install prisma --save-dev
  • npm install @prisma/client
初始化
  • npx prisma init
生成 Client
  • npx prisma generate
数据库迁移
  • npx prisma migrate dev --name init
打开 Prisma Studio(可视化工具)
  • npx prisma studio

基本CRUD

  • 假设schema.prisma 里面有数据模型
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
}
创建(create)
  • 语法
prisma.model.create({
    data:{...}, 
    select?:{...},
    include?:{...}
})
**属性作用
data必填,指定要创建的字段和值,可以包含关联的 createconnectconnectOrCreate
select可选,指定返回哪些字段(true/false),不能和 include 同时使用
include可选,指定返回关联的模型数据
skipDuplicates只在 createMany 时有效,忽略重复主键/唯一值记录**
// 创建单条
await prisma.user.create({
    data:{
         name: 'Alice',
        email: 'alice@example.com',
    }
})

// 创建并关联(发文关联作者)
await prisma.post.create({
    data:{
        title: 'Hello World',
        content: 'This is my first post',
        author:{
            connect:{id:1} // 关联已有用户
        }
    }
})

// 批量创建
await prisma.user.createMany({
    data:[
       { name: 'Bob', email: 'bob@example.com' },
       { name: 'Charlie', email: 'charlie@example.com' },
    ]
})

查询(findUnique / findFirst / findMany)
  • 语法
prisma.model.findUnique({
    where:{},
    select?: { ... }, // 返回指定字段
    include?: { ... },// 返回关联数据
    rejectOnNotFound?: boolean,// 找不到记录就抛异常 
})
属性作用
where必填,指定唯一条件(主键或唯一字段)
select返回特定字段
include返回关联数据
rejectOnNotFound如果找不到记录就抛异常
orderBy排序,可单字段或多字段
skip跳过多少条(分页用)
take取多少条(分页用)
distinct去重,按指定字段
// 查询单条
await prisma.user.findUnique({
    where:{id:1}
})

// 匹配第一条
await prisma.user.findFirst({
    where:{name:{contains:'A'}}
})

// 查多条
await prisma.user.findMany({
     where: {
        name: { startsWith: 'A' },
        email: { endsWith: '@example.com' }
      },
      orderBy: { createdAt: 'desc' },
      take: 10,
      skip: 0,
})

// 关联查询
await prisma.user.findUnique({
    where:{
        id:1
    },
    include: {
        posts: true
    }
})

// 只查部分字段
await prisma.user.findMany({
    select:{id:true,name:true}
})
更新(update,updateMany)
// 更新单条
await prisma.user.update({
  where: { id: 1 },
  data: { name: 'Alice Updated' }
})

// 批量更新
await prisma.user.updateMany({
  where: { email: { endsWith: '@example.com' } },
  data: { name: 'Bulk Updated' }
})

删除(delete , deleteMany)
// 删除单条
await prisma.user.delete({
  where: { id: 1 }
})

// 批量删除
await prisma.user.deleteMany({
  where: { name: 'Bulk Updated' }
})

操作符用法示例
equals{ age: { equals: 18 } }
not{ age: { not: 18 } }
in{ id: { in: [1, 2, 3] } }
notIn{ id: { notIn: [1, 2] } }
lt / lte{ age: { lt: 30 } }
gt / gte{ age: { gte: 18 } }
contains{ name: { contains: 'A' } }
startsWith{ name: { startsWith: 'Al' } }
endsWith{ email: { endsWith: '.com' } }
OR{ OR: [{ name: 'Alice' }, { name: 'Bob' }] }
AND{ AND: [{ age: { gt: 18 } }, { age: { lt: 30 } }] }

关联操作

创建关联记录
// 创建用户得同时创建文章
await prisma.user.create({
    data:{
        name: 'David',
        email: 'david@example.com',
        posts:{
            create:{
                { title: 'Post 1' },
                { title: 'Post 2' }
            }
        }
    }
})
// 关联已有记录
await prisma.post.update({
    where:{id:2},
    data:{
      author: { connect: { id: 2 } }
    }
})

// 断开关联
await prisma.post.update({
  where: { id: 1 },
  data: { author: { disconnect: true } }
})

// 删除关联记录
await prisma.user.update({
  where: { id: 1 },
  data: { posts: { deleteMany: {} } }
})

批量&事务

// 批量操作
await prisma.user.createMany({
     data: new Array(100).fill(0).map((_, i) => ({
        name: `User${i}`,
        email: `user${i}@example.com`
      })) 
})

// 事务(要么批量成功,要么批量失败)
await prisma.$transation([
     prisma.user.create({ data: { name: 'TX1', email: 'tx1@example.com' } }),
     prisma.user.create({ data: { name: 'TX2', email: 'tx2@example.com' } })
])

分页

const page = 1
const pageSize = 10
await prisma.user.findMany({
    skip: (page-1) * pageSize,
    take: pageSize,
    orderBy: { id: 'asc' }
})

聚合 & 分组

  • 聚合函数: 用来对多行数据进行统计计算并返回单一值的函数,比如:
    • COUNT()
    • SUM()
    • AVG()
    • MIN()
    • MAX()
// 返回表中用户总数
SELECT COUNT(*) AS total_users
FROM users;
  • 分组:把数据按照某个字段分组,然后每组数据执行聚合计算
// 按部门统计员工数量
SELECT department, COUNT(*) AS employee_count
FROM employees
GROUP BY department;

  • 聚合 + 分组 的执行顺序
    • SQL执行时,GROUP BY 会先分组,再对每组应用聚合函数
    • FROM->WHERE->GROUP BY->HAVING->SELECT->ORDER BY
      • 注意:WHERE 过滤的是分组前的数据,
      • HAVING 过滤的是分组后的结果。

常用的 CRUD 扩展参数

  • where 查询条件运算符
where :{
    age:{gte:18}, // >=18
    name:{contains:'A',node:'insensitive'},// 模糊匹配不区分大小写
    OR: [
        { name: { startsWith: 'Al' } },
        { email: { endsWith: '.com' } }
      ]
}
运算符作用
equals等于
not不等于
in在数组中
notIn不在数组中
lt / lte小于 / 小于等于
gt / gte大于 / 大于等于
contains包含(模糊搜索)
startsWith以…开头
endsWith以…结尾
OR
AND
NOT
  • ORDER BY 排序
orderBy:[
    {createAt:'desc'},
    {name:'asc'}
]
  • select / include 区别
    • select:只返回指定字段
    • include:返回关联模型的内容
  • connect / disconnect / set
// 关联已有记录
author: { connect: { id: 1 } }

// 断开关联
author: { disconnect: true }

// 替换关联
posts: { set: [{ id: 1 }, { id: 2 }] }

为什么要设置关联

  • 保证数据的完整性
    • 关联会在数据库里生成外键(foreign key)约束,比如 Post 必须有一个 authorId,而且这个 authorId 必须在 User 表里存在。
    • 避免出现“文章作者不存在”这种脏数据。
model Post{
  id        Int   @id @default(autoincrement())
  title     String
  authorId  Int
  author User @relation(field : [authorId], reference:[id])
}

  • 这样数据库会帮你保证:
    • 新增POST(文章)的时候,如果authorId不存在->会报错
    • 删除User的时候,如果关联有Post->会限制/级联处理(取决于设置)。

简化代码操作

有了关联,你可以直接一条语句查询/创建/更新多个表的数据,而不是自己写多次 SQL。

例子:没有关联

// 要查文章 + 作者,需要手写两条 SQL 再拼结果
const post = await prisma.post.findUnique({ where: { id: 1 } })
const author = await prisma.user.findUnique({ where: { id: post.authorId } })

有了关联

// 一次性查询
await prisma.post.findUnique({
  where: { id: 1 },
  include: { author: true }
})

支持关联写入

有了关联,你可以在一次操作中创建/更新多个模型的数据。

// 创建文章的同时创建作者
await prisma.post.create({
  data: {
    title: 'Hello',
    author: {
      create: { name: 'Alice', email: 'alice@example.com' }
    }
  }
})

(4) 自动类型提示

Prisma 会根据 @relation 生成带有类型安全的 API,TS 会帮你校验字段、关联名、嵌套结构。


2. 关联是必须的吗?

不是必须的,要看业务场景:

  • 必须的情况

    • 两张表确实有业务上的关系,比如“文章属于某个用户”,“订单属于某个客户”。
    • 需要保证数据的一致性和完整性。
    • 想用 Prisma 提供的 includeconnectcreate 这种关联操作。
  • 不需要的情况

    • 表之间完全独立,没有业务关系。
    • 你不想数据库加外键(可能是为了性能,或者是微服务分库的设计)。
    • 你打算自己用字段去维护关系,不依赖数据库外键。

3. 如果不设置关联,会怎样?

  • Prisma 依然能创建表,只是不会生成外键约束,也不能用 includeconnect 等 API。
  • 查询关联数据需要自己多写几次查询,或用 $queryRaw 手动写 SQL。
  • 更容易出现数据不一致,比如 post.authorId 指向一个不存在的用户。

4. 总结

能用关联的地方尽量用,因为它:

  • 帮你在数据库层面保证数据正确性
  • 简化代码
  • 提供类型安全和自动补全

但如果是为了性能优化或特殊业务架构(如无外键设计、分布式数据库),就可以不设置。

``

``

``

``

``

``

``

``

``

``

``

``

``

``

``

``

``

``

``