Advertisement

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

阅读量:

ORM(Object relational mappers) 的作用在于将数据模型与 Object 创建出紧密的关系,并使我们对数据执行增删改查的操作对应于对 Object 的操作。

Prisma 是 contemporary 的 Nodejs ORM 框架,通过 Prisma 官方文档 深入掌握其设计与使用。

概述

包含多种功能模块。
包括以下功能模块:包括 Prisma Schema、Prismal Client、Prism Migrate、Prism CLI 和 Prism Studio 等。
其中最为关键的是 Prismal Schema 和 Prismal Client。
它们分别用于描述应用的数据模型以及 Node 操作的 API 接口。

不同于一般的ORM系统中使用Class来描述数据模型,Primsa采用了全新的描述数据模型的语法方案Primsa Schema。然后运行命令 prisma generate 会生成一个配置文件并将其放置于 node_modules/.prisma/client 文件夹内。这样,在Node开发环境中就可以方便地调用Prisma Client实现对数据库的数据增删改查功能。

Prisma Schema

Primsa Schema 最近似地贴合数据库结构描述的基础之上,在此基础上对关联关系进行了高度抽象,并在背后维持着与数据模型的一一对应。这一现象通过图表清晰地得以呈现:

可以看出基本与数据库的标准架构完全一致其中新增的 postsauthor 则巧妙地解决了 databases 表间关联外键难以直观表现的问题。把它们转换为独立的对象实体在实际操作过程中则通过 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[]
    
 }

数据源数据库声明为连接数据库信息;生成器客户端声明为使用Prisma Client进行客户端操作;模型实例是最核心的模型定义。

在模型定义中,在使用 @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进行描述。例如,在示例中,我们描述了Comment与Post之间存在的nv1关联关系;其中Comment.postId与Post.id实现了双向关联

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

复制代码
 model Post {

    
   id @db.TinyInt(1)
    
 }

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

复制代码
 model Post {

    
   someField Unsupported("polygon")?
    
 }

此类字段无法采用 ORM API 进行查询;然而,则可通过 queryRaw 方式实现查询。此外,在 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直接调用数据库底层的特定功能,并非只能通过命令行接口进行操作。例如,在需要生成唯一标识时可以直接调用如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 模型后, 被生成的过程可以通过运行命令完成; 安装好 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 表的查询时

复制代码
 const user = await prisma.user.findMany({

    
   include: {
    
     posts: {
    
       include: {
    
     categories: true,
    
       },
    
     },
    
   },
    
 })

筛选条件支持等价于(equals)、非(not)以及一系列条件判断操作。这些操作符包括查找(search)、包含(contains)、开头匹配(startsWith)和结尾匹配(endsWith)。此外还有逻辑运算符如同时满足的逻辑与(AND)和至少满足一个的逻辑或(OR),以及反向筛选的非逻辑与(NOT)。一般用法如下

复制代码
 const result = await prisma.user.findMany({

    
   where: {
    
     name: {
    
       equals: 'Eleanor',
    
     },
    
   },
    
 })

在数据库查询中使用这种方法替代 SQL 的 where 子句 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游戏制作。

全部评论 (0)

还没有任何评论哟~