prisma

1,554 阅读27分钟

prisma

Prisma 是一个现代化的数据库工具套件,用于简化和改进应用程序与数据库之间的交互。它提供了一个类型安全的查询构建器和一个强大的 ORM(对象关系映射)层,使开发人员能够以声明性的方式操作数据库。

Prisma 支持多种主流数据库,包括 PostgreSQL、MySQL 和 SQLite,它通过生成标准的数据库模型来与这些数据库进行交互。使用 Prisma,开发人员可以定义数据库模型并生成类型安全的查询构建器,这些构建器提供了一套直观的方法来创建、更新、删除和查询数据库中的数据。

Prisma 的主要特点包括:

  • 类型安全的查询构建器:Prisma 使用强类型语言(如 TypeScript)生成查询构建器,从而提供了在编译时捕获错误和类型检查的能力。这有助于减少错误,并提供更好的开发人员体验。

  • 强大的 ORM 层:Prisma 提供了一个功能强大的 ORM 层,使开发人员能够以面向对象的方式操作数据库。它自动生成了数据库模型的 CRUD(创建、读取、更新、删除)方法,简化了与数据库的交互。

  • 数据库迁移:Prisma 提供了数据库迁移工具,可帮助开发人员管理数据库模式的变更。它可以自动创建和应用迁移脚本,使数据库的演进过程更加简单和可控。

  • 性能优化:Prisma 使用先进的查询引擎和数据加载技术,以提高数据库访问的性能。它支持高级查询功能,如关联查询和聚合查询,并自动优化查询以提供最佳的性能

安装

  1. 安装 Prisma CLI
  2. 初始化项目
  3. 连接mysql
// 使用 npm 安装
npm install prisma
// 使用 yarn 安装
yarn add prisma
// 初始化
npx prisma init 
yarn prisma init 
// 例如连接mysql:修改.env文件 [DATABASE_URL="mysql://账号:密码@主机:端口/库名"]
DATABASE_URL="mysql://root:123456@localhost:3306/test
// 修改prisma/schema.prisma进行数据库连接
datasource db {
    provider = "mysql"
    url = env("DATABASE_URL")
}

初始化执行结果如下:

image.png

初始化之后会生成4个文件:

image.png

prisma/schema.prisma文件的样子如下:

image.png

.env文件的样子如下:

image.png

.gitignore文件的样子如下:

image.png

配置.env文件后进行数据库链接:

// prisma/schema.prisma
datasource db {
    provider = "mysql"
    url = env("DATABASE_URL")
}

如果有多个项目使用了 prisma ,全局安装可能会导致这些项目之间的版本冲突,不推荐全局安装。

prisma 提供了 7 个命令:

命令说明
init在应用中初始化 Prisma
generate主要用来生成 Prisma Client
db管理数据库的模式和生命周期
migrate迁移数据库
studio启动一个Web 端的工作台来管理数据
validate检查 Prisma 的模式文件的语法是否正确
format格式化Prisma的模式文件,默认就是 prisma/schema.prisma

schema.prisma文件详情

schema.prisma 是使用 primsa 的主要配置文件代码如下,它包含三个基本结构:

  • 数据源
  • 生成器
  • 数据模型定义
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

// 生成器
generator client {
  provider = "prisma-client-js"
}

// 数据源
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

// 定义模型
model Post {
  id       Int     @id @default(autoincrement()) //表示id字段是整数且自增,主键
  title    String  //表示title字段是字符串类型
  publish  Boolean @default(false) //表示发布字段是布尔值默认false
  author   User   @relation(fields: [authorId], references: [id]) //表示author字段关联User用户表,关联关系:authorId 关联User表的id,外键
  authorId Int //表示authorId字段是关联id,和User表的id进行关联,定义User表和Post表是一对多的关系
}

model User {
  id    Int    @id @default(autoincrement()) //主键
  name  String
  email String @unique // 表示email字段是字符串且是唯一的
  posts Post[] //表示User表和Post表是一对多的关系
}
  1. 使用 generate定义生成器,通过 provider 属性声明为 prisma-client-js
  2. 使用 datasource 是定义数据源,用来设置 prisma 连接的数据库所需要的一些信息。provider 是连接到的数据库的类型,默认是 postgresql,可以根据所需改为mysqlurl 是数据库URL,通常为了保持配置分离,会将其单独定义到一个环境变量文件中,也就是 prisma自动生成的 .env 文件,通过 env() 函数会去读取此文件中的变量。

安装 VSC 插件

