1. 程式人生 > 其它 >Prisma:下一代ORM,不僅僅是ORM(上篇)

Prisma:下一代ORM,不僅僅是ORM(上篇)

前言

本篇文章將會介紹一個NodeJS社群中的ORM:Prisma。我接觸它的時間不算長,但已經對它的未來發展充滿信心。這篇文章其實三個月以前就寫了一部分,所以文中會出現“如果你覺得它不錯,不如考慮基於Prisma來完成你的畢設”這樣的話。

在剛開始寫的時候,bven爺的畢設一行都還沒動,而到了我今天發的時候,他已經是優秀畢業生了...

同時,原本準備一篇搞定所有內容,但是覺得這種教程類的文章如果寫的這麼長,很難讓人有讀完的興致。所以就拆成了兩部分:

  • 第一部分主要是鋪墊,介紹目前NodeJS社群比較主流的ORM與Query Builder,以及Prisma的簡單使用,這一部分主要是為接觸ORM較少的同學做一個基礎知識的鋪墊。
  • 第二部分包括Prisma的花式進階使用,包括多表級聯、多資料庫協作以及與GraphQL的實戰,最後會展開來聊一聊Prisma的未來。

文章的大致順序如下:

  • NodeJS社群中的老牌、傳統ORM
  • 傳統ORM的Data Mapper 與 Active Record模式
  • Query Builder
  • Prisma的基礎環境配置
  • Hello Prisma
  • 從單表CRUD開始
  • 多表、多資料庫實戰
  • Prisma與GraphQL:全鏈路型別安全
  • Prisma與一體化框架

NodeJS社群中的ORM

