跳到主要内容

从 Drizzle 迁移

本指南介绍如何从 Drizzle 迁移到 Prisma ORM。它使用基于 Drizzle Next.js 示例 的示例项目作为 示例项目 来演示迁移步骤。您可以在 GitHub 上找到用于本指南的示例。

提示

本迁移指南使用 Neon PostgreSQL 作为示例数据库,但它同样适用于 Prisma ORM 支持的 任何其他关系型数据库。

您可以在 Prisma ORM 与 Drizzle 页面上了解 Prisma ORM 与 Drizzle 的比较。

迁移过程概述

请注意,无论您正在构建哪种应用程序或 API 层,从 Drizzle 迁移到 Prisma ORM 的步骤始终相同。

  1. 安装 Prisma CLI
  2. 内省您的数据库
  3. 创建基线迁移
  4. 安装 Prisma Client
  5. 逐步用 Prisma Client 替换您的 Drizzle 查询

无论您是构建 REST API(例如使用 Express、koa 或 NestJS)、GraphQL API(例如使用 Apollo Server、TypeGraphQL 或 Nexus)还是任何其他使用 Drizzle 进行数据库访问的应用程序,这些步骤都适用。

Prisma ORM 非常适合 **增量采用**。这意味着您不必立即将整个项目从 Drizzle 迁移到 Prisma ORM,而是可以**逐步**将您的数据库查询从 Drizzle 迁移到 Prisma ORM。

步骤 1. 安装 Prisma CLI

采用 Prisma ORM 的第一步是在您的项目中 安装 Prisma CLI

npm install prisma --save-dev

步骤 2. 内省您的数据库

2.1. 设置 Prisma ORM

在您内省数据库之前,您需要设置您的 Prisma schema 并将 Prisma 连接到您的数据库。在项目的根目录中运行以下命令以创建基本 Prisma schema 文件。

npx prisma init

此命令会为您创建一个名为 prisma 的新目录,其中包含以下文件:

  • schema.prisma:您的 Prisma schema,指定您的数据库连接和模型
  • .env:一个 dotenv 用于将您的数据库连接 URL 配置为环境变量
提示

您可能已经有一个 .env 文件。如果是,则 prisma init 命令会将行追加到该文件,而不是创建新文件。

Prisma schema 目前如下所示

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

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

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

如果您使用的是 VS Code,请务必安装 Prisma VS Code 扩展 以获得语法高亮、格式化、自动完成以及更多酷炫功能。

2.2. 连接您的数据库

如果您没有使用 PostgreSQL,则需要将 datasource 块上的 provider 字段调整为您当前使用的数据库。

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

完成此操作后,您可以在 .env 文件中配置您的 数据库连接 URL。Drizzle 和 Prisma ORM 使用相同的连接 URL 格式,因此您的现有连接 URL 应该可以正常工作。

2.3. 使用 Prisma ORM 内省您的数据库

连接 URL 就位后,您可以 内省 您的数据库以生成 Prisma 模型。

npx prisma db pull

如果您使用的是 示例项目,将创建以下模型

prisma/schema.prisma
model todo {
id Int @id
text String
done Boolean @default(false)
}

生成的 Prisma 模型表示一个数据库表。Prisma 模型是您的程序化 Prisma Client API 的基础,它允许您向数据库发送查询。

2.4. 创建基线迁移

要继续使用 Prisma Migrate 来演变您的数据库模式,您需要 为您的数据库创建基线

首先,创建一个 migrations 目录,并在其中添加一个目录,用于存放您首选的迁移名称。在本例中,我们将使用 0_init 作为迁移名称。

mkdir -p prisma/migrations/0_init

接下来,使用 prisma migrate diff 生成迁移文件。使用以下参数

  • --from-empty:假设您要从中迁移的数据模型为空
  • --to-schema-datamodel:使用 datasource 块中的 URL 获取当前数据库状态
  • --script:输出 SQL 脚本
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script > prisma/migrations/0_init/migration.sql

查看生成的迁移,以确保一切正常。

接下来,使用 prisma migrate resolve--applied 参数将迁移标记为已应用。

npx prisma migrate resolve --applied 0_init

该命令会将 0_init 标记为已应用,方法是将其添加到 _prisma_migrations 表中。

您现在已经为当前数据库模式创建了基线。要对您的数据库模式进行进一步更改,您可以更新您的 Prisma schema 并使用 prisma migrate dev 将更改应用到您的数据库。

2.5. 调整 Prisma schema(可选)

通过内省生成的模型目前会与您的数据库表完全匹配。在本节中,您将了解如何调整 Prisma 模型的命名以符合 Prisma ORM 的命名约定

所有这些调整都是完全可选的,如果您现在不想进行任何调整,可以跳过此步骤。您可以随时返回并进行这些调整。

与 Drizzle 模型当前的 camelCase 表示法不同,Prisma ORM 的命名约定是

  • 模型名称使用 PascalCase
  • 字段名称使用 camelCase

您可以通过映射 Prisma 模型和字段名称到底层数据库中的现有表和列名称来调整命名,方法是使用 @@map@map

以下是如何修改上面模型的示例

prisma/schema.prisma
model Todo {
id Int @id
text String
done Boolean @default(false)

@@map("todo")
}

步骤 3. 安装并生成 Prisma Client

下一步,您可以在项目中安装 Prisma Client,以便开始替换当前使用 Drizzle 进行的数据库查询。

npm install @prisma/client

安装后,您需要运行 generate 以将您的架构反映在 TypeScript 类型和自动完成中。

npx prisma generate

步骤 4. 将 Drizzle 查询替换为 Prisma Client

