PlanetScale 与 Prisma 集成 #
本章将介绍如何在 Prisma ORM 中集成 PlanetScale 数据库,利用 Prisma 的强大功能简化数据库操作。
Prisma 简介 #
为什么选择 Prisma? #
text
┌─────────────────────────────────────────────────────────────┐
│ Prisma 优势 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ 类型安全 │
│ - 自动生成 TypeScript 类型 │
│ - 编译时错误检查 │
│ │
│ ✅ 直观的 API │
│ - 链式调用 │
│ - 自动补全 │
│ │
│ ✅ 数据库无关 │
│ - 支持 MySQL、PostgreSQL、SQLite 等 │
│ - 统一的 API │
│ │
│ ✅ 强大的工具 │
│ - Prisma Studio(可视化管理) │
│ - Prisma Migrate(迁移管理) │
│ - Prisma Client(类型安全客户端) │
│ │
└─────────────────────────────────────────────────────────────┘
项目配置 #
安装 Prisma #
bash
# 安装 Prisma
npm install prisma @prisma/client
# 初始化 Prisma
npx prisma init
配置 schema.prisma #
prisma
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
}
环境变量 #
env
DATABASE_URL="mysql://abc123:pscale_pw_xxx@aws.connect.psdb.cloud/my-database?sslaccept=strict"
重要配置说明 #
relationMode 设置 #
text
┌─────────────────────────────────────────────────────────────┐
│ relationMode 说明 │
├─────────────────────────────────────────────────────────────┤
│ │
│ PlanetScale 不支持外键约束,需要设置: │
│ │
│ datasource db { │
│ provider = "mysql" │
│ url = env("DATABASE_URL") │
│ relationMode = "prisma" │
│ } │
│ │
│ relationMode = "prisma" 的作用: │
│ - Prisma 在应用层模拟外键关系 │
│ - 不依赖数据库外键约束 │
│ - 兼容 PlanetScale 的架构 │
│ │
└─────────────────────────────────────────────────────────────┘
迁移策略 #
text
┌─────────────────────────────────────────────────────────────┐
│ 迁移策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ PlanetScale 使用分支管理 Schema,因此: │
│ │
│ ❌ 不要使用 prisma migrate │
│ - prisma migrate 需要外键支持 │
│ - 与 PlanetScale 分支工作流冲突 │
│ │
│ ✅ 使用 prisma db push │
│ - 直接推送 Schema 变更 │
│ - 不创建迁移文件 │
│ - 配合 PlanetScale 分支使用 │
│ │
│ 推荐工作流: │
│ 1. 创建 PlanetScale 开发分支 │
│ 2. 修改 schema.prisma │
│ 3. 运行 prisma db push │
│ 4. 创建 Deploy Request │
│ 5. 部署到生产 │
│ │
└─────────────────────────────────────────────────────────────┘
CRUD 操作 #
创建 Prisma Client #
typescript
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default prisma;
查询操作 #
typescript
import prisma from './prisma';
// 查询所有用户
const users = await prisma.user.findMany();
// 条件查询
const user = await prisma.user.findFirst({
where: {
email: 'test@example.com'
}
});
// 根据 ID 查询
const user = await prisma.user.findUnique({
where: {
id: 1
}
});
// 关联查询
const usersWithPosts = await prisma.user.findMany({
include: {
posts: true
}
});
// 分页查询
const users = await prisma.user.findMany({
skip: 0,
take: 10,
orderBy: {
createdAt: 'desc'
}
});
// 聚合查询
const count = await prisma.user.count();
创建操作 #
typescript
import prisma from './prisma';
// 创建单个记录
const user = await prisma.user.create({
data: {
email: 'test@example.com',
name: 'Test User'
}
});
// 创建并关联
const post = await prisma.post.create({
data: {
title: 'My First Post',
content: 'Hello World',
author: {
connect: {
id: 1
}
}
}
});
// 批量创建
const users = await prisma.user.createMany({
data: [
{ email: 'user1@example.com', name: 'User 1' },
{ email: 'user2@example.com', name: 'User 2' }
]
});
更新操作 #
typescript
import prisma from './prisma';
// 更新单个记录
const user = await prisma.user.update({
where: {
id: 1
},
data: {
name: 'Updated Name'
}
});
// 条件更新
const result = await prisma.user.updateMany({
where: {
email: { contains: '@old-domain.com' }
},
data: {
email: 'new@example.com'
}
});
// 更新或创建
const user = await prisma.user.upsert({
where: {
email: 'test@example.com'
},
update: {
name: 'Updated Name'
},
create: {
email: 'test@example.com',
name: 'New User'
}
});
删除操作 #
typescript
import prisma from './prisma';
// 删除单个记录
await prisma.user.delete({
where: {
id: 1
}
});
// 条件删除
await prisma.user.deleteMany({
where: {
email: { contains: '@test.com' }
}
});
Express 集成示例 #
typescript
import express from 'express';
import { PrismaClient } from '@prisma/client';
const app = express();
app.use(express.json());
const prisma = new PrismaClient();
app.get('/users', async (req, res) => {
const users = await prisma.user.findMany({
include: { posts: true }
});
res.json(users);
});
app.get('/users/:id', async (req, res) => {
const user = await prisma.user.findUnique({
where: { id: parseInt(req.params.id) },
include: { posts: true }
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
app.post('/users', async (req, res) => {
const user = await prisma.user.create({
data: req.body
});
res.status(201).json(user);
});
app.put('/users/:id', async (req, res) => {
const user = await prisma.user.update({
where: { id: parseInt(req.params.id) },
data: req.body
});
res.json(user);
});
app.delete('/users/:id', async (req, res) => {
await prisma.user.delete({
where: { id: parseInt(req.params.id) }
});
res.status(204).send();
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Next.js 集成示例 #
API Route #
typescript
import { PrismaClient } from '@prisma/client';
import type { NextApiRequest, NextApiResponse } from 'next';
const prisma = new PrismaClient();
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === 'GET') {
const users = await prisma.user.findMany();
return res.status(200).json(users);
}
if (req.method === 'POST') {
const user = await prisma.user.create({
data: req.body
});
return res.status(201).json(user);
}
res.status(405).json({ error: 'Method not allowed' });
}
Server Component #
typescript
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default async function UsersPage() {
const users = await prisma.user.findMany();
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
);
}
最佳实践 #
连接管理 #
typescript
import { PrismaClient } from '@prisma/client';
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ||
new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
错误处理 #
typescript
import { Prisma } from '@prisma/client';
try {
const user = await prisma.user.create({
data: { email: 'existing@example.com' }
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
console.log('Unique constraint violation');
}
}
throw error;
}
下一步 #
现在你已经掌握了 Prisma 集成,接下来学习 Vercel 集成,了解如何部署到 Vercel!
最后更新:2026-03-29