經常寫Node應用的同學通常免不了要和ORM打交道,畢竟寫原生SQL對於大部分前端同學來說真的是一種折磨。ORM的便利性使得很多情況下我們能直觀而方便的和資料庫打交道(雖然的確有些情況下ORM搞不定),用我們熟悉的JavaScript來花式操作資料庫。 NodeJS社群中主流的ORM主要有這麼幾個,它們都有各自的一些特色:

  • Sequelize,比較老牌的一款ORM,缺點是TS支援不太好,但是社群有Sequelize-TypeScript
    Sequelize定義表結構的方式是這樣的:
  • ``typescript
    const { Sequelize, Model, DataTypes } = require('sequelize');
    const sequelize = new Sequelize('sqlite::memory:');

    class User extends Model {}

    User.init({
    username: DataTypes.STRING,
    birthday: DataTypes.DATE
    }, { sequelize, modelName: 'user' });


    (async () => {
    await sequelize.sync();
    const jane = await User.create({
    username: 'janedoe',
    birthday: new Date(1980, 6, 20)
    });
    console.log(jane.toJSON());
    })();
    ```
    (我是覺得不那麼符合直覺,所以我只在入門時期簡單使用過)
  • TypeORM,NodeJS社群star最多的一個ORM。也確實很好用,在我周圍的同學裡備受好評,同時也是我自己用的最多的一個ORM。亮點在基於裝飾器語法宣告表結構、事務、級聯等,以及很棒的TS支援。
    TypeORM宣告表結構是這樣的:
  • `typescript
    import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

    @Entity()
    export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    age: number;
    }

    比起Sequelize來要直觀的多,而且由於通過類屬性的方式來定義資料庫欄位,可以很好的相容Mixin以及其他基於類屬性的工具庫,如TypeGraphQL
  • MikroORM,比較新的一個ORM,同樣大量基於裝飾器語法,亮點在於自動處理所有事務以及表實體會在全域性保持單例模式,暫時還沒深入使用過過。
    MikroORM定義表結構方式是這樣的:
  • `typescript
    @Entity()
    export class Book extends BaseEntity {

    @Property()
    title!: string;

    @ManyToOne()
    author!: Author;

    @ManyToOne()
    publisher?: IdentifiedReference<Publisher>;

    @ManyToMany({ fixedOrder: true })
    tags = new Collection<BookTag>(this);

    }

  • MongooseTypegoose,MongoDB專用的ORM,這裡簡單放一下TypeGoose的使用示例:
  • `typescript
    import { prop, getModelForClass } from '@typegoose/typegoose';
    import * as mongoose from 'mongoose';

    class User {
    @prop()
    public name?: string;

    @prop({ type: () => [String] })
    public jobs?: string[];
    }

    const UserModel = getModelForClass(User);

    (async () => {
    await mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true, dbName: 'test' });

    const { _id: id } = await UserModel.create({ name: 'JohnDoe', jobs: ['Cleaner'] } as User);

    const user = await UserModel.findById(id).exec();

    console.log(user);
    })();

  • Bookshelf,一個相對簡單一些但也五臟俱全的ORM,基於Knex(Strapi底層的Query Builder,後面會簡單介紹)。它的使用方式大概是這樣的:
  • `typescript
    const knex = require('knex')({
    client: 'mysql',
    connection: process.env.MYSQL_DATABASE_CONNECTION
    })
    // bookshelf 基於 knex,所以需要例項化knex然後傳入
    const bookshelf = require('bookshelf')(knex)

    const User = bookshelf.model('User', {
    tableName: 'users',
    posts() {
    return this.hasMany(Posts)
    }
    })

    const Post = bookshelf.model('Post', {
    tableName: 'posts',
    tags() {
    return this.belongsToMany(Tag)
    }
    })

    const Tag = bookshelf.model('Tag', {
    tableName: 'tags'
    })

    new User({id: 1}).fetch({withRelated: ['posts.tags']}).then((user) => {
    console.log(user.related('posts').toJSON())
    }).catch((error) => {
    console.error(error)
    })

    另外,一個比較獨特的地方是bookshelf支援了外掛機制,其他ORM通常通過hook或者subscriber的方式實現類似的功能,如密碼存入時進行一次加密、TPS計算、等。

ORM的Data Mapper與Actice Record模式

如果你去看了上面列舉的ORM文件,你會發現MikroORM的簡介中包含這麼一句話:TypeScript ORM for Node.js based on Data Mapper,而TypeORM的簡介中則是TypeORM supports both Active Record and Data Mapper patterns

先來一個問題,使用ORM的過程中,你是否瞭解過Data Mapper與Active Record這兩種模式的區別?

先來看看TypeORM中分別是如何使用這兩種模式的:

Active Record:

import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  isActive: boolean;
}

const user = new User();
user.name = "不渡";
user.isActive = true;

await user.save();

const newUsers = await User.find({ isActive: true });

TypeORM中,Active Record模式下需要讓實體類繼承BaseEntity類,這樣實體類上就具有了各種方法,如saveremovefind方法等。Active Record模式最早由Martin Fowler企業級應用架構模式一書中命名,這一模式使得物件上擁有了相關的CRUD方法。在RoR中就使用了這一模式來作為MVC中的M,即資料驅動層。如果你對RoR中的Active Record有興趣,可以閱讀全面理解Active Record(我不會Ruby,因此就不做介紹了)。

Data Mapper:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  isActive: boolean;
}

const userRepository = connection.getRepository(User);

const user = new User();
user.name = "不渡";
user.isActive = true;

await userRepository.save(user);

await userRepository.remove(user);

const newUsers = await userRepository.find({ isActive: true });

可以看到在Data Mapper模式中,實體類不再能夠自己進行資料庫操作,而是需要先獲取到一個對應到表的“倉庫”,然後再呼叫這個“倉庫”上的方法。

這一模式同樣由Martin Fowler最初命名,Data Mapper更像是一層攔在操作者與實際資料之間的訪問層,就如上面例子中先獲取具有訪問許可權(即相應方法)的物件,再進行資料的操作。

對這兩個模式進行比較,很容易發現Active Record模式要簡單的多,而Data Mapper模式則更加嚴謹。那麼何時使用這兩種模式就很清楚了,如果你在開發比較簡單的應用,直接使用Active Record模式就好了,因為這確實會減少很多程式碼。但是如果你在開發規模較大的應用,使用Data Mapper模式則能夠幫助你更好的維護程式碼(實體類不再具有訪問資料庫許可權了,只能通過統一的介面(getRepositorygetManager等)),一個例子是在Nest、Midway這兩個IoC風格的Node框架中,均使用Data Mapper模式注入Repository例項,然後再進行操作。

最後,NodeJS中使用Data Mapper的ORM主要包括Bookshelf、MikroORM、objection.js以及本文主角Prisma等。

Query Builder

實際上除了ORM與原生SQL以外,還有一種常用的資料庫互動方式:Query Builder(以下簡稱QB)。

QB和ORM其實我個人覺得既有相同之處又有不同之處,但是挺容易搞混,比如MQuery(MongoDB的一個Query Builder)的方法是這樣的:

mquery().find(match, function (err, docs) {
  assert(Array.isArray(docs));
})

mquery().findOne(match, function (err, doc) {
  if (doc) {
    // the document may not be found
    console.log(doc);
  }
})

mquery().update(match, updateDocument, options, function (err, result){})

是不是看起來和ORM很像?但我們再看看其他的場景:

mquery({ name: /^match/ })
  .collection(coll)
  .setOptions({ multi: true })
  .update({ $addToSet: { arr: 4 }}, callback)

在ORM中,通常不會存在這樣的多個方法鏈式呼叫,而是通過單個方法+多個引數的方式來操作,這也是Query Builder和ORM的一個重要差異。再來看看TypeORM的Query Builder模式:

import { getConnection } from "typeorm";

const user = await getConnection()
  .createQueryBuilder()
  .select("user")
  .from(User, "user")
  .where("user.id = :id", { id: 1 })
  .getOne();

以上的操作其實就相當於userRepo.find({ id: 1 }),你可能會覺得QB的寫法過於繁瑣,但實際上這種模式要靈活的多,和SQL語句的距離也要近的多(你可以理解為每一個鏈式方法呼叫都會對最終生成的SQL語句進行一次操作)。

同時在部分情境(如多級級聯下)中,Query Builder反而是程式碼更簡潔的那一方,如:

 const selectQueryBuilder = this.executorRepository
      .createQueryBuilder("executor")
      .leftJoinAndSelect("executor.tasks", "tasks")
      .leftJoinAndSelect("executor.relatedRecord", "records")
      .leftJoinAndSelect("records.recordTask", "recordTask")
      .leftJoinAndSelect("records.recordAccount", "recordAccount")
      .leftJoinAndSelect("records.recordSubstance", "recordSubstance")
      .leftJoinAndSelect("tasks.taskSubstance", "substance");

以上程式碼構建了一個包含多張表的級聯關係的Query Builder。

級聯關係如下:
  • Executor
    • tasks -> Task
    • relatedRecord -> Record

  • Task
    • substances -> Substance
  • Record
    • recordTask -> Task
    • recordAccount -> Account
    • recordSubstance -> Substance

再看一個比較主流的Query Builderknex,我是在嚐鮮strapi的過程中發現的,strapi底層依賴於knex去進行資料庫互動以及連線池相關的功能,knex的使用大概是這樣的:

const knex = require('knex')({
  client: 'sqlite3',
  connection: {
    filename: './data.db',
  },
});

try {

  await knex.schema
    .createTable('users', table => {
      table.increments('id');
      table.string('user_name');
    })
    .createTable('accounts', table => {
      table.increments('id');
      table.string('account_name');
      table
        .integer('user_id')
        .unsigned()
        .references('users.id');
    })

  const insertedRows = await knex('users').insert({ user_name: 'Tim' })

  await knex('accounts').insert({ account_name: 'knex', user_id: insertedRows[0] })

  const selectedRows = await knex('users')
    .join('accounts', 'users.id', 'accounts.user_id')
    .select('users.user_name as user', 'accounts.account_name as account')

  const enrichedRows = selectedRows.map(row => ({ ...row, active: true }))

} catch(e) {
  console.error(e);
};

可以看到knex的鏈式操作更進了一步,甚至可以鏈式建立多張資料庫表。

Prisma

接下來就到了我們本篇文章的主角:Prisma。Prisma對自己的定義仍然是NodeJS的ORM,但個人感覺它比普通意義上的ORM要強大得多。這裡放一張官方的圖,來大致瞭解下Prisma和ORM、SQL、Query Builder的能力比較:

你也可以閱讀方方老師翻譯的這篇Why Prisma?來了解更多。

獨特的Schema定義方式、比TypeORM更加嚴謹全面的TS型別定義(尤其是在級聯關係中)、更容易上手和更貼近原生SQL的各種操作符等,很容易讓初次接觸的人慾罷不能(別說了,就是我)。

簡單的介紹下這些特點:

  • Schema定義,我們前面看到的ORM都是使用JS/TS檔案來定義資料庫表結構的,而Prisma不同,它使用.prisma字尾的檔案來書寫獨特的Prisma Schema,然後基於schema生成表結構,VS Code有prisma官方提供的高亮、語法檢查外掛,所以不用擔心使用負擔。
    同時,這也就意味著圍繞Prisma Schema會產生一批generator功能的生態,如typegraphql-prisma就能夠基於Prisma Schema生成TypeGraphQL的Class定義,甚至還有CRUD的基本Resolver,類似的還有palJS提供的基於Prisma Schema生成Nexus的型別定義與CRUD方法(所以說GraphQL和Prisma這種都是SDL-First的工具真的是天作之合)。
    TypeGraphQL、Resolver屬於GraphQL相關的工具/概念,如果未曾瞭解過也不要緊。

    一個簡單的schema.prisma可能是這樣的:
    datasource db { provider = "sqlite" url = env("SINGLE_MODEL_DATABASE_URL") } generator client { provider = "prisma-client-js" output = "./client" } model Todo { id Int @id @default(autoincrement()) title String content String? finished Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
    是不是感覺即使你沒用過,但還是挺好看懂。

  • TS型別定義,可以說Prisma的型別定義是全覆蓋的,查詢引數、操作符引數、級聯引數、返回結果等等,比TypeORM的都更加完善。
  • 更全面的操作符,如對字串的查詢,Prisma中甚至提供了contains、startsWith、endsWith這種細粒度的操作符供過濾使用(而TypeORM中只能使用ILike這種方法來全量匹配)。(這些操作符的具體作用我們會在後面講到)

在這一部分的最後,我們來簡單的介紹下Prisma的使用流程,在正文中,我們會一步步詳細介紹Prisma的使用,包括單表、多表級聯以及Prisma與GraphQL的奇妙化學反應。

環境配置在下一節,這裡我們只是先感受一下使用方式
  • 首先,建立一個名為prisma的資料夾,在內部建立一個schema.prisma檔案
    如果你使用的是VS Code,可以安裝Prisma擴充套件來獲得.prisma的語法高亮

  • 在schema中定義你的資料庫型別、路徑以及你的資料庫表結構,示例如下:
    model Todo { id Int @id @default(autoincrement()) title String }
  • 執行prisma generate命令,prisma將為你生成Prisma Client,內部結構是這樣的:

  • 在你的檔案中匯入Prisma Client即可使用:
    import { PrismaClient } from "./prisma/client";

    const prisma = new PrismaClient();

    async function createTodo(title: string, content?: string) {
    const res = await prisma.todo.create({
    data: {
    title,
    content,
    },
    });
    return res;
    }

    每張表都會被存放在prisma.__YOUR_MODEL__的名稱空間下。

如果看完簡短的介紹你已經感覺這玩意有點好玩了,那麼在跟著本文完成實踐後,你可能也會默默把手上的專案遷移到Prisma(畢設也可以安排上)~

上手Prisma

你可以在Prisma-Article-Example找到完整的示例,以下的例子我們會從一個空資料夾開始。

專案初始化

  • 建立一個空資料夾,執行npm init -y
    yarn、pnpm同理

  • 全域性安裝@prisma/clinpm install prisma -g
    @prisma/cli包已被更名為prisma
    全域性安裝@prisma/cli是為了後面執行相關命令時方便些~

  • 安裝必要的依賴:
    npm install @prisma/client sqlite3 prisma -S
    npm install typescript @types/node nodemon ts-node -D

    安裝prisma到資料夾時會根據你的作業系統下載對應的Query Engine:

  • 執行prisma version,確定安裝成功。

  • 執行prisma init,初始化一個Prisma專案(這個命令的侵入性非常低,只會生成prisma資料夾和.env檔案,如果.env檔案已經存在,則會將需要的環境變數追加到已存在的檔案)。

  • 檢視.env檔案
    # Environment variables declared in this file are automatically made available to Prisma. # See the documentation for more detail:# Prisma supports the native connection string format for PostgreSQL, MySQL and SQLite. # See the documentation for all the connection string options:DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
    你會發現這裡的資料庫預設使用的是postgresql,在本文中為了降低學習成本,我們全部使用SQLite作為資料庫,因此需要將變數值修改為file:../demo.sqlite
    如果你此前沒有接觸過SQLite,可以理解為這是一個能被當作資料庫讀寫的檔案(.sqlite字尾),因此使用起來非常容易,也正是因為它是檔案,所以需要將DATABASE_URL這一變數改為file://協議。

    同樣的,在Prisma Schema中我們也需要修改資料庫型別為sqlite
    // This is your Prisma schema file, // learn more about it in the docs:datasource db { provider = "sqlite" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" }

建立資料庫

在上面的Prisma Schema中,我們只定義了datasource和generator,它們分別負責定義使用的資料庫配置和客戶端生成的配置,舉例來說,預設情況下prisma生成的client會被放置在node_modules下,匯入時的路徑也是import { PrismaClient } from "@prisma/client",但你可以通過client.output命令更改生成的client位置。

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

這一命令會使得client被生成到prisma資料夾下,如:

將client生成到對應的prisma資料夾下這一方式使得在monorepo(或者只是多個資料夾的情況)下,每個專案可以方便的使用不同配置的schema生成的client。

我們在Prisma Schema中新增資料庫表結構的定義:

datasource db {
  provider = "sqlite"
  url      = env("SINGLE_MODEL_DATABASE_URL")
}

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

model Todo {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  finished  Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

簡單解釋下相關語法:

  • Int、String等這一類標量會被自動基於資料庫型別對映到對應的資料型別。標量型別後的?意味著這一欄位是可選的。
  • @id意為標識此欄位為主鍵,@default()意為預設值,autoincrementnow為prisma內建的函式,分別代表自增主鍵與欄位寫入時的時間戳,類似的內建函式還有uuid、cuid等。

客戶端生成與使用

現在你可以生成客戶端了,執行prisma generate

還沒完,我們的資料庫檔案(即sqlite檔案)還沒創建出來,執行prisma db push

這個命令也會執行一次prisma generate,你可以使用--skip-generate跳過這裡的client生成。

現在根目錄下就出現了demo.sqlite檔案。

在根目錄下建立index.ts:

// index.ts
import { PrismaClient } from "./prisma/client";

const prisma = new PrismaClient();

async function main() {
  console.log("Prisma!");
}

main();
從使用方式你也可以看出來PrismaClient實際上是一個類,所以你可以繼承這個類來進行很多擴充套件操作,在後面我們會提到。

在開始使用前,為了後續學習的簡潔,我們使用nodemon+ts-node,來幫助我們在index.ts發生變化時自動重新執行。

{
  "name": "Prisma2-Explore",
  "restartable": "r",
  "delay": "500",
  "ignore": [
    ".git",
    "node_modules/**",
    "/prisma/*",
  ],
  "verbose": true,
  "execMap": {
    "": "node",
    "js": "node --harmony",
    "ts": "ts-node "
  },
  "watch": ["./**/*.ts"],
}

並將啟動指令碼新增到package.json:

{
   "scripts": {
    "start": "nodemon index.ts"
  }
}

執行npm start

Prisma單表初體驗

環境配置

接下來就到了正式使用環節,上面的程式碼只是一個簡單的開發工作流示範,本文接下來的部分不會使用到(但是你可以基於這個工作流自己進一步的探索Prisma)。

在接下來,你所需要的相關環境我已經準備完畢,見Prisma-Article-Example,clone倉庫到本地,執行配置完畢的npm scripts即可。在這裡簡單的介紹下專案中的npm scripts,如果在閱讀完畢本部分內容後覺得意猶未盡,可以使用這些scripts直接執行其他部分如多表、GraphQL相關的示例。簡單介紹部分scripts:

  • yarn flow:從零開始完整的執行生成客戶端 - 構建專案 - 執行構建產物的流程。
  • yarn dev:**:在開發模式下執行專案,檔案變化後重啟程序。
  • yarn generate:**:為專案生成Prisma Client。
    • 使用yarn gen:client來為所有專案生成Prisma Client。

  • yarn setup:**:為構建完畢的專案生成SQLite檔案。
  • yarn invoke:**:執行構建後的JS檔案。
    • 使用yarn setup執行所有構建後的JS檔案。

本部分(Prisma單表示例)的程式碼見single-model,相關的命令包括:

$ yarn dev:single
$ yarn generate:single
$ yarn setup:single
$ yarn invoke:single

在開始下文的CRUD程式碼講解時,最好首先執行起來專案。首先執行yarn generate:single,生成Prisma Client,然後再yarn dev:single,進入開發模式,如下:

我直接一頓CRUD

根據前面已經提到的使用方式,首先引入Prisma Client並例項化:

import { PrismaClient } from "./prisma/client";

const prisma = new PrismaClient();

Prisma將你的表類(Table Class)掛載在prisma.MODEL下,MODEL值直接來自於schema.prisma中的model名稱,如本例是Todo,那麼就可以在prisma.todo下獲取到相關的操作方法:

因此,簡單的CRUD完全可以直接照著API來,

建立:

async function createTodo(title: string, content?: string) {
  const res = await prisma.todo.create({
    data: {
      title,
      content: content ?? null,
    },
  });
  return res;
}

create方法接受兩個引數:

  • data,即你要用來建立新資料的屬性,型別定義由你的schema決定,如這裡content在schema中是可選的字串(String?),其型別就為string|null,所以需要使用??語法來照顧引數未傳入的情況。
  • select,決定create方法返回的物件中的欄位,如果你指定select.id為false,那麼create方法的返回值物件中就不會包含id這一屬性。這一引數在大部分prisma方法中都包含。

讀取:

async function getTodoById(id: number) {
  const res = await prisma.todo.findUnique({
    where: { id },
  });
  return res;
}

findUnique方法類似於TypeORM中的findOne方法,都是基於主鍵查詢,在這裡將查詢條件傳入給where引數。

讀取所有:

async function getTodos(status?: boolean) {
  const res = await prisma.todo.findMany({
    orderBy: [{ id: "desc" }],
    where: status
      ? {
          finished: status,
        }
      : {},
    select: {
      id: true,
      title: true,
      content: true,
      createdAt: true,
    },
  });
  return res;
}

在這裡我們額外傳入了orderBy方法來對返回的查詢結果進行排序,既然有了排序,當然也少不了分頁。你還可以傳入cursorskiptake等引數來完成分頁操作。

cursor-based 與 offset-based 實際上是兩種不同的分頁方式。

類似的,更新操作:

async function updateTodo(
  id: number,
  title?: string,
  content?: string,
  finished?: boolean
) {
  const origin = await prisma.todo.findUnique({
    where: { id },
  });

  if (!origin) {
    throw new Error("Item Inexist!");
  }

  const res = await prisma.todo.update({
    where: {
      id,
    },
    data: {
      title: title ?? origin.title,
      content: content ?? origin.content,
      finished: finished ?? origin.finished,
    },
  });
  return res;
}

這裡執行的是在未查詢到主鍵對應的資料實體時丟擲錯誤,你也可以使用upsert方法來在資料實體不存在時執行建立。

批量更新:

async function convertStatus(status: boolean) {
  const res = await prisma.todo.updateMany({
    where: {
      finished: !status,
    },
    data: {
      finished: {
        set: status,
      },
    },
  });

  return res;
}

注意,這裡我們使用set屬性,來直接設定finished的值。這一方式和直接設定其為false是效果一致的,如果這裡是個number型別,那麼除了set以外,還可以使用increment、decrement、multiply以及divide方法。

最後是刪除操作:

async function deleteTodo(id: number) {
  const res = await prisma.todo.delete({
    where: { id },
  });
  return res;
}

async function clear() {
  const res = await prisma.todo.deleteMany();
  return res;
}

你可以自由的在以上這些例子以外,藉助良好的TS型別提示花式探索Prisma的API,也可以提前看看其它部分的例子來早一步感受Prisma的強大能力。

尾聲 & 下篇預告

以上使用到的Prisma方法(如create)與操作符(如set)只是一小部分,目的只是為了讓你大致感受下Prisma與其他傳統ORM相比新奇的使用方式。在下篇中,我們將會介紹:

  • Prisma多張資料表的級聯關係處理
  • 多個Prisma Client協作
  • Prisma與其他ORM的協作
    • 和上一項一樣都屬於

  • Prisma + GraphQL 全流程實戰
  • Prisma的展望:工作原理、一體化框架

敬請期待~

漫思