跳至主要内容

从 Drizzle 迁移

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

注意

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

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

迁移过程概述

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

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

无论您是构建 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 模式并将 Prisma 连接到您的数据库。在项目的根目录中运行以下命令以创建基本的 Prisma 模式文件

npx prisma init

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

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

您可能已经有一个 .env 文件。如果是这样,prisma init 命令将向其中追加行,而不是创建新文件。

Prisma 模式当前如下所示

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

该命令将通过将其添加到 _prisma_migrations 表中来将 0_init 标记为已应用。

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

2.5. 调整 Prisma 模式(可选)

通过内省生成的模型当前*完全*映射到您的数据库表。在本节中,您将了解如何调整 Prisma 模型的命名以符合Prisma ORM 的命名约定

所有这些调整都是完全可选的,如果您现在不想调整任何内容,您可以直接跳到下一步。您可以在以后的任何时间返回并进行调整。

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

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

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

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

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 允许您隐式地建模多对多关系。也就是说,在多对多关系中,您不必在模式中显式地管理关系表(有时也称为 JOIN 表)。以下是一个比较 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 为使用隐式语法创建的多对多关系创建的关系表(JOIN 表)没有主键。