Nest 使用 prisma
初始化
bash1pnpm install prisma -D
调用 prisma CLI
bash1 npx prisma
初始化 prisma
bash1npx prisma init
该命令会创建一个 prisma 文件夹并生成两个文件:
schema.prisma文件:指定数据库连接并包含数据库信息的 schema
prisma1generator client { 2 provider = "prisma-client-js" 3} 4 5datasource db { 6 provider = "postgresql" 7 url = env("DATABASE_URL") 8}
-
.env:包含数据库 credentials 等环境变量信息js1DATABASE_URL = 'postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public'
默认情况下 datasource provider 为 postgresql。如果是 mysql,则修改为 mysql。
然后是将账号密码以及本地数据库名填入 .env。

再就是定义 database 的 schema 了,这个 schema 是一种 Model,抽象描述表数据结构模型:
js1// schema.prisma 2model User { 3 id String @id @default(uuid()) 4 name String 5 age String? 6 phone_number String @unique 7 Post Post[] 8} 9 10model Post { 11 id String @id @default(uuid()) 12 title String 13 content String? 14 published Boolean @default(false) 15 author User @relation(fields: [authorId], references: [id],onDelete: Cascade,onUpdate: Cascade) 16 authorId String 17}
后面再解释 schema 里的含义。
最后执行 migrate 让 model 跟数据表同步:
bash1npx prisma migrate dev --name user_post
此时生成 migrations 文件夹,里面包含了建表的一些 sql 文件以及版本信息,还有 lock 信息。

prisma 帮助我们在数据库中已经创建好了两张表:

多出来的_prisma_migrations表是记录版本信息的,比如这次创建两张表是一个版本,下次创建新的表又是一个新的版本。(由 prisma 内部使用,不要随意删除)
注入 prisma
在 src 目录下创建 prisma service:
bash1nest generate service Prisma --no-spec
在 prisma.service.ts 文件中填入:
ts1import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common' 2import { PrismaClient } from '@prisma/client' 3 4@Injectable() 5export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { 6 constructor() { 7 // 开启日志模式,查看数据库操作日志 8 super({ 9 log: [ 10 { emit: 'stdout', level: 'query' }, 11 { emit: 'stdout', level: 'info' }, 12 { emit: 'stdout', level: 'warn' }, 13 { emit: 'stdout', level: 'error' }, 14 ], 15 }) 16 } 17 async onModuleInit() { 18 await this.$connect() 19 } 20 async onModuleDestroy() { 21 await this.$disconnect() 22 } 23}
注入 prisma.service 并且写一段插入数据和查询数据的代码:
typescript1import { Controller, Get } from '@nestjs/common' 2import { User } from '@prisma/client' 3import { PrismaService } from './prisma/prisma.service' 4@Controller() 5export class AppController { 6 constructor(private prisma: PrismaService) {} 7 8 @Get('/all') 9 async getUser(): Promise<User[]> { 10 return await this.prisma.user.findMany() 11 } 12 @Get('/create') 13 async createUser(): Promise<User> { 14 return await this.prisma.user.create({ 15 data: { name: 'Alice', email: 'aaabbb@gmail.com' }, 16 }) 17 } 18}
现在访问http://localhost:3000/create 会在 User 表中创建一条记录:

访问http://localhost:3000/all查询所有:

在创建 User 时,顺便加入几条 post 的代码:
ts1 async createUser() { 2 await this.prisma.user.create({ 3 data: { 4 name: 'test', 5 phone_number: '17342123321', 6 Post: { 7 create: [ 8 { title: 'test', content: 'test' }, 9 { title: 'test', content: 'test' }, 10 ], 11 }, 12 }, 13 }); 14 }
这种写法的前提是在 schema 结构中已经把 User 和 Post 做了关联:

调用createUser方法后,查看终端的 log 日志:

可以看到 prisma 内部做了以下事情:
- 开启了一个 transaction
- 插入了 User 和 Post 的三条记录
- 从 User 里查询记录(用于做返回值)
- transaction 提交
Prisma 全部命令
在上面的流程中,我们已经创建好了 User 和 Post 两张表,以及 prisma 相关的 migration、sql 代码等。
为了熟悉 Prisma,所以请将_prisma_migrations 表、跟 Prisma 相关的代码手动删除,下面的文档中,我们将重复这一过程。
prisma init
执行 npx prisma init会生成 schema 文件和 env 里的数据。

