面向Node.js和TypeScript的下一代ORM工具Prisma

准备

数据库准备

数据库可以通过docker跑一个服务,但是目前市场上也有好几个能提供免费的PostgreSQL 服务云厂商,有如下几个

Supabase是一款开源的后端即服务(Backend-as-a-Service)平台,它提供了类似于Firebase的功能,包括实时数据同步、身份验证和授权,以及通过SQL查询API访问PostgreSQL数据库。Supabase免费计划可以创建两个应用,并且PostgreSQL数据库每个月提供1GB的存储空间和50GB的传输量,个人开发应该绰绰有余,这里我采用Supabase ,首先注册个账号,新建个应用

https://cos.codefe.top/images/prisma_supabase_new.png

创建应用后,在设置选项中查看连接选项, 可以看到在这个数据库URI地址

https://cos.codefe.top/images/supabase_postgre_uri.png

Supabase本身就提供了数据库相关操作的功能,可以直接通过快捷的UI交互,创建表格和字段,执行查询语句,查看表数据等,但这里我们重点是使用它来学习 Prisma这一新一代ORM工具

初始化项目

Prisma主要由三部分组成:

  • Prisma Client: 为Node.jsTypeScript自动生成和类型安全的查询生成器
  • Prisma Migrate: 迁移工具,可以轻松地将数据库模式从原型设计应用到生产
  • Prisma Studio: 用于查看和编辑数据库中数据的GUI

可用于各种工具和框架, 以Nextjs使用举例,你可以决定在构建时(getStaticProps)、请求时(getServerSideProps)、使用 API 路由或将后端完全分离成独立的服务器来访问您的数据库,如下分别对应四种场景

https://cos.codefe.top/images/prisma_nextjs_getStaticProps.png

https://cos.codefe.top/images/prisma_nextjs_getserverside.png

https://cos.codefe.top/images/prisma_nextjs_apiroute.png

https://cos.codefe.top/images/prisma_nextjs_standalone.png

首先通过命令创建一个Nextjs项目

npx create-next-app@latest

安装Prisma

npm install prisma

初始化prisma设置

npx prisma init

执行完后,多了个配置文件prisma/schema.prisma.env文件,内容如下

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

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

其中datasource代表数据源

provider默认就是postgresql无需修改,其根据不同的数据库还支持mysqlsqlitesqlservermongodb

url 字段指连接 URL。通常由以下部分组成(SQLite 除外):

  • User: 数据库用户名
  • Password: 数据库用户密码
  • Host: 数据库服务器的 ip 或者域名
  • Port: 数据库服务器的端口
  • Database name: 数据库名称
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

根据提示,我们将env中的数据库地址修改成我们的地址

https://cos.codefe.top/images/prisma_init.png

Prisma Schema

Prisma schema 的数据模型定义部分定义了你的应用模型(也称为 Prisma 模型)。模型:

  • 构成了应用领域的 实体
  • 映射到数据库的 (关系型数据库,例如 PostgreSQL)或 集合 (MongoDB)
  • 构成 Prisma Client API 中 查询 的基础

假设我们有以下模型

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

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String
  user   User   @relation(fields: [userId], references: [id])
  userId Int
}

model Post {
  id         Int        @id @default(autoincrement())
  createdAt  DateTime   @default(now())
  title      String
  published  Boolean    @default(false)
  author     User       @relation(fields: [authorId], references: [id])
  authorId   Int
  categories Category[] @relation(references: [id])
}

model Category {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[] @relation(references: [id])
}

enum Role {
  USER
  ADMIN
} 

其反映的的数据库如下所示:

https://prisma.yoga/static/fc0ed56f4213ebf4d2309c0965b166da/a6d36/sample-database.png

模型会映射到数据源的底层结构

  • PostgreSQLMySQL 等关系型数据库中,model 映射至 
  • MongoDB 中,model 映射至 集合