在编辑模式文件前,在 VS Code 中安装 Prisma 插件,它针对 .prisma 文件提供了代码高亮、格式化、自动补全、跳转定义和检查的功能。没有这个插件的加持,模式文件就是一个纯文本。

image.png

image.png

安装@prisma/client包( prisma 客户端)

保持prisma 和 @prisma/client的包的版本一致以避免任何意外错误或行为的发生

npm install @prisma/client
yarn add @prisma/client

@prisma/client安装完成后,使用以下命令生成 Prisma 客户端:

npx prisma generate 或者 yarn prisma generate

// 官网说直接用 prisma generate 就可以,但是使用后提示,这东西不是个命令

运行后提示如下信息:

image.png

大概意思是在项目的schema.prisma文件中,没有发现任何的models,所以不会产生任何东西。在项目的schema.prisma文件需要定义一个模型。(模型就是关系型数据库中的表名,非关系型数据库中集合)

创建了模型后,正确生成 Prisma 客户端提示信息如下:

image.png

实例化客户端

使用Prisma客户端之前,需要实例化客户端。从Prisma Client 生成的node_modules/.prisma/client中导出的@prisma/client,可以将其导入并在代码中实例化客户端,如下所示:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()
//在你的应用程序中使用' prisma '来读写你的数据库中的数据

const { PrismaClient } = require('@prisma/client')

const prisma = new PrismaClient()
//在你的应用程序中使用' prisma '来读写你的数据库中的数据

prisma 客户端位置

如果没有在 prisma/schema.prisma文件内自定义output,则默认generator将 Prisma 客户端(Prisma Client) 生成到文件夹/node_modules/.prisma/client中。

自定义output路径:可以在schema.prisma文件中配置output,意思是指定自定义路径generatorschema.prisma文件位于默认prisma文件夹下。

generator client {
  provider = "prisma-client-js"
  output   = "../src/generated/client"
}

自定义output路径后运行prisma generate命令,Prisma 客户端包将位于:src/generated/client

实例化客户端时,要从自定义位置导入PrismaClient

// src/index.ts
import { PrismaClient } from './generated/client' 
const prisma = new PrismaClient()

创建表(定义模型)

作为 ORM 工具,肯定少不了模型了。Prisma 的模型主要有以下作用:

  • 构成了应用领域的 实体
  • 映射到数据库的 (关系型数据库,例如 MySQL)或 集合 (MongoDB)
  • 构成 Prisma Client API中 查询 的基础
  • 在使用 TypeScript 时,Prisma Client 为模型及其变体提供 类型定义,保证数据库访问的类型安全

定义模型时用到的形如 @id()@default() 这些是Prisma 内置的工具函数。比如 @id() 用来声明主键,@default() 用来设置默认值,命名都非常语义化,基本就是 SQL 中的一些关键字。

字段

字段由下面四种描述组成:

  • 字段名
  • 字段类型:字段类型可以是定义model名称,比如关联类型字段场景;字段类型还可以是底层数据类型,通过 @db. 描述;对于 Prisma 不支持的类型,还可以使用 Unsupported,但这种类型的字段无法通过 ORM API 查询,但可以通过 queryRaw 方式查询,queryRaw 是一种 ORM 对原始 SQL 模式的支持。
  • 可选的类型修饰:类型修饰有 ?[] 两种语法,分别表示可选与数组
  • 可选的属性描述:属性描述有如下几种语法
  1. @id 对应数据库的 PRIMARY KEY(主键)。
  2. @default 设置字段默认值,可以联合函数使用,比如 @default(autoincrement()),可用函数包括 autoincrement()dbgenerated()cuid()uuid()now(),还可以通过dbgenerated直接调用数据库底层的函数,比如 dbgenerated("gen_random_uuid()")
  3. @unique 设置字段值唯一,每次创建时会自动判断是否已经存在
  4. @relation 设置关联。
  5. @map 设置映射。
  6. @updatedAt 修饰字段用来存储上次更新时间,一般是数据库自带的能力。
  7. @ignore 是一个自定义的属性,通常用于排除模型中的特定字段或关系,使其不会被生成到数据库表中
  8. 所有属性描述都可以组合使用,并且还存在需对 model 级别的描述,一般用两个 @ 描述,包括 @@id@@unique@@index@@map@@ignore
// @@index([title]) 表示创建一个数据库索引,该索引加速了 title 字段的搜索操作。
model Post {
  id  Int  @id @default(autoincrement())
  title    String
  content  String
  createdAt DateTime

  @@index([title]) // 创建一个索引,加速 title 字段的搜索
}