在执行 init 后会提示我们:
- 在.env 里修改 database 的 url
- 在 schema.prisma 中修改 provider 信息,指定用哪种数据库
- 执行
prisma db pull将数据库已有的结构同步到 Prisma schema 中 - 执行
prisma generate来生成 Prisma Client 代码
prisma db pull
执行npx prisma db pull试试:

prisma 已经将我数据库里的表转化成数据模型,并在 schema.prisma 中写好了。
由于我的数据库中依然存在 User 和 Post 两张表,这次 db pull 之后会同步出跟以前差不多的代码:
typescript1model Post { 2 id String @id 3 title String 4 content String? 5 published Boolean @default(false) 6 authorId String 7 User User @relation(fields: [authorId], references: [id], onDelete: Cascade) 8} 9 10model User { 11 id String @id 12 name String 13 age String? 14 phone_number String @unique 15 Post Post[] 16}
如果有差别,手动修改一下即可。
除了prisma db pull外,还有几个命令:

prisma db push
pull 是将数据库的表结构拉取到 schema.prisma 中。
那 push 就是将 schema.prisma 的结构同步到数据库中。
先删掉 User 和 Post 两张表试一试这个命令:
bash1npx prisma db push

提示 schema 已经同步到数据库中了,同时也生成了Prisma Client代码。
数据库中确实出现了这两张表。

prisma db seed
seed 命令是执行脚本插入初始数据到数据库。
我们有时候会需要在数据库中加入一些用于 mock 的初始数据,就需要用到 seed 命令。
首先在 prisma 目录下创建 seed.ts 文件并写入:
ts1import { PrismaClient } from '@prisma/client' 2 3const prisma = new PrismaClient({ 4 log: [ 5 { 6 emit: 'stdout', 7 level: 'query', 8 }, 9 ], 10}) 11 12async function main() { 13 const user = await prisma.user.create({ 14 data: { 15 name: '球球球球', 16 phone_number: '12345678901', 17 Post: { 18 create: [ 19 { 20 title: 'aaa', 21 content: 'aaaa', 22 }, 23 ], 24 }, 25 }, 26 }) 27 console.log(user) 28} 29 30main()
接着在 package.json 中写脚本命令执行 seed.ts 文件:
ts1"scripts": { ... }, 2"prisma":{ 3 "seed":"npx ts-node prisma/seed.ts" 4 },
接着执行npx prisma db seed

seed 数据已经插入进数据库了。
prisma db execute
npx prisma db execute 是用来执行 sql 的。
比如我写一个 prisma/test.sql 的文件:
sql1DELETE FROM "User" WHERE NAME = '球球球球';
再执行:
bash1npx prisma db execute --file prisma/test.sql --schema prisma/schema.prisma
其中 --file 是指定 sql 文件的,--schema 是指定 schema.prisma 文件用来读取数据库 URL、账号密码登信息的。

脚本执行成功。
prisma migrate dev
migrate 是迁移的意思,它能够记录并体现表的结构变化。一共有这么几个命令

开发中最常用的是 prisma migrate dev。
它会根据 schema 的变化生成 sql 文件,并执行这个 sql,还会生成 client 代码。
如果第一次使用该命令,会提示清空数据库里的数据:

选择 yes 后,看一下它会做哪些事情:

从上到下依次是:
- 应用此次 migration
- 生成 migration.sql
- 生成 Prisma Client 代码
- 执行 seed, 给数据库填入种子数据
生成的 migration.sql 代码记录着每一次变更 schema 并且执行npx prisma migrate dev后的行为变化对应的 sql 语句。
例如一开始我们写好了 User 和 Post 两个 model,并 migrate 了,它生成的 SQL 是关于创建这两张表的。

如果我们再修改一下 schema :
diff1model User { 2 id String @id @default(uuid()) 3 name String 4 age String? 5+ email String? @unique 6 phone_number String @unique 7 Post Post[] 8}
执行npx prisma migrate dev --name add_email后得到提示:
bash1The following migration(s) have been created and applied from new schema changes: 2 3migrations/ 4 └─ 20231022132056_add_email/ 5 └─ migration.sql 6 7Your database is now in sync with your schema.
此时生成了第二个 migration.sql 。里面的内容是关于在 User 表中添加 email 字段的。

在数据库中有个 _prisma_migrations 表,也记录着数据库 migration 的历史:

