从 Drizzle 迁移
本指南介绍如何从 Drizzle 迁移到 Prisma ORM。它使用基于 Drizzle Next.js 示例 的示例项目作为 示例项目 来演示迁移步骤。您可以在 GitHub 上找到用于本指南的示例。
本迁移指南使用 Neon PostgreSQL 作为示例数据库,但它同样适用于 Prisma ORM 支持的 任何其他关系型数据库。
您可以在 Prisma ORM 与 Drizzle 页面上了解 Prisma ORM 与 Drizzle 的比较。
迁移过程概述
请注意,无论您正在构建哪种应用程序或 API 层,从 Drizzle 迁移到 Prisma ORM 的步骤始终相同。
- 安装 Prisma CLI
- 内省您的数据库
- 创建基线迁移
- 安装 Prisma Client
- 逐步用 Prisma Client 替换您的 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 schema 并将 Prisma 连接到您的数据库。在项目的根目录中运行以下命令以创建基本 Prisma schema 文件。
npx prisma init
此命令会为您创建一个名为 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"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
如果您使用的是 VS Code,请务必安装 Prisma VS Code 扩展 以获得语法高亮、格式化、自动完成以及更多酷炫功能。
2.2. 连接您的数据库
如果您没有使用 PostgreSQL,则需要将 datasource
块上的 provider
字段调整为您当前使用的数据库。
- PostgreSQL
- MySQL
- Microsoft SQL Server
- SQLite
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
datasource db {
provider = "sqlserver"
url = env("DATABASE_URL")
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
完成此操作后,您可以在 .env
文件中配置您的 数据库连接 URL。Drizzle 和 Prisma ORM 使用相同的连接 URL 格式,因此您的现有连接 URL 应该可以正常工作。
2.3. 使用 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.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
该命令会将 0_init
标记为已应用,方法是将其添加到 _prisma_migrations
表中。
您现在已经为当前数据库模式创建了基线。要对您的数据库模式进行进一步更改,您可以更新您的 Prisma schema 并使用 prisma migrate dev
将更改应用到您的数据库。
2.5. 调整 Prisma schema(可选)
通过内省生成的模型目前会与您的数据库表完全匹配。在本节中,您将了解如何调整 Prisma 模型的命名以符合 Prisma ORM 的命名约定。
所有这些调整都是完全可选的,如果您现在不想进行任何调整,可以跳过此步骤。您可以随时返回并进行这些调整。
与 Drizzle 模型当前的 camelCase 表示法不同,Prisma ORM 的命名约定是
- 模型名称使用 PascalCase
- 字段名称使用 camelCase
您可以通过映射 Prisma 模型和字段名称到底层数据库中的现有表和列名称来调整命名,方法是使用 @@map
和 @map
。
以下是如何修改上面模型的示例
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
并将其从文件中导出,以便您稍后在路由处理程序中使用它
import { PrismaClient } from '@prisma/client'
export const prisma = new PrismaClient()
4.1. 替换 getData
查询
全栈 Next.js 应用程序有几个 actions
,包括 getData
。
getData
操作目前按如下方式实现
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 实现的相同操作
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
操作目前按如下方式实现
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 实现的相同操作
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
操作目前按如下方式实现
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 实现的相同操作
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
操作目前按如下方式实现
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 实现的相同操作
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
操作目前按如下方式实现
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 实现的相同操作
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 允许您隐式建模多对多关系。也就是说,您不必在模式中显式管理关系表(有时也称为联接表)的多对多关系。以下是一个比较 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),
});
此模式等效于以下 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 关系表的约定,该关系可以如下所示
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
表示)没有主键。