// @@ignore用于排除整个模型(表)及其相关字段不会在数据库中生成
model User {
  id       Int       @id @default(autoincrement())
  username String
  email    String    @unique
  // 其他字段

  @@ignore
}

model Tag {
  name String? @id
}

在上述代码中,包含字段名 name、字段类型 String、类型修饰 ?表示可选、属性描述 @id

model User {
  id          Int      @id @default(autoincrement())
  name        String   
  email       String   @unique
  password    String
  createdTime DateTime @default(now()) @map("created_time")
  updatedTime DateTime @updatedAt @map("updated_time")
  
  @@map("user")
}

上面代码的解析如下:

  1. 模型的名字默认就是创建的数据表的名字,上面是大写的 User,那么数据表名也就是大写的 User,可以使用 @@map() 来设置映射的表名,改为小写的 user。
  2. 每个模型的记录都是唯一可识别的,也就是要有主键,可以使用 @id 去声明。
  3. 字段的类型,比如 IntString,会经过Prisma 转为数据库对应的类型,也就是 intvarchar
  4. @unique 是唯一值约束,所以生成的 user 表的 email 字段的值不能重复。
  5. 像是创建时间和更新时间,为了符合 JS、TS 中的命名规范,字段名使用了小驼峰命名,为了符合数据库命名规范,在后面使用 @map() 重新映射为了下划线命名。

函数

  • autoincrement():在底层数据库中创建一个整数序列,并根据序列将递增的值分配给创建的记录的 ID 值
  • uuid():生成通用唯一标识符
  • cuid():针对水平缩放和二分搜索查找性能进行了优化的防冲突 ID
  • now():创建记录时的时间戳
  • dbgenerated():表示无法在 Prisma 模式中表达的默认值

同步数据库

在创建了模型之后,执行命令: yarn prisma db push 或者 npx prisma db push

命令执行后就是将prisma/schema.prisma文件中的模型 User (model User) 也就是关系型数据库中的表名User推送到数据库,这样一来就完成了通过prisma客户端向mysql数据库添加表的功能了。简言之就是在数据库中建立了一个User表(关系型数据库)或者User集合(非关系型数据库)。

如果是一个已经有数据的项目,就不能使用这个命令了,转而使用 yarn prisma migrate 或者npx prisma migrate迁移

数据库迁移

yarn prisma migrate 或者npx prisma migrate进行数据库迁移,它能够生成本地的一个.sql数据库结构文件,并一起通过git进行版本管理。

更新数据库

当变更模型时,执行yarn prisma migrate dev或者npx prisma migrate dev。该命令有些参数如下:

  • --name optionName:命名此次迁移,名称可任意,比如npx prisma migrate dev --name updatae_post
  • --create-only:只生成迁移文件,不自动执行,可人为进行修改,后续需要再次执行dev命令使其生效

命令yarn prisma migrate dev --name updatae_post执行后会进行以下操作:

  • 根据schema.prisma生成修改数据表的SQL语句
  • 创建一个新迁移文件,命名为"updatae_post.sql"
  • 在开发环境数据库执行SQL,完成修改数据表的操作
  • Prisma Client也会根据新的schema重新生成

更新生产环境数据库

当需要修改生产环境的数据库,可以执行命令:yarn prisma migrate deploy或者npx prisma migrate deploy

完整更新生产环境数据库的步骤是:

  1. 在本地开发环境,更新 Prisma schema,例如添加新字段
  2. 在本地运行:npx prisma migrate dev --name add_new_field 来生成新的迁移文件
  3. 将生成的迁移文件更新到生产环境
  4. 在生产环境运行:npx prisma migrate deploy
  5. 这会找到尚未执行的迁移文件,并运行里面的SQL语句来更新数据库 schema
  6. 如果一切顺利,迁移完成后可以重新启动相关服务
  7. 下次部署时,npx prisma migrate deploy 会自动忽略已迁移文件

重置数据库

当在开发过程中,通过多次npx prisma migrate devnpx prisma migrate deploy执行了一系列的迁移操作后,数据库的schema可能已经和最初的状态不一样了。如果这个时候想直接重置数据库schema到一个空的初始状态,可以使用这个命令:yarn prisma migrate reset或者npx prisma migrate reset

重置迁移会:

  1. 删除数据库中所有由Prisma Migrate创建的表
  2. 删除_Migration表,这是Prisma Migrate内部用来记录迁移历史的表
  3. 重新创建并初始化_Migration

这样就会让数据库回到一个没有任何Prisma模型的干净状态。

