跳到主要内容

如何从 Drizzle 迁移到 Prisma ORM

简介

本指南将向您展示如何将应用程序从 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 客户端
  5. 逐步用 Prisma 客户端替换您的 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 架构并将 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 客户端 API 的基础,该 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

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

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

npx prisma migrate resolve --applied 0_init

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

您现在拥有当前数据库架构的基线。要进一步更改数据库架构,您可以更新您的 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 客户端

下一步,您可以在您的项目中安装 Prisma 客户端,这样您就可以开始替换项目中当前使用 Drizzle 执行的数据库查询

npm install @prisma/client

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

npx prisma generate

步骤 4. 用 Prisma 客户端替换您的 Drizzle 查询

在本节中,我们将展示一些基于示例 REST API 项目中的示例路由从 Drizzle 迁移到 Prisma 客户端的示例查询。有关 Prisma 客户端 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 来修改此关系中的记录,因为你可以从 Post 直接到 Category(反之亦然),而无需首先遍历 PostToCategories 模型。

警告

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