当前位置: 首页 > 工具软件 > Prisma > 使用案例 >

建议收藏|一文带你读懂 Prisma 的使用

姜正初
2023-12-01

ORM(Object relational mappers) 的含义是,将数据模型与 Object 建立强力的映射关系,这样我们对数据的增删改查可以转换为操作 Object(对象)。

Prisma 是一个现代 Nodejs ORM 库,根据 Prisma 官方文档 可以了解这个库是如何设计与使用的。

概述

Prisma 提供了大量工具,包括 Prisma Schema、Prisma Client、Prisma Migrate、Prisma CLI、Prisma Studio 等,其中最核心的两个是 Prisma Schema 与 Prisma Client,分别是描述应用数据模型与 Node 操作 API。

与一般 ORM 完全由 Class 描述数据模型不同,Primsa 采用了一个全新语法 Primsa Schema 描述数据模型,再执行 prisma generate 产生一个配置文件存储在 node_modules/.prisma/client 中,Node 代码里就可以使用 Prisma Client 对数据增删改查了。

Prisma Schema

Primsa Schema 是在最大程度贴近数据库结构描述的基础上,对关联关系进行了进一步抽象,并且背后维护了与数据模型的对应关系,下图很好的说明了这一点:

<img width=400 src="https://z3.ax1x.com/2021/10/17/5YwZoF.png">

可以看到,几乎与数据库的定义一模一样,唯一多出来的 posts 与 author 其实是弥补了数据库表关联外键中不直观的部分,将这些外键转化为实体对象,让操作时感受不到外键或者多表的存在,在具体操作时再转化为 join 操作。下面是对应的 Prisma Schema:

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

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

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String? @map("post_content")
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

datasource db 申明了链接数据库信息;generator client 申明了使用 Prisma Client 进行客户端操作,也就是说 Prisma Client 其实是可以替换实现的;model 是最核心的模型定义。

在模型定义中,可以通过 @map 修改字段名映射、@@map 修改表名映射,默认情况下,字段名与 key 名相同:

model Comment {
  title @map("comment_title")

  @@map("comments")
}

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

  • 字段名。
  • 字段类型。
  • 可选的类型修饰。
  • 可选的属性描述。
model Tag {
  name String? @id
}

在这个描述里,包含字段名 name、字段类型 String、类型修饰 ?、属性描述 @id

字段类型

字段类型可以是 model,比如关联类型字段场景:

model Post {
  id       Int       @id @default(autoincrement())
  // Other fields
  comments Comment[] // A post can have many comments
}

model Comment {
  id     Int
  // Other fields
  Post   Post? @relation(fields: [postId], references: [id]) // A comment can have one post
  postId Int?
}

关联场景有 1v1, nv1, 1vn, nvn 四种情况,字段类型可以为定义的 model 名称,并使用属性描述 @relation 定义关联关系,比如上面的例子,描述了 Commenct 与 Post 存在 nv1 关系,并且 Comment.postId 与 Post.id 关联。

字段类型还可以是底层数据类型,通过 @db. 描述,比如:

model Post {
  id @db.TinyInt(1)
}

对于 Prisma 不支持的类型,还可以使用 Unsupported 修饰:

model Post {
  someField Unsupported("polygon")?
}

这种类型的字段无法通过 ORM API 查询,但可以通过 queryRaw 方式查询。queryRaw 是一种 ORM 对原始 SQL 模式的支持,在 Prisma Client 会提到。

类型修饰

类型修饰有 ? [] 两种语法,比如:

model User {
  name  String?
  posts Post[]
}

分别表示可选与数组。

属性描述

属性描述有如下几种语法:

model User {
  id        Int     @id @default(autoincrement())
  isAdmin   Boolean @default(false)
  email     String  @unique

  @@unique([firstName, lastName])
}

@id 对应数据库的 PRIMARY KEY。

@default 设置字段默认值,可以联合函数使用,比如 @default(autoincrement()),可用函数包括 autoincrement()dbgenerated()cuid()uuid()now(),还可以通过 dbgenerated 直接调用数据库底层的函数,比如 dbgenerated("gen_random_uuid()")

@unique 设置字段值唯一。

@relation 设置关联,上面已经提到过了。

@map 设置映射,上面也提到过了。

@updatedAt 修饰字段用来存储上次更新时间,一般是数据库自带的能力。

@ignore 对 Prisma 标记无效的字段。

所有属性描述都可以组合使用,并且还存在需对 model 级别的描述,一般用两个 @ 描述,包括 @@id@@unique@@index@@map@@ignore

ManyToMany

Prisma 在多对多关联关系的描述上也下了功夫,支持隐式关联描述:

model Post {
  id         Int        @id @default(autoincrement())
  categories Category[]
}

model Category {
  id    Int    @id @default(autoincrement())
  posts Post[]
}

看上去很自然,但其实背后隐藏了不少实现。数据库多对多关系一般通过第三张表实现,第三张表会存储两张表之间外键对应关系,所以如果要显式定义其实是这样的:

model Post {
  id         Int                 @id @default(autoincrement())
  categories CategoriesOnPosts[]
}

model Category {
  id    Int                 @id @default(autoincrement())
  posts CategoriesOnPosts[]
}

model CategoriesOnPosts {
  post       Post     @relation(fields: [postId], references: [id])
  postId     Int // relation scalar field (used in the `@relation` attribute above)
  category   Category @relation(fields: [categoryId], references: [id])
  categoryId Int // relation scalar field (used in the `@relation` attribute above)
  assignedAt DateTime @default(now())
  assignedBy String

  @@id([postId, categoryId])
}

