7

Node ORM 框架 Prisma 快速上手

 2 years ago
source link: https://blog.dteam.top/posts/2022-01/prisma-introduction.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Prisma 是一个 开源 的下一代 ORM。它包含了以下部分:

  • Prisma Client: 自动生成、类型安全的查询构建器,用于 Node.js 和 TypeScript
  • Prisma Migrate: 数据迁移系统
  • Prisma Studio: 查询和编辑数据库中数据的图形化界面

Prisma 客户端可以被用在 任何 Node.js 或 TypeScript 后端应用中(包括 Serverless 应用和微服务)。可以是一个 REST API,一个 GraphQL API,一个 gRPC API,或任何其他需要数据库的东西。

本文将快速介绍 Prisma 基本使用方法。

官方 Quickstart 示例

直接访问 官方示例

创建工程模板

创建 typescript 工程,使用 gts:

mkdir -p hello-prisma
cd hello-prisma
npx gts init -y
npm install -D @vercel/ncc

安装 prisma 依赖:

npm install -D prisma

稍微调整下tsconfig.json的内容如下(gts 使用的module配置为commonjs,主要这个需要调整下):

{
  "extends": "./node_modules/gts/tsconfig-google.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "module": "ES2020",
    "target": "ES2020",
    "lib": ["ES2020"],
    "esModuleInterop": true,
    "moduleResolution": "node"
  },
  "include": ["src/**/*.ts", "test/**/*.ts"]
}

初始化 prisma 模板:

$ npx prisma init

✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.

Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver or mongodb (Preview).
3. Run prisma db pull to turn your database schema into a Prisma schema.
4. Run prisma generate to generate the Prisma Client. You can then start querying your database.

More information in our documentation:
https://pris.ly/d/getting-started

prisma cli 已经帮我们生成了一个 schema 模板 prisma/schema.prisma:

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

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

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

以及一个dotenv配置文件.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#using-environment-variables

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

DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

基本工作流程

调整数据库连接方式,在这个基础上追加表结构定义就可以了。比如我们定义一个 user 表:

model User {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  email     String   @unique
  name      String?
}

model 命名需要满足大写驼峰式,并且不能使用保留字,参考官方文档 prisma-schema-reference#naming-conventions

运行 npx prisma migrate dev --name init 即可创建数据库并生成 migration 脚本:

