跳至主要内容

如何从 Drizzle 迁移到 Prisma ORM

15 分钟

介绍

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

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

先决条件

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

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

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

您可以在 Prisma ORM vs 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 @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg pg
信息

如果你使用的是其他数据库提供程序(MySQL、SQL Server、SQLite),请安装相应的驱动程序适配器包,而不是 @prisma/adapter-pg。有关更多信息,请参阅 数据库驱动程序

步骤 2. 内省您的数据库

2.1. 设置 Prisma ORM

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

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

此命令为您创建了一个名为 `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"
}

generator client {
provider = "prisma-client"
output = "./generated/prisma"
}
提示

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

2.2. 连接您的数据库

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

schema.prisma
datasource db {
provider = "postgresql"
}

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

2.3. 配置 Prisma

在项目根目录中创建一个 prisma.config.ts 文件,内容如下

prisma.config.ts
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config';

export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations',
},
datasource: {
url: env('DATABASE_URL'),
},
});
注意

您需要安装 `dotenv` 包来加载环境变量。如果您尚未安装,请使用您的包管理器进行安装

npm install dotenv

2.4. 使用 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.5. 创建基线迁移

要继续使用 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

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

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

npx prisma migrate resolve --applied 0_init

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

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

2.6. 调整 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

现在您已经在步骤 1 中安装了 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 '../generated/prisma/client'
import { PrismaPg } from '@prisma/adapter-pg'
import 'dotenv/config'

const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL,
})

export const prisma = new PrismaClient({
adapter,
})

4.1. 替换 `getData` 查询

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

`getData` action 目前实现如下

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 实现的相同 action

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` 请求期间使用的 action

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

`addTodo`

`addTodo` action 目前实现如下

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 实现的相同 action

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` action 目前实现如下

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 实现的相同 action

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` action 目前实现如下

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 实现的相同 action

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` action 目前实现如下

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 实现的相同 action

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 之旅: 我们的活跃社区。保持信息灵通,参与其中,并与其他开发人员协作。

我们真诚地感谢你的参与,并期待你成为我们社区的一部分!

© . This site is unofficial and not affiliated with Prisma Data, Inc.