如何从 Drizzle 迁移到 Prisma ORM
介绍
本指南将向您展示如何将应用程序从 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 的步骤始终相同
- 安装 Prisma CLI
- 内省您的数据库
- 创建基线迁移
- 安装 Prisma Client
- 逐步将 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 目前如下所示
// 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` 字段调整为您当前使用的数据库
- PostgreSQL
- MySQL
- Microsoft SQL Server
- SQLite
datasource db {
provider = "postgresql"
}
datasource db {
provider = "mysql"
}
datasource db {
provider = "sqlserver"
}
datasource db {
provider = "sqlite"
}
完成此操作后,您可以在 `.env` 文件中配置您的数据库连接 URL。Drizzle 和 Prisma ORM 使用相同的连接 URL 格式,因此您现有的连接 URL 应该可以正常工作。
2.3. 配置 Prisma
在项目根目录中创建一个 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
如果您正在使用 示例项目,将创建以下模型
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 模型和字段名称**映射**到底层数据库中现有的表名和列名来调整命名。
以下是如何修改上述模型的示例
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` 并从文件中导出,以便您稍后可以在路由处理程序中使用它
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 目前实现如下
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
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 目前实现如下
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
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 目前实现如下
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
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 目前实现如下
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
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 目前实现如下
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
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 的比较示例
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
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 关系表的约定,关系可以如下所示
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 之旅: 我们的活跃社区。保持信息灵通,参与其中,并与其他开发人员协作。
- 在 X 上关注我们 获取公告、直播活动和实用技巧。
- 加入我们的 Discord 提问、与社区交流,并通过对话获得积极支持。
- 在 YouTube 上订阅 获取教程、演示和直播。
- 在 GitHub 上参与 加星收藏存储库、报告问题或为问题做出贡献。