之后如果再次运行npx prisma migrate dev,就会重新生成初始的迁移文件,可以重新执行数据库schema的变更。

所以在开发过程中,如果发现数据库schema已经混乱难以维护时,可以用npx prisma migrate reset来清空状态,然后重新开始迁移。

在生产环境中要非常小心使用该命令,它会导致现有数据被清空。

批准迁移

使用npx prisma migrate dev生成了许多迁移文件之后,这些迁移都只是未批准的状态。如果想让这些迁移生效,我们通常需要运行npx prisma migrate deploy

但是如果想批量批准所有的迁移文件,可以使用npx prisma migrate resolve

这会直接在数据库中应用所有这些未批准的迁移,等效于依次执行了每一个npx prisma migrate deploy

这样可以避免需要一个一个地部署迁移文件,特别是在开发环境需要重建数据库时,会非常方便。在生产环境中要慎用这个命令,因为它会直接变更数据库,建议还是每次只部署一个迁移文件。

primsa 中数据库表之间关系

数据库中的关系:一对一(也称为 1-1 关系)、一对多(也称为 1-n 关系)、多对多(也称为 mn 关系)。

在一对一、一对多关系中,通常认为只存在一方拥有者;而在多对多关系中,通常认为互为拥有者。在关系的拥有者中,只需要定义字段以及字段代表的实体,而在关系的另一方中,我们需要使用prisma的@relation语法来标注这一字段表征的关系,是表中的外键字段。

一对一关系

用户和用户详情是一个常见的一对一关系,以下创建两个表:User用户表和Profile用户详情表。

model User {
  id      Int      @id @default(autoincrement())
  profile Profile // User表是关系拥有者,profile字段代表Profile模型
}

model Profile {
  id     Int  @id @default(autoincrement())
  gender String?
  birthday String?
  realeName String?
  user   User @relation(fields: [userId], references: [id]) // 关联字段,用于关联用户表,关联关系是userId关联User表的id
  userId Int  @unique // 外键
}

User用户表与 Profile 用户详情表构成一对一的关联关系,关系拥有者User表的主键id和Profile表的外键userId进行关联。且关系拥有者定义的字段需要与关系另一方的外键必须保持一致的可选或者必选。即profile Profileuser UseruserId Int要么同为可选,要么同为必选。

  • fields属性位于当前的表中,userIduser必须保持一致的可选或者必选,即要么同为可选,要么同为必选。
  • references属性位于关系的另一方中,表示关系另一方中与外键userId对应的字段。
  • 在一对一、一对多关系中,@relation是必须被使用的。

一对多关系

用户和用户发表的文章是一个常见的一对多,关系在prisma中定义模型的一对多关系如下:

model Post {
  id       Int     @id @default(autoincrement()) //表示id字段是整数且自增,主键
  title    String  //表示title字段是字符串类型
  publish  Boolean @default(false) //表示publish字段是布尔值默认false
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  author   User   @relation(fields: [authorId], references: [id]) //关联字段,用于关联用户表,关联关系是authorId关联User表的id
  authorId Int //外键
}

model User {
  id    Int    @id @default(autoincrement()) //主键
  name  String
  email String @unique // 表示email字段是字符串且是唯一的
  posts Post[] //关系拥有者,表示User表和Post表是一对多的关系,[]表示列表
}

多对多关系

文章表和文章分类表属于多对多关系,定义模型如下:

model Category {
  id  Int  @id @default(autoincrement())
  name String
  posts Post[] // 关系拥有者
}

model Post {
  id  Int @id @default(autoincrement())
  title String
  content String?
  publish Boolean @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  author   User @relation(fields: [authorId], references: [id])
  authorId Int
  categories Category[] // 关系拥有者
}

在多对多关系中,如Post与Category,可以不使用@relation来声明级联关系,这样将会自动使用双方表中的主键@id来建立级联关系。如果觉得这种隐式指定可能会带来歧义或者需要额外定制,也可以使用额外的一张数据表,使用@relation分别与Post、Category建立一对多关系。

通过 CategoriesOnPosts 中间模型进行Post文章表与Category文章分类表关联:

model Post {
  id Int  @id @default(autoincrement()) // 主键
  title String
  categories CategoriesOnPosts[] // 关系拥有者,和CategoriesOnPosts模型关联,一对多关系
}

model Category {
  id  Int @id @default(autoincrement()) // 主键
  name  String
  posts CategoriesOnPosts[] // 关系拥有者,和CategoriesOnPosts模型关联,一对多关系
}