背后生成如下 SQL:

CREATE TABLE "Category" (
    id SERIAL PRIMARY KEY
);
CREATE TABLE "Post" (
    id SERIAL PRIMARY KEY
);
-- Relation table + indexes -------------------------------------------------------
CREATE TABLE "CategoryToPost" (
    "categoryId" integer NOT NULL,
    "postId" integer NOT NULL,
    "assignedBy" text NOT NULL
    "assignedAt" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY ("categoryId")  REFERENCES "Category"(id),
    FOREIGN KEY ("postId") REFERENCES "Post"(id)
);
CREATE UNIQUE INDEX "CategoryToPost_category_post_unique" ON "CategoryToPost"("categoryId" int4_ops,"postId" int4_ops);

Prisma Client

描述好 Prisma Model 后,执行 prisma generate,再利用 npm install @prisma/client 安装好 Node 包后,就可以在代码里操作 ORM 了:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

CRUD

使用 create 创建一条记录:

const user = await prisma.user.create({
  data: {
    email: 'elsa@prisma.io',
    name: 'Elsa Prisma',
  },
})

使用 createMany 创建多条记录:

const createMany = await prisma.user.createMany({
  data: [
    { name: 'Bob', email: 'bob@prisma.io' },
    { name: 'Bobo', email: 'bob@prisma.io' }, // Duplicate unique key!
    { name: 'Yewande', email: 'yewande@prisma.io' },
    { name: 'Angelique', email: 'angelique@prisma.io' },
  ],
  skipDuplicates: true, // Skip 'Bobo'
})

使用 findUnique 查找单条记录:

const user = await prisma.user.findUnique({
  where: {
    email: 'elsa@prisma.io',
  },
})

对于联合索引的情况:

model TimePeriod {
  year    Int
  quarter Int
  total   Decimal

  @@id([year, quarter])
}

需要再嵌套一层由 _ 拼接的 key:

const timePeriod = await prisma.timePeriod.findUnique({
  where: {
    year_quarter: {
      quarter: 4,
      year: 2020,
    },
  },
})

使用 findMany 查询多条记录:

const users = await prisma.user.findMany()

可以使用 SQL 中各种条件语句,语法如下:

const users = await prisma.user.findMany({
  where: {
    role: 'ADMIN',
  },
  include: {
    posts: true,
  },
})

使用 update 更新记录:

const updateUser = await prisma.user.update({
  where: {
    email: 'viola@prisma.io',
  },
  data: {
    name: 'Viola the Magnificent',
  },
})

使用 updateMany 更新多条记录:

const updateUsers = await prisma.user.updateMany({
  where: {
    email: {
      contains: 'prisma.io',
    },
  },
  data: {
    role: 'ADMIN',
  },
})

使用 delete 删除记录:

const deleteUser = await prisma.user.delete({
  where: {
    email: 'bert@prisma.io',
  },
})

使用 deleteMany 删除多条记录:

const deleteUsers = await prisma.user.deleteMany({
  where: {
    email: {
      contains: 'prisma.io',
    },
  },
})

使用 include 表示关联查询是否生效,比如:

const getUser = await prisma.user.findUnique({
  where: {
    id: 19,
  },
  include: {
    posts: true,
  },
})

这样就会在查询 user 表时,顺带查询所有关联的 post 表。关联查询也支持嵌套:

const user = await prisma.user.findMany({
  include: {
    posts: {
      include: {
        categories: true,
      },
    },
  },
})

筛选条件支持 equalsnotinnotInltltegtgtecontainssearchmodestartsWithendsWithANDORNOT,一般用法如下:

const result = await prisma.user.findMany({
  where: {
    name: {
      equals: 'Eleanor',
    },
  },
})

这个语句代替 sql 的 where name="Eleanor",即通过对象嵌套的方式表达语义。

Prisma 也可以直接写原生 SQL:

const email = 'emelie@prisma.io'
const result = await prisma.$queryRaw(
  Prisma.sql`SELECT * FROM User WHERE email = ${email}`
)

中间件

Prisma 支持中间件的方式在执行过程中进行拓展,看下面的例子:

const prisma = new PrismaClient()

// Middleware 1
prisma.$use(async (params, next) => {
  console.log(params.args.data.title)
  console.log('1')
  const result = await next(params)
  console.log('6')
  return result
})

// Middleware 2
prisma.$use(async (params, next) => {
  console.log('2')
  const result = await next(params)
  console.log('5')
  return result
})

// Middleware 3
prisma.$use(async (params, next) => {
  console.log('3')
  const result = await next(params)
  console.log('4')
  return result
})

const create = await prisma.post.create({
  data: {
    title: 'Welcome to Prisma Day 2020',
  },
})

const create2 = await prisma.post.create({
  data: {
    title: 'How to Prisma!',
  },
})

输出如下:

Welcome to Prisma Day 2020 
1 
2 
3 
4 
5 
6 
How to Prisma! 
1 
2 
3 
4 
5 
6

可以看到,中间件执行顺序是洋葱模型,并且每个操作都会触发。我们可以利用中间件拓展业务逻辑或者进行操作时间的打点记录。

如果你想开发小程序或者了解更多小程序的内容,可以通过专业开发公司,来帮助你实现开发需求:厦门在乎科技-专注厦门小程序定制开发、APP开发、网站开发、H5小游戏开发

 类似资料: