<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          現(xiàn)代 Nodejs ORM 庫 Prisma 的使用詳解

          共 10234字,需瀏覽 21分鐘

           ·

          2021-10-21 14:56

          大廠技術(shù)??高級前端??Node進階

          點擊上方?程序員成長指北,關(guān)注公眾號

          回復(fù)1,加入高級Node交流群

          ORM(Object relational mappers) 的含義是,將數(shù)據(jù)模型與 Object 建立強力的映射關(guān)系,這樣我們對數(shù)據(jù)的增刪改查可以轉(zhuǎn)換為操作 Object(對象)。

          Prisma 是一個現(xiàn)代 Nodejs ORM 庫,根據(jù) Prisma 官方文檔 可以了解這個庫是如何設(shè)計與使用的。

          概述

          Prisma 提供了大量工具,包括 Prisma Schema、Prisma Client、Prisma Migrate、Prisma CLI、Prisma Studio 等,其中最核心的兩個是 Prisma Schema 與 Prisma Client,分別是描述應(yīng)用數(shù)據(jù)模型與 Node 操作 API。

          與一般 ORM 完全由 Class 描述數(shù)據(jù)模型不同,Primsa 采用了一個全新語法 Primsa Schema 描述數(shù)據(jù)模型,再執(zhí)行 prisma generate 產(chǎn)生一個配置文件存儲在 node_modules/.prisma/client 中,Node 代碼里就可以使用 Prisma Client 對數(shù)據(jù)增刪改查了。

          Prisma Schema

          Primsa Schema 是在最大程度貼近數(shù)據(jù)庫結(jié)構(gòu)描述的基礎(chǔ)上,對關(guān)聯(lián)關(guān)系進行了進一步抽象,并且背后維護了與數(shù)據(jù)模型的對應(yīng)關(guān)系,下圖很好的說明了這一點:

          可以看到,幾乎與數(shù)據(jù)庫的定義一模一樣,唯一多出來的 postsauthor 其實是彌補了數(shù)據(jù)庫表關(guān)聯(lián)外鍵中不直觀的部分,將這些外鍵轉(zhuǎn)化為實體對象,讓操作時感受不到外鍵或者多表的存在,在具體操作時再轉(zhuǎn)化為 join 操作。下面是對應(yīng)的 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 申明了鏈接數(shù)據(jù)庫信息;generator client 申明了使用 Prisma Client 進行客戶端操作,也就是說 Prisma Client 其實是可以替換實現(xiàn)的;model 是最核心的模型定義。

          在模型定義中,可以通過 @map 修改字段名映射、@@map 修改表名映射,默認情況下,字段名與 key 名相同:

          model Comment {
          title @map("comment_title")

          @@map("comments")
          }

          字段由下面四種描述組成:

          • 字段名。
          • 字段類型。
          • 可選的類型修飾。
          • 可選的屬性描述。
          model Tag {
          name String? @id
          }

          在這個描述里,包含字段名 name、字段類型 String、類型修飾 ?、屬性描述 @id。

          字段類型

          字段類型可以是 model,比如關(guān)聯(lián)類型字段場景:

          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?
          }

          關(guān)聯(lián)場景有 1v1, nv1, 1vn, nvn 四種情況,字段類型可以為定義的 model 名稱,并使用屬性描述 @relation 定義關(guān)聯(lián)關(guān)系,比如上面的例子,描述了 CommenctPost 存在 nv1 關(guān)系,并且 Comment.postIdPost.id 關(guān)聯(lián)。

          字段類型還可以是底層數(shù)據(jù)類型,通過 @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[]
          }

          分別表示可選與數(shù)組。

          屬性描述

          屬性描述有如下幾種語法:

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

          @@unique([firstName, lastName])
          }

          @id 對應(yīng)數(shù)據(jù)庫的 PRIMARY KEY。

          @default 設(shè)置字段默認值,可以聯(lián)合函數(shù)使用,比如 @default(autoincrement()),可用函數(shù)包括 autoincrement()、dbgenerated()、cuid()uuid()、now(),還可以通過 dbgenerated 直接調(diào)用數(shù)據(jù)庫底層的函數(shù),比如 dbgenerated("gen_random_uuid()")。

          @unique 設(shè)置字段值唯一。

          @relation 設(shè)置關(guān)聯(lián),上面已經(jīng)提到過了。

          @map 設(shè)置映射,上面也提到過了。

          @updatedAt 修飾字段用來存儲上次更新時間,一般是數(shù)據(jù)庫自帶的能力。

          @ignore 對 Prisma 標記無效的字段。

          所有屬性描述都可以組合使用,并且還存在需對 model 級別的描述,一般用兩個 @ 描述,包括 @@id@@unique、@@index@@map、@@ignore

          ManyToMany

          Prisma 在多對多關(guān)聯(lián)關(guān)系的描述上也下了功夫,支持隱式關(guān)聯(lián)描述:

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

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

          看上去很自然,但其實背后隱藏了不少實現(xiàn)。數(shù)據(jù)庫多對多關(guān)系一般通過第三張表實現(xiàn),第三張表會存儲兩張表之間外鍵對應(yīng)關(guān)系,所以如果要顯式定義其實是這樣的:

          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 后,執(zhí)行 prisma generate,再利用 npm install @prisma/client 安裝好 Node 包后,就可以在代碼里操作 ORM 了:

          import?{?PrismaClient?}?from?'@prisma/client'

          const?prisma?=?new?PrismaClient()

          CRUD

          使用 create 創(chuàng)建一條記錄:

          const?user?=?await?prisma.user.create({
          ??data:?{
          ????email:?'[email protected]',
          ????name:?'Elsa?Prisma',
          ??},
          })

          使用 createMany 創(chuàng)建多條記錄:

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

          使用 findUnique 查找單條記錄:

          const?user?=?await?prisma.user.findUnique({
          ??where:?{
          ????email:?'[email protected]',
          ??},
          })

          對于聯(lián)合索引的情況:

          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:?'[email protected]',
          ??},
          ??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:?'[email protected]',
          ??},
          })

          使用 deleteMany 刪除多條記錄:

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

          使用 include 表示關(guān)聯(lián)查詢是否生效,比如:

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

          這樣就會在查詢 user 表時,順帶查詢所有關(guān)聯(lián)的 post 表。關(guān)聯(lián)查詢也支持嵌套:

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

          篩選條件支持 equals、not、innotIn、lt、lte、gt、gte、contains、search、mode、startsWithendsWith、AND、OR、NOT,一般用法如下:

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

          這個語句代替 sql 的 where name="Eleanor",即通過對象嵌套的方式表達語義。

          Prisma 也可以直接寫原生 SQL:

          const?email?=?'[email protected]'
          const?result?=?await?prisma.$queryRaw(
          ??Prisma.sql`SELECT?*?FROM?User?WHERE?email?=?${email}`
          )

          中間件

          Prisma 支持中間件的方式在執(zhí)行過程中進行拓展,看下面的例子:

          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

          可以看到,中間件執(zhí)行順序是洋蔥模型,并且每個操作都會觸發(fā)。我們可以利用中間件拓展業(yè)務(wù)邏輯或者進行操作時間的打點記錄。

          精讀

          ORM 的兩種設(shè)計模式

          ORM 有 Active Record 與 Data Mapper 兩種設(shè)計模式,其中 Active Record 使對象背后完全對應(yīng) sql 查詢,現(xiàn)在已經(jīng)不怎么流行了,而 Data Mapper 模式中的對象并不知道數(shù)據(jù)庫的存在,即中間多了一層映射,甚至背后不需要對應(yīng)數(shù)據(jù)庫,所以可以做一些很輕量的調(diào)試功能。

          Prisma 采用了 Data Mapper 模式。

          ORM 容易引發(fā)性能問題

          當數(shù)據(jù)量大,或者性能、資源敏感的情況下,我們需要對 SQL 進行優(yōu)化,甚至我們需要對特定的 Mysql 的特定版本的某些內(nèi)核錯誤,對 SQL 進行某些看似無意義的申明調(diào)優(yōu)(比如在 where 之前再進行相同條件的 IN 范圍限定),有的時候能取得驚人的性能提升。

          而 ORM 是建立在一個較為理想化理論基礎(chǔ)上的,即數(shù)據(jù)模型可以很好的轉(zhuǎn)化為對象操作,然而對象操作由于屏蔽了細節(jié),我們無法對 SQL 進行針對性調(diào)優(yōu)。

          另外,得益于對象操作的便利性,我們很容易通過 obj.obj. 的方式訪問某些屬性,但這背后生成的卻是一系列未經(jīng)優(yōu)化(或者部分自動優(yōu)化)的復(fù)雜 join sql,我們在寫這些 sql 時會提前考慮性能因素,但通過對象調(diào)用時卻因為成本低,或覺得 ORM 有 magic 優(yōu)化等想法,寫出很多實際上不合理的 sql。

          Prisma Schema 的好處

          其實從語法上,Prisma Schema 與 Typeorm 基于 Class + 裝飾器的拓展幾乎可以等價轉(zhuǎn)換,但 Prisma Schema 在實際使用中有一個很不錯的優(yōu)勢,即減少樣板代碼以及穩(wěn)定數(shù)據(jù)庫模型。

          減少樣板代碼比較好理解,因為 Prisma Schema 并不會出現(xiàn)在代碼中,而穩(wěn)定模型是指,只要不執(zhí)行 prisma generate,數(shù)據(jù)模型就不會變化,而且 Prisma Schema 也獨立于 Node 存在,甚至可以不放在項目源碼中,相比之下,修改起來會更加慎重,而完全用 Node 定義的模型因為本身是代碼的一部分,可能會突然被修改,而且也沒有執(zhí)行數(shù)據(jù)庫結(jié)構(gòu)同步的操作。

          如果項目采用 Prisma,則模型變更后,可以執(zhí)行 prisma db pull 更新數(shù)據(jù)庫結(jié)構(gòu),再執(zhí)行 prisma generate 更新客戶端 API,這個流程比較清晰。

          總結(jié)

          Prisma Schema 是 Prisma 的一大特色,因為這部分描述獨立于代碼,帶來了如下幾個好處:

          1. 定義比 Node Class 更簡潔。
          2. 不生成冗余的代碼結(jié)構(gòu)。
          3. Prisma Client 更加輕量,且查詢返回的都是 Pure Object。

          至于 Prisma Client 的 API 設(shè)計其實并沒有特別突出之處,無論與 sequelize 還是 typeorm 的 API 設(shè)計相比,都沒有太大的優(yōu)化,只是風(fēng)格不同。

          不過對于記錄的創(chuàng)建,我更喜歡 Prisma 的 API:

          //?typeorm?-?save?API
          const?userRepository?=?getManager().getRepository(User)
          const?newUser?=?new?User()
          newUser.name?=?'Alice'
          userRepository.save(newUser)

          //?typeorm?-?insert?API
          const?userRepository?=?getManager().getRepository(User)
          userRepository.insert({
          ??name:?'Alice',
          })

          //?sequelize
          const?user?=?User.build({
          ??name:?'Alice',
          })
          await?user.save()

          //?Mongoose
          const?user?=?await?User.create({
          ??name:?'Alice',
          ??email:?'[email protected]',
          })

          //?prisma
          const?newUser?=?await?prisma.user.create({
          ??data:?{
          ????name:?'Alice',
          ??},
          })

          首先存在 prisma 這個頂層變量,使用起來會非常方便,另外從 API 拓展上來說,雖然 Mongoose 設(shè)計得更簡潔,但添加一些條件時拓展性會不足,導(dǎo)致結(jié)構(gòu)不太穩(wěn)定,不利于統(tǒng)一記憶。

          Prisma Client 的 API 統(tǒng)一采用下面這種結(jié)構(gòu):

          await?prisma.modelName.operateName({
          ??//?數(shù)據(jù),比如?create、update?時會用到
          ??data:?/**?...?*/,
          ??//?條件,大部分情況都可以用到
          ??where:?/**?...?*/,
          ??//?其它特殊參數(shù),或者?operater?特有的參數(shù)
          })

          所以總的來說,Prisma 雖然沒有對 ORM 做出革命性改變,但在微創(chuàng)新與 API 優(yōu)化上都做得足夠棒,github 更新也比較活躍,如果你決定使用 ORM 開發(fā)項目,還是比較推薦 Prisma 的。

          在實際使用中,為了規(guī)避 ORM 產(chǎn)生笨拙 sql 導(dǎo)致的性能問題,可以利用 Prisma Middleware 監(jiān)控查詢性能,并對性能較差的地方采用 prisma.$queryRaw 原生 sql 查詢。

          討論地址是:精讀《Prisma 的使用》· Issue #362 · dt-fe/weekly

          Node 社群


          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


          ???“分享、點贊、在看” 支持一波??


          瀏覽 90
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草免费在线视频观看免费 | www.1800av | 香蕉国产成人毛片 | 久久88 | 国产精品123视频 |