model CategoriesOnPosts {
  post   Post @relation(fields: [postId], references: [id]) // 和Post模型关联,关联关系:postId与Post模型的id关联
  postId  Int // 外键
  category Category @relation(fields: [categoryId], references: [id]) // 和Category模型关联,关联关系:categoryId与Category模型的id关联
  categoryId Int // 外键
  assignedAt DateTime @default(now())
  assignedBy String

  @@id([postId, categoryId])
}

@relation 关系函数

@relation 类似一个函数可以传递的参数:

  • name:标记名称
  • fields:当前模型的字段列表(一般使用本表中的外键)
  • references:关联模型的字段列表(一般使用关联表中的主键)
  • map:自定义名字
  • onUpdate:定义更新关联 action
  • onDelete:定义删除 action
model User {
  id           Int     @id @default(autoincrement())
  writtenPosts Post[]  @relation("WrittenPosts")
  pinnedPost   Post?   @relation("PinnedPost")
}

model Post {
  id         Int     @id @default(autoincrement())
  author     User    @relation("WrittenPosts", fields: [authorId], references: [id])
  authorId   Int
  pinnedBy   User?   @relation("PinnedPost", fields: [pinnedById], references: [id])
  pinnedById Int?    @unique
}

如果不使用 name 当关系比较复杂时候,很难一眼看出关联关系。在 @relation 中使用 name作为第一个参数,可以消除歧义。

prisma client 来对数据库进行增删改查操作

prisma client结合express框架对数据库进行增删改查操作。

prisma.env配置文件:

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="mysql://root:123456@localhost:3306/my_test"

将有User(用户表)、Profile(用户简介表)、Post(用户发布的文章表)和Category(文章的种类表)模型,各个模型之间的关系如下:

  • User -> Profile 1-1(一对一关系)
  • User -> Post 1-m (一对多关系)
  • Post -> Category m-n (多对多关系)

创建模型如下:

// prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model Post {
  id         Int       @id @default(autoincrement()) // 表示id字段是整数且自增,主键
  title      String // 表示title字段是字符串类型
  publish    Boolean   @default(false) // 表示发布字段是布尔值默认false
  createdAt  DateTime  @default(now())
  updatedAt  DateTime  @updatedAt
  author     User      @relation(fields: [authorId], references: [id]) // 关联User用户表,关联关系:authorId 关联User表的id,必选
  authorId   Int // 外键,必选
  categories Category[] //  关系拥有者,和Category模型关联,且与Category模型互为关系拥有者,多对多关系,必选
}

model User {
  id    Int    @id @default(autoincrement()) // 主键
  name  String
  email String @unique // 表示email字段是字符串且是唯一的
  posts Post[] // 关系拥有者,和Post模型关联,一对多关系,必选
  profile Profile? // 关系拥有者,和Post模型关联,一对多关系,可选
}

model Profile {
  id        Int     @id @default(autoincrement()) // 主键
  gender    String?
  birthday  String?
  realeName String?
  user User? @relation(fields: [userId],references: [id]) // 关联User用户表,关联关系:userId 关联User表的id,可选
  userId Int? @unique // 外键,可选
}

model Category {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[] // 关系拥有者,和Post模型关联,且与Post模型互为关系拥有者,多对多关系,必选
}

创建模型后,执行yarn prisma db push,便可以通过实例化的客户端对数据库中的表进行增删改查等操作。在执行yarn prisma db push时会自动执行 prisma generate,不需要再次执行。但是一旦prisma.schema 文件发生了变动,比如修改了模型,就需要重新执行yarn prisma generate这个命令,重新生成 Prisma Client。

image.png

增(向数据库中添加数据)

接口代码:index.js

