跳到主要内容

如何从 Drizzle 迁移到 Prisma ORM

15 分钟

简介

本指南将向您展示如何将应用程序从 Drizzle 迁移到 Prisma ORM。我们将使用一个基于 Drizzle Next.js 示例 的示例项目来演示迁移步骤。您可以在 GitHub 上找到本指南使用的示例。

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

先决条件

在开始本指南之前,请确保您已具备以下条件:

  • 要迁移的 Drizzle 项目
  • 已安装 Node.js(版本 16 或更高)
  • PostgreSQL 或其他支持的数据库
  • 对 Drizzle 和 Next.js 有基本了解
注意

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

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

迁移过程概述

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

  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 && npm install @prisma/client

步骤 2. 内省您的数据库

2.1. 设置 Prisma ORM

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

npx prisma init --output ../generated/prisma

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

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

您可能已经有一个 .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 来演进数据库 schema,您需要为数据库创建基线

首先,创建一个 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

检查生成的迁移以确保一切正确。

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

npx prisma migrate resolve --applied 0_init

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

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

2.5. 调整 Prisma schema(可选)

通过内省生成的模型目前与数据库表精确映射。在本节中,您将学习如何调整 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 以使您的 schema 反映在 TypeScript 类型和自动补全中。

npx prisma generate

步骤 4. 使用 Prisma Client 替换您的 Drizzle 查询

在本节中,我们将基于示例 REST API 项目的示例路由,展示一些从 Drizzle 迁移到 Prisma Client 的示例查询。有关 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 应用程序有多个操作,包括 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 允许您隐式建模多对多关系。也就是说,在多对多关系中,您无需在 schema 中**显式**管理关系表(有时也称为 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),
});

此 schema 等同于以下 Prisma schema:

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 schema 中,多对多关系通过关系表 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 更符合人体工程学且更简洁,用于修改此关系中的记录,因为您可以从 Post 直接访问 Category(反之亦然),而无需首先遍历 PostToCategories 模型。

警告

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


与 Prisma 保持联系

通过以下方式与我们保持联系,继续您的 Prisma 之旅: 我们活跃的社区。保持信息灵通,积极参与,并与其他开发者协作

我们真诚地珍视您的参与,并期待您成为我们社区的一员!

© . All rights reserved.