在本节中,我们将展示一些从 Drizzle 迁移到 Prisma Client 的示例查询,这些查询基于示例 REST API 项目中的示例路由。有关 Prisma Client API 与 Drizzle 的差异的全面概述,请查看比较页面.

首先,设置 PrismaClient 实例,您将使用该实例从各种路由处理程序发送数据库查询。在 db 目录中创建一个名为 prisma.ts 的新文件

touch db/prisma.ts

现在,实例化 PrismaClient 并将其从文件中导出,以便您稍后在路由处理程序中使用它

db/prisma.ts
import { PrismaClient } from '@prisma/client'

export const prisma = new PrismaClient()

4.1. 替换 getData 查询

全栈 Next.js 应用程序有几个 actions,包括 getData

getData 操作目前按如下方式实现

actions/todoActions.ts
import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const getData = async () => {
const data = await db.select().from(todo);
return data;
};

以下是使用 Prisma Client 实现的相同操作

src/controllers/FeedAction.ts
import { prisma } from "@/db/prisma";

export const getData = async () => {
const data = await prisma.todo.findMany();
return data;
};

4.2. 替换 POST 请求中的查询

示例项目有四个在 POST 请求期间使用的操作

  • addTodo:创建新的 Todo 记录
  • deleteTodo:删除现有的 Todo 记录
  • toggleTodo:切换现有 Todo 记录上的布尔值 done 字段
  • editTodo:编辑现有 Todo 记录上的 text 字段

addTodo

addTodo 操作目前按如下方式实现

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const addTodo = async (id: number, text: string) => {
await db.insert(todo).values({
id: id,
text: text,
});
revalidatePath("/");
};

以下是使用 Prisma Client 实现的相同操作

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import { prisma } from "@/db/prisma";

export const addTodo = async (id: number, text: string) => {
await prisma.todo.create({
data: { id, text },
})
revalidatePath("/");
};

deleteTodo

deleteTodo 操作目前按如下方式实现

actions/todoActions.ts
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";

import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const deleteTodo = async (id: number) => {
await db.delete(todo).where(eq(todo.id, id));
revalidatePath("/");
};

以下是使用 Prisma Client 实现的相同操作

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import { prisma } from "@/db/prisma";

export const deleteTodo = async (id: number) => {
await prisma.todo.delete({ where: { id } });
revalidatePath("/");
};

toggleTodo

ToggleTodo 操作目前按如下方式实现

actions/todoActions.ts
import { eq, not } from "drizzle-orm";
import { revalidatePath } from "next/cache";

import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const toggleTodo = async (id: number) => {
await db
.update(todo)
.set({
done: not(todo.done),
})
.where(eq(todo.id, id));
revalidatePath("/");
};

以下是使用 Prisma Client 实现的相同操作

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import { prisma } from "@/db/prisma";

export const toggleTodo = async (id: number) => {
const todo = await prisma.todo.findUnique({ where: { id } });
if (todo) {
await prisma.todo.update({
where: { id: todo.id },
data: { done: !todo.done },
})
revalidatePath("/");
}
};

请注意,Prisma ORM 无法“就地”编辑布尔字段,因此必须预先获取记录。

editTodo

editTodo 操作目前按如下方式实现

actions/todoActions.ts
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";

import db from "@/db/drizzle";
import { todo } from "@/db/schema";

export const editTodo = async (id: number, text: string) => {
await db
.update(todo)
.set({
text: text,
})
.where(eq(todo.id, id));

revalidatePath("/");
};

以下是使用 Prisma Client 实现的相同操作

actions/todoActions.ts
import { revalidatePath } from "next/cache";

import { prisma } from "@/db/prisma";

export const editTodo = async (id: number, text: string) => {
await prisma.todo.update({
where: { id },
data: { text },
})
revalidatePath("/");
};

更多

隐式多对多关系

与 Drizzle 不同,Prisma ORM 允许您隐式建模多对多关系。也就是说,您不必在模式中显式管理关系表(有时也称为联接表)的多对多关系。以下是一个比较 Drizzle 与 Prisma ORM 的示例

schema.ts
import { boolean, integer, pgTable, serial, text } from "drizzle-orm/pg-core";

export const posts = pgTable('post', {
id: serial('serial').primaryKey(),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false).notNull(),
});

export const categories = pgTable('category', {
id: serial('serial').primaryKey(),
name: text('name').notNull(),
});

export const postsToCategories = pgTable('posts_to_categories', {
postId: integer('post_id').notNull().references(() => users.id),
categoryId: integer('category_id').notNull().references(() => chatGroups.id),
});

此模式等效于以下 Prisma 模式

schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
postsToCategories PostToCategories[]

@@map("post")
}

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

@@map("category")
}

model PostToCategories {
postId Int
categoryId Int
category Category @relation(fields: [categoryId], references: [id])
post Post @relation(fields: [postId], references: [id])

@@id([postId, categoryId])
@@index([postId])
@@index([categoryId])
@@map("posts_to_categories")
}

在此 Prisma 模式中,多对多关系通过关系表 PostToCategories 显式建模。

相反,通过遵循 Prisma ORM 关系表的约定,该关系可以如下所示

schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
categories Category[]
}

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

这也会导致更符合人体工程学且更简洁的 Prisma Client API 来修改此关系中的记录,因为您有从 PostCategory 的直接路径(反之亦然),而不是需要先遍历 PostToCategories 模型。

警告

如果您的数据库提供商要求表具有主键,那么您必须使用显式语法,并手动创建具有主键的联接模型。这是因为 Prisma ORM 为使用隐式语法创建的多对多关系创建的关系表(联接表)(通过 @relation 表示)没有主键。