// index.js
import express from 'express';
import {PrismaClient} from '@prisma/client';
// 实例化prisma client客户端
const prisma = new PrismaClient({
    // 显示日志信息的简写形式,不直观,LogDefinition其中的值emit始终为stdout
    // log: ['query', 'info', 'warn', 'error'] 
    // 推荐使用这种,LogDefinition其中的值emit设置为event就是显示在控制台的
    log: [
        { level: 'query', emit: 'event' }, // 在控制台显示
        { level: 'info', emit: 'stdout' }, // 不在控制台显示
        // { level: 'info', emit: 'event' }, // 在控制台显示
        { level: 'warn', emit: 'event' }, // 在控制台显示
        { level: 'error', emit: 'event' },// 在控制台显示
    ],
});
// 将4种日志信息显示在控制台
prisma.$on('query', e => {
    console.log(e)
})
// prisma.$on('info', e => {
//     console.log(e)
// })
prisma.$on('warn', e => {
    console.log(e)
})
prisma.$on('error', e => {
    console.log(e)
});
const sever = express();
sever.use(express.json());
// 新增接口,向user表中添加数据
sever.post('/create_user',async (req,res) => {
    const {name,email} = req.body;
    // user是表名,create是创建数据,配置项中data表示需要添加的数据
    // 一次性添加一条数据
    const data = await prisma.user.create({
        data:{
            name,
            email,
            // user表和post表是一对多的关系,在添加user表数据时还可以给post表添加数据,一次性可以给post表添加一条或者多条数据
            posts:{
                // 给post表添加数据,添加多条数据使用数组
                create:[{
                    title:'Vue简介'
                },{
                    title:'React Hooks',
                    publish:true
                }]
            }
        }
    });
    res.send(data);
});
sever.listen(8089, () => {
    console.log("8089端口已启动");
});

测试接口代码:request.http

POST http://localhost:8089/create_user HTTP/1.1
Content-Type: application/json

{
    "name":"G.E.M.",
    "email":"info@gnation.hk"
}

执行结果如下:

image.png

user表成功添加一条数据:

image.png

post表成功添加两条数据:

image.png

数据表中成功添加数据且数据之间的一对多的关系也自动关联。

一次添加一条数据

一次添加一条数据时,采用对象

import express from 'express';
import {PrismaClient} from '@prisma/client';
// 实例化prisma client客户端
const prisma = new PrismaClient({
    // 显示日志信息的简写形式,不直观,LogDefinition其中的值emit始终为stdout
    // log: ['query', 'info', 'warn', 'error'] 
    // 推荐使用这种,LogDefinition其中的值emit设置为event就是显示在控制台的
    log: [
        { level: 'query', emit: 'event' }, // 在控制台显示
        { level: 'info', emit: 'stdout' }, // 不在控制台显示
        // { level: 'info', emit: 'event' }, // 在控制台显示
        { level: 'warn', emit: 'event' }, // 在控制台显示
        { level: 'error', emit: 'event' },// 在控制台显示
    ],
});
// 将4种日志信息显示在控制台
prisma.$on('query', e => {
    console.log(e)
})
// prisma.$on('info', e => {
//     console.log(e)
// })
prisma.$on('warn', e => {
    console.log(e)
})
prisma.$on('error', e => {
    console.log(e)
});
const sever = express();
sever.use(express.json());
// 新增接口,向user表中添加数据
sever.post('/create_user',async (req,res) => {
    const {name,email} = req.body;
    // user是表名,create是创建数据,配置项中data表示需要添加的数据
    // 一次性添加一条数据
    const data = await prisma.user.create({
        data:{
            name,
            email,
            // user表和post表是一对多的关系,在添加user表数据时还可以给post表添加数据,一次性可以给post表添加一条或者多条数据
            posts:{
                // 给post表添加数据,添加一条数据使用对象
                create:{
                    title:'node.js简介',
                    publish:true
                }
            }
        }
    });
    res.send(data);
});
sever.listen(8089, () => {
    console.log("8089端口已启动");
});

一次添加多条数据

一次添加多条数据时,采用数组

import express from 'express';
import {PrismaClient} from '@prisma/client';
// 实例化prisma client客户端
const prisma = new PrismaClient({
    // 显示日志信息的简写形式,不直观,LogDefinition其中的值emit始终为stdout
    // log: ['query', 'info', 'warn', 'error'] 
    // 推荐使用这种,LogDefinition其中的值emit设置为event就是显示在控制台的
    log: [
        { level: 'query', emit: 'event' }, // 在控制台显示
        { level: 'info', emit: 'stdout' }, // 不在控制台显示
        // { level: 'info', emit: 'event' }, // 在控制台显示
        { level: 'warn', emit: 'event' }, // 在控制台显示
        { level: 'error', emit: 'event' },// 在控制台显示
    ],
});
// 将4种日志信息显示在控制台
prisma.$on('query', e => {
    console.log(e)
})
// prisma.$on('info', e => {
//     console.log(e)
// })
prisma.$on('warn', e => {
    console.log(e)
})
prisma.$on('error', e => {
    console.log(e)
});
const sever = express();
sever.use(express.json());
// 新增接口,向user表中添加数据
sever.post('/create_user',async (req,res) => {
    // user是表名,create是创建数据,配置项中data表示需要添加的数据
    // 一次性添加多条数据
    const data = await prisma.user.create({
         data:[
             {name:'gem',email:'gloria@816.com'},
             {name:'hyomin',email:'hyomin@530.com'},
            // 邮箱要唯一,这里就重复了,只会添加一条
             {name:'hyomin',email:'hyomin@530.com'},
             {name:'jessica',email:'jessica@418.com'}
        ],
         // 不能插入具有唯一字段或 ID 字段已存在的记录,简言之不能插入重复数据
         skipDuplicates: true,
     })
     res.send(data);
});
sever.listen(8089, () => {
    console.log("8089端口已启动");
});