**model  支持的全部字段标量类型可以参考文档, 值得注意的是,上面User中的postsprofile是关系字段,分别代表一个用户有多个posts和一个用户有一个关联的profile, 这另个字段属于关系字段,关系字段在 Prisma 层定义模型间的联系,且 不存在于数据库中。这些字段用于生成 Prisma Client , 如果没有这个关系字段,Prisma插件会有警告

https://cos.codefe.top/images/prisma_format_error.png

这时候可以通过执行以下命令来自动补全

npx prisma format

Prisma Migrate

Prisma Migrate 是一个命令式数据库架构迁移工具,它使您能够:

  • 保持数据库架构与Prisma 架构的同步
  • 维护数据库中的现有数据

Prisma Migrate 会生成一个.sql迁移文件的历史记录,能在开发或和部署中发挥作用。

<aside>
💡 不支持 MongoDB

Prisma Migrate 目前不支持 MongoDB connector

</aside>

https://cos.codefe.top/images/prisma_schema.png

数据模型中的每项功能都会映射成基础数据库中的相应功能,创建Prisma模型

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

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  published Boolean @default(true)
  authorId  Int
  author    User    @relation(fields: [authorId], references: [id])
}

创建第一个迁移:

npx prisma migrate dev --name init

执行完后目录下多了一个文件

migrations/
  └─ 20230420131352_init/
    └─ migration.sql

对应内容如下