prisma migrate reset
如果想要 reset 数据,可以使用npx prisma migrate reset。
它会重置所有 database 的数据并重新执行所有 migration。

还会生成 client 代码,并且执行 prisma db seed 来初始化数据。
prisma generate
当使用 Prisma 定义了数据模型(通过 Prisma Schema 文件),prisma generate 命令会根据模型生成相应的 Prisma Client 代码。
这些代码包括了访问数据库所需的 TypeScript 类型、query、mutation 等方法。
prisma generate 命令起到了将你的数据模型转化为实际可用的数据库访问代码的作用。
generate 命令只能生成 client 代码,并不能同步数据库:

根据 schema 定义,在 node_modules/@prisma/client 下生成代码,用于 CRUD。
很多命令都附带这个功能,所以这个命令用得不是很多。
prisma studio
这个命令会生成一个 web 端的图形化界面,可以看到 model 和数据并且能做一些增删改查的可视化操作。


prisma validate
这个是用来检查 schema 文件是否有语法错误的。
如果 vscode 里有下载 Prisma 插件则可以忽略它。
prisma format
顾名思义,用来格式化代码的。
如果 vscode 里有下载 Prisma 插件则可以忽略它。

prisma version
这个就是展示一些版本信息的。

小结
通过npx prisma -h查看所有命令:

总结一下 prisma 的全部命令:
- init:创建 schema 文件
- generate: 根据 shcema 文件生成 client 代码
- db:同步数据库和 schema
- migrate:生成数据表结构更新的 sql 文件
- studio:用于 CRUD 的图形化界面
- validate:检查 schema 文件的语法错误
- format:格式化 schema 文件
- version:版本信息
其中,prisma init、prisma migrate dev 是最常用的。
prisma db pull、prisma db push 也可以方便的用来做 schema 和数据库的同步。
Prisma schema
Prisma schema 是最重要的 prisma 文件,它定义了 model、database 信息,client 信息等。
prisma client
默认情况下,generate 代码会生成在 node_modules/@prisma/client 下,通过在 schema 中指定 output 可以更改这一行为:
scheme1generator client { 2 provider = "prisma-client-js" 3 output = "../generated/client" 4}

prisma docs/json
prisma 还支持生成 schema 文件的 json 版和 doc 版。
安装两个包:
bash1pnpm install -D prisma-docs-generator prisma-json-schema-generator
在 schema 文件中定义 generator:
scheme1generator docs { 2 provider = "node node_modules/prisma-docs-generator" 3 output = "../generated/docs" 4} 5 6generator json { 7 provider = "prisma-json-schema-generator" 8 output = "../generated/json" 9}
generate 命令
bash1npx prisma generate
生成成功后会有日志以及对应的文件夹:

json-schema.json 文件就是将 schema 的内容输出成 json:

docs 则是一个网页,通过 http-server 查看一下内容:
bash1http-server ./generated/docs

里面的内容大概是:
- 有哪些 Model,
- Model 都有哪些字段
- orm 对 model 增删改查的代码
- 增删改查的代码都可以有哪些参数,以及参数的类型是什么
可以说 prisma 的文档对于开发者来说真是太友好了。
常用 Model 语法
一个囊括多数开发应用场景的 Model 示例:
scheme1model Post { 2 id String @id @default(uuid()) 3 title String @db.VarChar(60) 4 content String? @db.Text() 5 published Boolean @default(false) 6 userId Int 7 User User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) 8 9 createTime DateTime @default(now()) 10 updateTime DateTime @updatedAt 11} 12 13enum Role { 14 ADMIN 15 USER 16} 17model User { 18 id Int @unique @default(autoincrement()) 19 name String 20 age String? 21 email String? 22 phone_number String @unique @map("telephone_number") 23 Post Post[] 24 authority Role @default(USER) 25 createTime DateTime @default(now()) 26 updateTime DateTime @updatedAt 27 28 @@id([id,phone_number]) 29 @@map("rls_game_user") 30}
String 默认是 VARCHAR(191)
@id 会创建主键约束,
@default 是默认值,而 autoincrement 是自增。
@unique 会创建唯一约束。
@relation 是外键约束的意思,onDelete 和 onUpdate 分别指定删除或更新的策略。
@db.xxx 可以指定具体的 mysql 的数据类型,有括号的是可以填入参数的。比如 @db.VarChar(60) 是指定长度。
@@map 是给表起另一个名字
@map 是给字段起另一个名字
@@id 联合主键。
@updatedAt 是更新时自动更新时间
enum 定义枚举类型
Prisma API
常用单表 CRUD
在 本地 docs 或者 API 文档 中 prisma 列举了能涵盖绝大多数 CRUD 操作的 API:
- findUnique:用来查找唯一的记录的,可以根据主键或者唯一的列来查
- findUniqueOrThrow:如果没找到对应的记录会抛异常
- findFirst:返回全部记录的第一条
- findMany:返回全部记录
- create:创建记录
- delete:删除记录
- update:更新记录
- deleteMany:批量删除
- updateMany:批量更新
- createMany:批量增加
- upsert:update 和 insert 的结合,有记录则更新,没记录则创建
- count:和 findMany 参数一样,只不过这里不返回具体记录,而是返回记录的条数。
- aggregate:统计相关,统计最大值、最小值、计数、平均值等
- groupBy:分组函数
多表插入
typescript1await this.prisma.department.create({ 2 data: { 3 name: '技术部', 4 employees: { 5 create: [ 6 { 7 name: '小张', 8 phone: '13333333333', 9 }, 10 { 11 name: '小李', 12 phone: '13222222222', 13 }, 14 ], 15 }, 16 }, 17})
插入多条关联数据
typescript1await this.prisma.department.create({ 2 data: { 3 name: '技术部', 4 employees: { 5 createMany: { 6 data: [ 7 { 8 name: '小张', 9 phone: '13333333333', 10 }, 11 { 12 name: '小李', 13 phone: '13222222222', 14 }, 15 ], 16 }, 17 }, 18 }, 19})
查询主表与关联表
typescript1const a = await this.prisma.department.findUnique({ 2 where: { 3 id: 3, 4 }, 5 include: { 6 employees: true, 7 }, 8}) 9 10const b = await this.prisma.department.findUnique({ 11 where: { 12 id: 3, 13 }, 14 include: { 15 employees: { 16 where: { 17 name: '小张', 18 }, 19 }, 20 }, 21})
查询关联表
typescript1const c = await this.prisma.department 2 .findUnique({ 3 where: { 4 id: 3, 5 }, 6 }) 7 .employees() 8const d = await this.prisma.department 9 .findUnique({ 10 where: { 11 id: 3, 12 }, 13 }) 14 .employees({ where: { name: '小张' } })
更新时插入关联数据
typescript1await this.prisma.department.update({ 2 where: { 3 id: 3, 4 }, 5 data: { 6 name: '销售部', 7 employees: { 8 create: [ 9 { 10 name: '小刘', 11 phone: '13266666666', 12 }, 13 ], 14 }, 15 }, 16})
connect 关联
connect 可用于多表之间建立关联关系。
更新某条数据时与关联表的非关联数据建立关联关系的代码:
typescript1await this.prisma.department.update({ 2 where: { 3 id: 3, 4 }, 5 data: { 6 name: '销售部', 7 employees: { 8 connect: { 9 id: 7, 10 }, 11 }, 12 }, 13})
这个操作会在数据库中将 id 为 3 的部门修改为“销售部”并且将 id 为 7 的员工的 deaprtmentId 修改为 3,这样两条数据就关联起来了。
此外还有 connectOrCreate 意思是没有 id 为 7 这条数据就 create 一个。
typescript1await this.prisma.department.update({ 2 where: { 3 id: 3, 4 }, 5 data: { 6 name: '销售部', 7 employees: { 8 connectOrCreate: { 9 where: { id: 7 }, 10 create: { name: '张三', phone: '17342123321' }, 11 }, 12 }, 13 }, 14})
删除关联子表的数据
typescript1await this.prisma.employee.deleteMany({ 2 where: { 3 department: { 4 id: 3, 5 }, 6 }, 7})
执行原始 SQL
查询(返回查询记录)
typescript1const a = await this.prisma.$queryRaw`SELECT * FROM "Department"`
更新或插入(返回影响数量)
typescript1const a: number = await this.prisma.$executeRaw`UPDATE "Department" SET "name"='测试部' WHERE id = 5;`
小结
本节主要描述单表的 CRUD 操作以及多表插入的部分语法。
单表主要为 findXxx、updateXxx、deleteXxx、createXxx 那些方法,多表查询的时候可以通过 include 包含关联记录,新增修改的时候可以通过 create、connect、connectOrCreate 来关联或者插入记录。
此外,还可以直接执行 sql。