查询数据库中的数据

查询时没有数据就返回空数组。

获取某一个表的所有记录

import express from 'express';
import {PrismaClient} from '@prisma/client';
// 实例化prisma client客户端
const prisma = new PrismaClient();
const sever = express();
sever.use(express.json());
// 查询接口,获取某一个表的所有记录
sever.get('/userList',async (req,res) => {
    // user是表名,findMany是获取表的所有数据
    const data = await prisma.user.findMany();
    res.send(data);
});
sever.listen(8089, () => {
    console.log("8089端口已启动");
});

测试代码:request.http

GET  http://localhost:8089/userList HTTP/1.1

查询user表的所有数据:

image.png

获取某个表的记录同时获取关联表的数据(关联查找)

关联查找将返回一个树状结构的数据

import express from 'express';
import {PrismaClient} from '@prisma/client';
// 实例化prisma client客户端
const prisma = new PrismaClient();
const sever = express();
sever.use(express.json());
// 查询接口,获取某一个表的所有记录
sever.get('/userList',async (req,res) => {
    // user是表名,findMany是获取表的所有数据
    // 配置对象加入include对象,设置关联字段为true,即可进行关联查找
    const data = await prisma.user.findMany({
        include:{
            posts:true
        }
    });
    res.send(data);
});
sever.listen(8089, () => {
    console.log("8089端口已启动");
});

测试代码:request.http

GET  http://localhost:8089/userList HTTP/1.1

查询user表的所有数据并且关联查询post表数据:

image.png

单条数据查询

import express from 'express';
import {PrismaClient} from '@prisma/client';
// 实例化prisma client客户端
const prisma = new PrismaClient();
const sever = express();
sever.use(express.json());
// 单条数据查询接口
sever.get('/user/:id',async (req,res) => {
    // 获取动态参数
    const id = Number(req.params.id); // 数据库查询有严格的类型限制,id为INT类型,转换数字
    // user是表名,findMany是获取表的所有数据
    // 配置对象加入where对象,设置需要查询的条件
    const data = await prisma.user.findMany({
        where:{
            id
        }
    });
    res.send(data);
});
sever.listen(8089, () => {
    console.log("8089端口已启动");
});

测试代码:request.http

GET  http://localhost:8089/user/1 HTTP/1.1

查询结果:

image.png

没有数据的查询结果:

image.png

改(编辑数据库中的数据)

更新单条记录

import express from 'express';
import {PrismaClient} from '@prisma/client';
// 实例化prisma client客户端
const prisma = new PrismaClient();
const sever = express();
sever.use(express.json());
// 修改接口
sever.post('/update_user',async (req,res) => {
    const {name,email,id} = req.body;
    // user是数据表,update是更改数据
    // 配置对象中data是修改的数据,where是条件
    try{
        await prisma.user.update({
            data:{
                name,
                email
            },
            where:{
                id:Number(id)
            }
        });
        res.send({msg:'修改成功',code:200});
    }catch(error){
        console.log(error);
        res.send({msg:'修改失败',code:300,err:error});
    }
});
sever.listen(8089, () => {
    console.log("8089端口已启动");
});

测试代码:request.http

POST http://localhost:8089/update_user HTTP/1.1
Content-Type: application/json

{
    "email":"gloria@gnation.hk",
    "id":1
}

返回结果:

image.png

数据库中的数据修改成功:

image.png

更新多条记录

举例:

sever.post('/update_user',async (req,res) => {
    // 更新 user表中 email字段中包含prisma.io的全部数据,找到这些数据后,
    // 更新的是数据中的name字段值,更新为ADMIN
    try{
        await prisma.user.updateMany({
            data: {
                name: 'ADMIN',
            },
            where: {
                email: {
                    contains: 'prisma.io',
                }
            }
        });
        res.send({msg:'修改成功',code:200});
    }catch(error){
        console.log(error);
        res.send({msg:'修改失败',code:300,err:error});
    }
})