-- CreateTable
CREATE TABLE "User" (
    "id" SERIAL NOT NULL,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "email" TEXT NOT NULL,
    "name" TEXT,

    CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

本地进行快速原型设计还可以用 db push 直接修改数据库,而不创建 migrate 脚本,这在本地快速迭代,不关心中间的变更场景下很有用。

但在产品环境期望不丢失数据而安全迁移数据库的场景下,应该用 migrate deploy 而非 db push。有关 db push 的描述以及与 migrate 的对比参见官方文档: db-push

修改 schema ,增加一个字段:

model User {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  email     String   @unique
  name      String?
  // 增加一个 foo 字段,定义为 VARCHAR 类型
  foo       String?  @db.VarChar(10)
}

运行 npx prisma migrate dev --name add_foo_column,生成的 sql 如下:

-- AlterTable
ALTER TABLE "User" ADD COLUMN     "foo" VARCHAR(10);

以后修改 prisma/schema.prisma 之后再次运行 npx prisma migrate dev 即可将改动同步到数据库中。如果追加 --create-only 参数,则只生成 migration.sql 文件,不会同步到数据库中。

开发环境生成 migrate 脚本(主要是 migrate devmigrate reset 命令需要用 shadow database)需要有数据库的 createdb 权限,否则会报错。如果你的环境无法提供 createdb 权限,你需要单独创建一个 shadow database,在 datasource db {} 配置块中额外添加 shadowDatabaseUrl = env('SHADOW_DATABASE_URL') 指定,参考官方文档: shadow-database

产品环境直接用 migrate deploymigrate resolve 运行 migrate 脚本,无需创建 shadow database。

Schema 定义

注意 Prisma 维护了一组默认的 schema 到数据库类型的映射,如果不满足你的需求,可以追加 @db.<database_type> 参数变更,如 String 类型默认映射为数据库的 TEXT 类型,可能你希望映射成 VARCHAR 类型:

model User {
  userName String @db.VarChar(10)
}

prisma schema 所有支持的字段类型以及可以映射的数据库类型(@db.<database_type>)可以参考官方文档: model-field-scalar-types

其他常用的 schema 定义

prisma 支持两种注释 /////,前者只是代码普通注释,后者会出现在节点语法树中(AST),以 Data Model Meta Format (DMMF)形式呈现:

/// This comment will get attached to the `User` node in the AST
model User {
  /// This comment will get attached to the `id` node in the AST
  id     Int   @default(autoincrement())
  // This comment is just for you
  weight Float /// This comment gets attached to the `weight` node
}

// This comment is just for you. It will not
// show up in the AST.

/// This comment will get attached to the
/// Customer node.
model Customer {}

当前 prisma 还不支持数据库级别的表和字段注释(DDL COMMENT),参见官方issue

目前只能通过 --create-only 参数生成 sql 之后手改

表/字段重命名

为了规避数据库的大小写问题,一般数据库最佳实践都是表和字段统一使用小写下划线形式命名。对于 prisma 需要使用 @@map@map 函数定义:

model User {
  id     Int @id
  // @map 用于定义列名
  cardId Int @map("card_id")

  // @@map 用于定义表名
  @@map("user")
}

对应生成的 sql 如下:

-- CreateTable
CREATE TABLE "user" (
    "id" INTEGER NOT NULL,
    "card_id" INTEGER NOT NULL,

    CONSTRAINT "user_pkey" PRIMARY KEY ("id")
);

如果要对索引、约束等命名的话参考官方文档: names-in-underlying-databas#using-custom-constraint–index-names

字段默认值(@default)

@default 可以定义字段的默认值,参数可以是静态的固定值,如5, false等等,也可以是 prisma 提供的几种函数,如autoincrement(), uuid(), now() 等等,参见文档: prisma-schema-reference#default

model User {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
}

可以定义字段为枚举类型:

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  role  Role    @default(USER)
}

enum Role {
  USER
  ADMIN
}

对应 sql:

-- CreateEnum
CREATE TYPE "Role" AS ENUM ('USER', 'ADMIN');

-- CreateTable
CREATE TABLE "User" (
    "id" SERIAL NOT NULL,
    "email" TEXT NOT NULL,
    "name" TEXT,
    "role" "Role" NOT NULL DEFAULT E'USER',

    CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

自动存储更新时间(@updatedAt)

这个属性可以自动设置更新时间:

model Post {
  id        String   @id
  createdAt DateTime @default(now()) @db.Timestamptz
  updatedAt DateTime @updatedAt @db.Timestamptz
}

索引(@@index)

直接使用 @@index 函数可以创建索引:

model Post {
  id      Int     @id @default(autoincrement())
  title   String
  content String?

  @@index([title, content])
}

如果要自定义索引类型,可以用 npx prisma migrate dev --create-only 生成 migration.sql 文件,然后在 migration.sql 中修改。

如果期望在 schema 文件中定义,则需要启用 extendedIndexes preview feature:

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["extendedIndexes"]
}

就可以在 @@index 中使用 type 参数指定索引类型了:

model Example {
  id    Int @id
  value Int

  @@index([value], type: Hash)
}

有关 @@index 部分的文档参考: prisma-schema-reference#index

extendedIndexes 部分的文档参考: indexes

主键(@id / @@id)

model Table {
  id Int @id @default(autoincrement())
}

生成 sql:

-- CreateTable
CREATE TABLE "Table" (
    "id" SERIAL NOT NULL,

    CONSTRAINT "Table_pkey" PRIMARY KEY ("id")
);
model Table {
  firstName String @db.VarChar(10)
  lastName  String @db.VarChar(10)

  @@id([firstName, lastName])
}

生成 sql:

-- CreateTable
CREATE TABLE "Table" (
    "firstName" VARCHAR(10) NOT NULL,
    "lastName" VARCHAR(10) NOT NULL,

    CONSTRAINT "Table_pkey" PRIMARY KEY ("firstName","lastName")
);

唯一性(@unique / @@unique)

单列唯一性

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

多列唯一性