-- CreateTable
CREATE TABLE "User" (
    "id" SERIAL NOT NULL,
    "name" TEXT NOT NULL,

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

-- CreateTable
CREATE TABLE "Post" (
    "id" SERIAL NOT NULL,
    "title" TEXT NOT NULL,
    "published" BOOLEAN NOT NULL DEFAULT true,
    "authorId" INTEGER NOT NULL,

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

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

同时我们的数据库的表也被创建出来

https://cos.codefe.top/images/prisma_supabse_tabledata_create.png

假如我们再次新增一个性别字段gender

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

创建第二次迁移

npx prisma migrate dev --name add_gender

Prisma架构再次与数据库架构同步,迁移历史记录里包含了两次迁移

migrations/
  └─ 20230420131352_init/
    └─ migration.sql
  └─ 20230420132624_add_gender/
    └─ migration.sql
-- AlterTable
ALTER TABLE "User" ADD COLUMN  "gender" INTEGER NOT NULL;

db push

[db push](https://prisma.yoga/reference/api-reference/command-reference/#db-push)  使用与 Prisma Migrate 相关的引擎来同步 Prisma 架构和数据库架构,最适合于架构原型db push 命令:

  1. 检查数据库,以推断和执行所需的更改,使数据库架构反映 Prisma 架构的状态。
  2. 默认情况下,将更改应用到数据库架构后会触发生成器 (例如,Prisma Client)。您不需要手动调用Prisma生成
  3. 如果 db push 预测到更改可能导致数据丢失,它会:

    • 抛出一个错误
    • 如果仍要进行更改,则需要加上-accept-data-loss选项

<aside>
💡 请注意: db push 不与迁移交互或依赖迁移。不会更新迁移表,也不会生成迁移文件。

</aside>

在开发中,我们可能也不确定需要哪些字段,字段哪个类型合适,因此会频繁修改数据库结构,会持续迭代 schema,当迭代到满足需求直到它达到一个相对稳定的状态时,这时我们才生成一个迁移文件,到达初始原型的步骤没有被保留也不需要保留, 该命令常用于数据库原型设计阶段。

加入我们在User中再添加一个头像avatar字段,并执行

npx prisma db push

这是远程数据库表结构已经更新,再次生成迁移记录时

npx prisma migrate dev --name added-avatar

会打印警告提示

https://cos.codefe.top/images/prisma_migrate_warning.png

因为您在原型设计期间手动进行的更改和使用 db push 进行的更改不属于迁移历史记录, 会检测到已有的迁移和数据库目前状态对应不起来。Prisma Migrate 会复制现有的迁移历史,根据 schema 变化生成一个新的迁移,并将这些变化应用到数据库。

Prisma Studio

Prisma Studio 是数据库中数据的可视化编辑器

npx prisma studio

执行上面命令会打开http://localhost:5555页面

https://cos.codefe.top/images/prisma_studio_ui.png

https://cos.codefe.top/images/prisma_studio_demo.png

在页面可以查看项目的所有 model并用一种很方便的交互方式来操作数据库

Prisma Client

Prisma Client 是一个自动生成的类型安全查询构造器,可根据你的数据进行定制。

生成 **PrismaClient 实例

Prisma Client 是一个跟据你的数据库 schema 自动生成的定制化数据库客户端 client。默认情况下, Prisma Client 被生成到 node_modules/.prisma/client 文件夹下

  1. 安装 @prisma/clientnpm 包:

    npm install @prisma/client
  2. 使用以下命令生成 Prisma Client:

    prisma generate

<aside>
💡 重要提示
每次对 Prisma schema 进行更改后,你都需要重新运行命令 prisma generate去更新生成的 Prisma Client 代码。

</aside>

例如我们的schema

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

修改为

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

再次运行prisma generate命令后, 类型也得到更新,提示需要进行修改

https://cos.codefe.top/images/prisma_format_error.png

**创建 PrismaClient 实例

以下示例演示了如何使用 默认输出路径 导入并创建你 生成的 PrismaClient 实例:

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

你的应用程序通常只应当创建 一个PrismaClient 实例。如何实现这一点取决于你是在 长期运行的应用程序 还是在 无主机 (serverless) 环境中使用 Prisma。这样做的原因是每个 PrismaClient
 实例会维护一个连接池,意味着大量数据库连接会 耗尽数据库连接数限制, 太多的数据库连接可能会 使你的数据库变慢并最终导致错误,这适用于所有数据库连接器 。

在长期运行的应用程序中的PrismaClient

长期运行的应用程序中,建议:

  • ✔ 创建一个PrismaClient实例,并在整个应用程序中重复使用它

    默认情况下适用默认的池大小(num_physical_cpus * 2 + 1)- 你不需要设置connection_limit参数

  • ✔ 将 PrismaClient 分配给一个全局变量仅在开发环境中防止因创建新实例而产生热重载

该对象在模块第一次被导入时被缓存。随后的请求会返回缓存的对象,而不是创建一个新的PrismaClient:

import { PrismaClient } from '@prisma/client'
let prisma = new PrismaClient()
export default prisma

防止热重载创建PrismaClient的新实例

Next.js这样的框架支持热重载改变的文件,这使你能够在不重启的情况下看到你的应用程序的变化。然而,如果框架刷新了负责导出PrismaClient的模块,这可能会导致开发环境中出现额外的、不需要的PrismaClient实例

作为一个变通方法,你可以在开发环境中把PrismaClient作为一个全局变量来存储,因为全局变量不会被重新加载:

import { PrismaClient } from '@prisma/client'

// add prisma to the NodeJS global type
interface CustomNodeJsGlobal extends NodeJS.Global {
  prisma: PrismaClient
}

// Prevent multiple instances of Prisma Client in development
declare const global: CustomNodeJsGlobal

const prisma = global.prisma || new PrismaClient()

if (process.env.NODE_ENV === 'development') global.prisma = prisma

export default prisma

连接和断开

PrismaClient 使用以下两种方法连接和断开数据源:

  • [$connect()](https://prisma.yoga/reference/api-reference/prisma-client-reference/#connect-1)
  • [$disconnect()](https://prisma.yoga/reference/api-reference/prisma-client-reference/#disconnect-1)

在大多数情况下,你 不需要显式调用这些方法PrismaClient 会在你运行第一条查询时自动连接数据库,并创建一个 连接池,然后在 Node.js 进程结束时断开连接。

CRUD 增删改查

接下来介绍如何使用生成的 Prisma Client API 执行 CRUD 操作

所有示例均基于以下 schema:

  • 展开示例 schema

    model ExtendedProfile {
      id        Int    @id @default(autoincrement())
      biography String
      user      User   @relation(fields: [userId], references: [id])
      userId    Int
    }
    
    model User {
      id           Int              @id @default(autoincrement())
      name         String?
      email        String           @unique
      profileViews Int              @default(0)
      role         Role             @default(USER)
      coinflips    Boolean[]
      posts        Post[]
      profile      ExtendedProfile?
    }
    
    model Post {
      id         Int        @id @default(autoincrement())
      title      String
      published  Boolean    @default(true)
      author     User       @relation(fields: [authorId], references: [id])
      authorId   Int
      comments   Json?
      views      Int        @default(0)
      likes      Int        @default(0)
      categories Category[]
    }
    
    model Category {
      id    Int    @id @default(autoincrement())
      name  String @unique
      posts Post[]
    }
    
    enum Role {
      USER
      ADMIN
    }

创建

创建单个

以下查询创建([create](https://prisma.yoga/reference/api-reference/prisma-client-reference/#create) )具有两个字段的单个用户:

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

创建多个记录

Prisma Client 在 2.20.0 和更高版本中支持将大量插入作为 基础可用 功能。

以下 [createMany](https://prisma.yoga/reference/api-reference/prisma-client-reference/#createmany)  查询创建多个用户并跳过任何重复项 (email 必须是唯一的):

const createMany = await prisma.user.createMany({
  data: [
    { name: 'Bob', email: 'bob@prisma.io' },
    { name: 'Bobo', email: 'bob@prisma.io' }, // 唯一键重复!
    { name: 'Yewande', email: 'yewande@prisma.io' },
    { name: 'Angelique', email: 'angelique@prisma.io' },
  ],
  skipDuplicates: true, // 跳过 'Bobo'
})

读取

按 ID 或唯一标识符获取记录

以下查询按唯一标识符或 ID 返回单个记录 ([findUnique](https://prisma.yoga/reference/api-reference/prisma-client-reference/#findunique) )

// 按唯一标识符
const user = await prisma.user.findUnique({
  where: {
    email: 'elsa@prisma.io',
  },
})

// 按 ID
const user = await prisma.user.findUnique({
  where: {
    id: 99,
  },
})

获取所有记录

以下 [findMany](https://prisma.yoga/reference/api-reference/prisma-client-reference/#findmany)  查询返回 所有 User 记录:

const users = await prisma.user.findMany()

你也可以 对你的结果进行分页

获取与特定条件匹配的第一条记录

以下 [findFirst](https://prisma.yoga/reference/api-reference/prisma-client-reference/#findfirst)  查询返回 至少有一个帖子有超过 100 个赞的 最新创建的用户

  1. 按升序 ID 排序用户(最大的优先)- 最大的 ID 是最近的 ID
  2. 以升序返回第一个用户,其中至少有一个帖子有 100 个以上的赞
  const findUser = await prisma.user.findFirst({
    where: {
      posts: {
        some: {
          likes: {
            gt: 100
          }
        }
      }
    },
    orderBy: {
      id: "asc"
    }
  })
}

获取经过过滤的记录列表

Prisma Client 支持记录字段和相关记录字段的 过滤

按单个字段值过滤

下面的查询返回所有 User 记录,其中包含以 "prisma.io" 结尾的电子邮件:

  const users = await prisma.user.findMany({
    where: {
      email: {
        endsWith: "prisma.io"
      }
    },
  }

按多个字段值过滤

以下查询使用 operators  组合返回名称以 E 开头的用户  至少具有 1 个 profile 的管理员:

const users = await prisma.user.findMany({
  where: {
    OR: [
      {
        name: {
          startsWith: 'E',
        },
      },
      {
        AND: {
          profileViews: {
            gt: 0,
          },
          role: {
            equals: 'ADMIN',
          },
        },
      },
    ],
  },
})

按相关记录字段值过滤

以下查询返回的用户电子邮件以 prisma.io 结尾,并且 至少有  篇(some)为未发布的帖子:

  const users = await prisma.user.findMany({
    where: {
      email: {
        endsWith: "prisma.io"
      },
      posts: {
        some: {
          published: false
        }
      }
    },
  }

有关过滤相关字段值的更多示例,请参见 使用关系

选择字段

默认情况下,当查询返回记录(与计数相反)时,结果包括 默认选择集

  • Prisma schema 中定义的 所有 标量字段(包括枚举)
  •  关系

要自定义结果,请执行以下操作:

可以仅选择所需的字段和关系,而不依赖默认选择集 ✔ 减小响应的大小并 ✔ 提高查询速度。

以下示例仅返回 email 和 name 字段:

选择特定字段

使用 select 返回有限的字段子集,而不是所有字段。以下示例仅返回 email 和 name 字段:

// 返回一个对象或 null
const getUser: object | null = await prisma.user.findUnique({
  where: {
    id: 22,
  },
  select: {
    email: true,
    name: true,
  },
})

包括关系并选择关系字段

要返回 特定关系字段,你可以:

  • 使用嵌套的 select -在 include 中使用 select
要返回 all 关系字段,请仅使用 include - 例如 { include: { posts: true } }。

以下查询使用嵌套的 select 来选择每个用户的 name 和每个相关帖子的 title

const users = await prisma.user.findMany({
  select: {
    name: true,
    posts: {
      select: {
        title: true,
      },
    },
  },
})

Show CLI results

以下查询在 include 中使用 select,并返回 所有 用户字段和每篇文章的 title 字段:

const users = await prisma.user.findMany({
  // 返回所有用户字段
  include: {
    posts: {
      select: {
        title: true,
      },
    },
  },
})

Show CLI results

有关查询关系的更多信息,请参阅以下文档:

分页

Prisma Client 支持偏移分页和基于游标的分页。

偏移分页

偏移分页使用 skip 和 take 跳过一定数量的结果并选择有限的范围。以下查询跳过前 3 个 Post 记录并返回记录 4 - 7:

const results = await prisma.post.findMany({
  skip: 3,
  take: 4,
})

https://prisma.yoga/static/8cbb3ea8b5fc961b73cd2e583313f3a9/a6d36/offset-skip-take.png

要实现结果页面,只需 skip 页面数乘以每页显示的结果数即可。

基于游标的分页

基于游标的分页使用 cursor and take 在给定 游标 之前或之后返回一组有限的结果。游标在结果集中为你的位置添加书签,并且必须是唯一的连续列,例如 ID 或时间戳。

以下示例返回包含单词 "Prisma" 的前 4 条 Post 记录,并将最后一条记录的 ID 保存为 myCursor

注意:由于这是第一个查询,因此没有要传递游标。
const firstQueryResults = prisma.post.findMany({
  take: 4,
  where: {
    title: {
      contains: 'Prisma'/* 可选过滤器 */,
    },
  },
  orderBy: {
    id: 'asc',
  },
})
// 在结果集中为你的位置添加书签 - 在此
// 案例,列表 4 中最后一篇文章的 ID。
const lastPostInResults = firstQueryResults[3]// 记住:从零开始的索引!:)
const myCursor = lastPostInResults.id// 示例: 29

下图显示了前 4 个结果 - 或第 1 页的 ID。下一个查询的游标为 29

https://prisma.yoga/static/2fd4a37fa5e3775c6510ff17279d6b6c/a6d36/cursor-1.png

第二个查询返回前 4 个 Post 记录,这些记录包含单词 "Prisma" 在提供的游标之后(换句话说,大于 29 的 ID):

const secondQueryResults = prisma.post.findMany({
  take: 4,
  skip: 1,// 跳过游标
  cursor: {
    id: myCursor,
  },
  where: {
    title: {
      contains: 'Prisma'/* 可选过滤器 */,
    },
  },
  orderBy: {
    id: 'asc',
  },
})
const lastPostInResults = secondQueryResults[3]// 记住:从零开始的索引!:)
const myCursor = lastPostInResults.id// 示例: 52

下图显示了 ID 为 29 的记录 之后 的前 4 条 Post 记录。在本例中,新游标为 52

https://prisma.yoga/static/1c59bfa7c9f0e25127a67d6e962a3839/a6d36/cursor-2.png

嵌套写入

嵌套写入 允许你使用多个 操作 执行单个 Prisma Client API 调用,这些操作涉及多个 相关的 记录。例如,创建 用户 和 post 或更新 订单 和 发票Prisma Client 确保所有操作作为一个整体成功或失败。

下面的示例演示了带有 create 的嵌套写入:

// 在一个事务中创建一个具有两个帖子的新用户
const newUser: User = await prisma.user.create({
  data: {
    email: 'alice@prisma.io',
    posts: {
      create: [
        { title: 'Join the Prisma Slack on https://slack.prisma.io' },
        { title: 'Follow @prisma on Twitter' },
      ],
    },
  },
})

以下示例演示了带有 update 的嵌套写入:

// 在单个事务中更改帖子的作者
const updatedPost: Post = await prisma.post.update({
  where: { id: 42 },
  data: {
    author: {
      connect: { email: 'alice@prisma.io' },
    },
  },
})

内省

将 Prisma 添加到现有项目时,内省通常用于生成数据模型的 初始 版本。

内省能做什么?

内省有一个主要功能: 反映当前数据库架构的数据模型来填充您的 Prisma 架构。

https://res.cloudinary.com/prismaio/image/upload/v1628761155/docs/f7itiYw.png

以下是它的主要功能概述:

  • 将数据库中的  映射到Prisma models
  • 将数据库中的  映射到 Prisma 模型的字段
  • 将数据库中的 indexes 映射到 Prisma 架构中的indexes
  • 将 database constraints 映射到 Prisma 架构中的attributestype modifiers

prisma db pull 命令

您可以使用 Prisma CLI 的 prisma db pull 命令内省您的数据库。请注意,使用此命令需要在您的 Prisma 架构 datource中设置连接 URL

内省的工作流

不使用 Prisma Migrate,而是使用纯 SQL 或其他迁移工具的项目的典型工作流如下:

  1. 更改数据库架构 (例如使用纯 SQL)
  2. 运行 prisma db pull 更新 Prisma 架构
  3. 运行 prisma generate Prisma Client
  4. 在应用程序中使用更新的 Prisma Client

请注意,随着应用程序的发展,此过程可以重复无限次。

https://res.cloudinary.com/prismaio/image/upload/v1628761155/docs/ToNkpb2.png

我们可以执行以下命令来生成您的 Prisma 模型:

npx prisma db pull

创建了以下 Prisma 模式:

model category {
  id     Int           @id @default(autoincrement())
  name   String
  post_categories_category post_categories_category[]
}
model profile {
  id     Int     @id @default(autoincrement())
  bio    String?
  userId Int?    @unique
  user   user?   @relation(fields: [userId], references: [id])
}

model user {
  id      Int      @id @default(autoincrement())
  name    String?
  email   String   @unique
  post    post[]
  profile profile?
}

调整 Prisma schema(可选)

所有调整都是可选的,不想调整任何内容,可以跳到下一步。 后续随时返回调整。

Prisma 的命名与当前 snake_case 的命名约定不同:

  • 模型(Prisma model)命名遵守 PascalCase
  • 字段(field)命名遵守 camelCase

可以使用 @@map 和 @map 映射的模型和字段名称到已有的数据表和列名来调整命名。

另请注意,可以重命名 关联字段 以优化稍后将用于向数据库发送查询的 Prisma Client API。 例如,user 模型上的 post 字段是数组格式,因此这个字段的更好名称是 posts 以表明它是复数形式。

model Category {
  id                Int                @id @default(autoincrement())
  name              String
  postsToCategories PostToCategories[]

  @@map("category")
}
model Profile {
  id     Int     @id @default(autoincrement())
  bio    String?
  userId Int?    @unique
  user   User?   @relation(fields: [userId], references: [id])

  @@map("profile")
}

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

  @@map("user")
}

总结

最后我们以问答的形式来总结一下prisma

  • Prisma是什么?它的作用是什么?

    Prisma是一款现代化的ORM框架,它可以连接到多种数据库类型(如PostgreSQLMySQLSQLiteSQL Server),提供高效、类型安全和易于使用的API,从而方便地操作和管理数据库。它的作用是帮助开发人员快速构建可扩展、高性能的数据库应用程序,同时减少手写底层SQL代码的工作量。

  • Prisma有哪些主要的特性和功能?

    Prisma的主要特性和功能包括:

    • 数据建模:支持通过编写数据模型(schema)定义应用程序的数据结构来定义数据库中的表、字段、关系、索引等内容。
    • 数据查询:提供基于类型安全的查询API来查询数据库,并支持AND/OR逻辑运算、分页、过滤、排序、连接查询等高级功能。
    • 数据修改:支持通过数据编辑API来添加、更新和删除数据库中的数据,并提供事务管理等功能。
    • 数据迁移:支持数据库模式的迁移操作,方便在开发、测试和生产环境之间进行数据库的版本控制和管理。
  • Prisma如何处理关系型数据?

    Prisma的关系型数据库支持包括一对一、一对多和多对多关系。Prisma的数据建模语言允许开发人员定义表之间的关系,并在查询时自动处理关系查询、连接和过滤。

    Prisma基于外键关系自动管理关系的连接和解除连接。在定义数据模型时,可以使用关系属性来建立连接。例如,下面的代码定义了一个名为posts的文档集合:

    model Post {
      id    Int     @id @default(autoincrement())
      title String
      body  String
      comments Comment[]
    }
    
    model Comment {
      id     Int    @id @default(autoincrement())
      body   String
      postId Int
      post   Post   @relation(fields: [postId], references: [id])
    }
    

    在这个例子中,Comment模型具有一个指向Post模型的引用,并且在执行查询时,Prisma会自动处理两个模型之间的连接。

  • Prisma 与传统TypeORM 有什么区别和优势?

    虽然PrismaTypeORM解决了类似的问题,但它们的工作方式非常不同。

    TypeORM是一种传统的ORM,将表映射到模型类。这些模型类可用于生成SQL迁移。然后,模型类的实例在运行时为应用程序提供CRUD查询接口。

    Prisma是一种新类型的ORM,可以缓解传统ORM中许多问题,例如臃肿的模型实例、业务逻辑与存储逻辑混合、缺乏类型安全性或由懒加载引起的不可预测查询等。

    TypeORM为其查找方法(例如findfindByIdsfindOne等)提供了一个select选项,例如:

    const postRepository = getManager().getRepository(Post)
    const publishedPosts: Post[] = await postRepository.find({
        where: { published: true },
        select: ['id', 'title'],
    })

    虽然返回的publishedPosts数组中的每个对象在运行时只携带所选的idtitle属性,但TypeScript编译器并不知道这一点。它将允许您在查询之后访问Post实体上定义的任何其他属性,例如:

    const post = publishedPosts[0]
    // TypeScript编译器没有问题
    if (post.content.length > 0) {
        console.log(`This post has some content.`)
    }

    此代码将导致运行时错误:

    TypeError: Cannot read property 'length' of undefined

    Prisma Client可以在相同情况下保证完全类型安全,并防止您访问未从数据库检索到的字段。考虑使用Prisma Client查询相同示例,在这种情况下,TypeScript编译器将在编译时抛出以下错误:

    [ERROR] 14:03:39 ⨯ Unable to compile TypeScript:
    src/index.ts:36:12 - error TS2339: Property 'content' does not exist on type '{ id: number; title: string; }'.
    
    42   if (post.content.length > 0) {

    这是因为Prisma Client动态生成其查询的返回类型。在本例中,publishedPosts的类型如下所示:

    const publishedPosts: {
      id: number
      title: string
    }[]

    因此,您无法访问未有类型声明的属性

本文由mdnice多平台发布


timewilltell
68 声望4 粉丝

本人方向为前端,从八月份准备到九月份面试到十月份拿到满意的offer,前前后后大概两个月,期间面试了很多互联网公司,在面试中发现基础占了80%,项目和其他占了20%,现在面试结束了,想系统的把前端常见的面试问...