删(删除数据库中的数据)

删除数据时,如果数据表之间有关联关系,直接删除关系拥有者的数据表的数据会报错,需要先删除关联的数据才能删除。

级联删除:有外键关联其主键的数据不能直接删除,需要先删除主键关联的数据,才能删除该条数据。

删除单条数据

user表和post表的数据进行了关联,上述代码让两张表都有了数据,直接删除user表的数据会报错

// 删除接口
sever.post('/delete_user',async (req,res) =>{
    const {id} = req.body;
    // user是数据表,delete是删除单条数据
    // 配置对象是需要删除数据的条件
    try{
        await prisma.user.delete({
            where:{
                id:Number(id)
            }
        })
        res.send({msg:'删除成功',code:200});
    }catch(error){
        res.send({msg:'删除失败',code:300,err:error});
    };
});

测试代码:request.http

POST http://localhost:8089/delete_user HTTP/1.1
Content-Type: application/json

{
    "id":1
}

删除报错:

image.png

级联删除:

import express from 'express';
import {PrismaClient} from '@prisma/client';
// 实例化prisma client客户端
const prisma = new PrismaClient({
    // 显示日志信息的简写形式,不直观,LogDefinition其中的值emit始终为stdout
    // log: ['query', 'info', 'warn', 'error'] 
    // 推荐使用这种,LogDefinition其中的值emit设置为event就是显示在控制台的
    log: [
        { level: 'query', emit: 'event' }, // 在控制台显示
        { level: 'info', emit: 'stdout' }, // 不在控制台显示
        // { level: 'info', emit: 'event' }, // 在控制台显示
        { level: 'warn', emit: 'event' }, // 在控制台显示
        { level: 'error', emit: 'event' },// 在控制台显示
    ],
});
const sever = express();
sever.use(express.json());
// 删除接口
sever.post('/delete_user',async (req,res) =>{
    const {id} = req.body;
    try{
        // 先删除与之关联的数据
    // post是数据表,deleteMany是删除多条数据
    // 配置对象是需要删除数据的条件,authorId是外键,其值等于user表的主键id
    await prisma.post.deleteMany({
        where:{
            authorId:Number(id)
        }
    });
    // 再删除本条数据
    // user是数据表,delete是删除单条数据
    // 配置对象是需要删除数据的条件
        await prisma.user.delete({
            where:{
                id:Number(id)
            }
        })
        res.send({msg:'删除成功',code:200});
    }catch(error){
        console.log(error);
        res.send({msg:'删除失败',code:300,err:error});
    };
});
sever.listen(8089, () => {
    console.log("8089端口已启动");
});

删除结果:

image.png

数据库中的数据成功删除:

image.png

删除多条数据

举例:

sever.post('/delete_user',async (req,res) =>{
    try{
    await prisma.user.deleteMany({
        where: {
            email: {
                contains: 'prisma.io',
            },
        },
    })
        res.send({msg:'删除成功',code:200});
    }catch(error){
        console.log(error);
        res.send({msg:'删除失败',code:300,err:error});
    };
});

删除所有数据

举例:

// 删除接口
sever.post('/delete_user',async (req,res) =>{
    try{
        await await prisma.user.deleteMany({});
        res.send({msg:'删除成功',code:200});
    }catch(error){
        console.log(error);
        res.send({msg:'删除失败',code:300,err:error});
    };
});

事务

可以使用事务来确保一组数据库操作的原子性,即要么全部语句都成功提交,要么全部语句都失败。

在实际业务中,经常会遇到需要进行深度嵌套的事务写入操作。比如,需要在一个事务中创建一个用户,然后创建该用户的配置文件,接着创建配置文件的相关信息。这种情况下,需要确保所有的操作都成功,或者都失败,以保持数据的一致性。可以通过$transaction api方式来处理事务操作,将多个数据库操作包装在一个事务中,以确保它们要么全部成功,要么全部失败。

async createUserWithProfileAndInfo(){
    try {
        const user = await prisma.user.create({ 
            data: { name: 'John Doe', email: 'john@example.com', }, 
         });
         const profile = await prisma.profile.create({ 
             data: { userId: user.id, bio: 'Hello, I am John Doe', }, 
         });
         const profileFile = await prisma.info.create({ 
             data: { profileId: profile.id, details: 'Some additional details', }, 
         });
         // 提交事务
         await prisma.$transaction([user, profile, profileFile])
    }catch(e){
        console.error(e);
    }finally{
        // 全部都失败,回滚事务
        await prisma.$disconnect();
    }
}