model User {
  id        Int  @id
  firstname Int
  lastname  Int
  card      Int?

  @@unique([firstname, lastname, card])
}

默认字段类型都是非空的,可空给字段类型加上 ? 就行了:

model User {
  id        Int  @id
  firstname Int
  lastname  Int
  card      Int?
}

外键约束(@relation)

稍微麻烦些,需要同时在两个 model 定义互相的对应关系,如下所示:

model User {
  id        Int    @id
  firstname String @db.VarChar(10)
  lastname  String @db.VarChar(10)
  Post      Post[]
}

model Post {
  id      Int    @id
  author  User   @relation(fields: [userId], references: [id])
  content String @db.VarChar(200)
  userId  Int
}

生成 sql:

-- CreateTable
CREATE TABLE "User" (
    "id" INTEGER NOT NULL,
    "firstname" VARCHAR(10) NOT NULL,
    "lastname" VARCHAR(10) NOT NULL,

    CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Post" (
    "id" INTEGER NOT NULL,
    "content" VARCHAR(200) NOT NULL,
    "userId" INTEGER NOT NULL,

    CONSTRAINT "Post_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "Post" ADD CONSTRAINT "Post_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

这里有个比较方便的方法可以快速创建出这种映射关系,就是直接利用 prisma format 的功能即可。

比如先像 Hibernate 那样创建表关联:

model User {
  id        Int    @id
  firstname String @db.VarChar(10)
  lastname  String @db.VarChar(10)
}

model Post {
  id      Int    @id
  author  User
  content String @db.VarChar(200)
}

然后在命令行下执行 npx prisma format,或者在 vscode 安装插件Prisma后直接在 vscode 格式化即可自动生成上述 model 映射关系。

更多映射关系(一对一,一对多,多对多)的定义可以参考官方文档: relations

使用 Prisma Client

定义好 schema,完成 migrate 之后,需要通过 prisma-client 调用 schema 完成数据库的操作。

npm install @prisma/client

第一次安装 @prisma/client 之后,会自动调用 prisma generate 生成定制化的 client,以后每次修改了 schema 之后,需要手工调用 npx prisma generate 生成新的 client model。

在项目中引入 PrismaClient 组件即可:

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

const prisma = new PrismaClient();

想要打印 sql 可以这么配置:

const prisma = new PrismaClient({
  log: ["query", "info", "warn", "error"],
});

PrismaClient 默认使用连接池,关于连接池的配置可以参考官方文档: connection-pool

基本 CURD 操作

有关详细的基本 CURD 操作直接参考官方文档完整的示例: crud

都比较简单,一目了然。基本的基于 model 的 CURD 函数主要有下面几个:

  • findUnique
  • findFirst
  • findMany
  • create
  • update
  • upsert
  • delete
  • createMany
  • updateMany
  • deleteMany
  • count
  • aggregate
  • groupBy

此外,还可以通过 select 属性控制返回的字段:

// Returns an object or null
const getUser: object | null = await prisma.user.findUnique({
  where: {
    id: 22,
  },
  select: {
    email: true,
    name: true,
  },
});

更多详情参见: select-fields

当前 prisma 还不支持 exclude 排除特定的字段(如 password),官方正在对这个设计进行探讨中,参见 issue: prisma/prisma#5042

如果用到类似于 join 之类的关联查询,可以参考文档: relation-queries

对于 JSONB 相关的查询,prisma API 也有支持: working-with-json-fields

每个基本的 CURD 操作都会在单独的事务中运行(打印 sql 的时候可以看到包装有BEGIN...COMMIT),此外,还可以明确使用 $transaction 函数包装多个操作在一个事务中:

const [posts, totalPosts] = await prisma.$transaction([
  prisma.post.findMany({ where: { title: { contains: "prisma" } } }),
  prisma.post.count(),
]);

调用 sql

prisma 提供了一些包含 Raw 关键字的函数,可以直接使用 sql,如:

const result = await prisma.$queryRaw`SELECT * FROM User`;
const result: number =
  await prisma.$executeRaw`UPDATE User SET active = true WHERE emailValidated = true`;

所有直接调用 sql 的函数可以参阅官方文档: raw-database-access


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK