
Prisma ORM:TypeScript 中的类型安全数据库访问
设置与模式
npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(cuid())
email String @unique
name String?
role Role @default(USER)
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
@@map("users") // 表名
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
viewCount Int @default(0)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId String
tags Tag[]
publishedAt DateTime?
createdAt DateTime @default(now())
@@index([authorId])
@@index([published, createdAt])
}
model Tag {
id String @id @default(cuid())
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
MODERATOR
}
迁移
# 创建迁移
npx prisma migrate dev --name add-user-profile
# 在生产环境应用
npx prisma migrate deploy
# 生成客户端
npx prisma generate
CRUD 操作
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient({
log: ['query', 'error', 'warn'],
});
// 创建并关联
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
posts: {
create: [
{ title: 'My first post' },
{ title: 'Hello World', published: true },
],
},
},
include: { posts: true },
});
// 带过滤和关联的查询
const publishedPosts = await prisma.post.findMany({
where: {
published: true,
author: { role: 'ADMIN' },
createdAt: { gte: new Date('2024-01-01') },
},
include: {
author: { select: { name: true, email: true } },
tags: true,
},
orderBy: { createdAt: 'desc' },
skip: 0,
take: 20,
});
// 更新
await prisma.post.update({
where: { id: 'post_123' },
data: {
published: true,
publishedAt: new Date(),
viewCount: { increment: 1 },
},
});
// 更新或创建
await prisma.user.upsert({
where: { email: 'alice@example.com' },
create: { email: 'alice@example.com', name: 'Alice' },
update: { name: 'Alice Updated' },
});
// 级联删除
await prisma.user.delete({ where: { id: userId } });
事务
// 交互式事务
const result = await prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data: { email: 'bob@example.com' } });
const post = await tx.post.create({
data: { title: 'First Post', authorId: user.id },
});
return { user, post };
});
// 顺序事务(更简单)
const [usersCount, postsCount] = await prisma.$transaction([
prisma.user.count(),
prisma.post.count({ where: { published: true } }),
]);
原始查询
// 当 Prisma API 不够用时使用原始 SQL
const users = await prisma.$queryRaw<User[]>`
SELECT u.*, COUNT(p.id)::int as post_count
FROM users u
LEFT JOIN posts p ON p.author_id = u.id
GROUP BY u.id
ORDER BY post_count DESC
LIMIT 10
`;
// 执行原始查询(无返回值)
await prisma.$executeRaw`
UPDATE posts SET view_count = view_count + 1
WHERE id = ${postId}
`;
中间件与软删除
prisma.$use(async (params, next) => {
// 软删除中间件
if (params.model === 'Post') {
if (params.action === 'delete') {
params.action = 'update';
params.args['data'] = { deletedAt: new Date() };
}
if (params.action === 'findMany') {
params.args.where = { ...params.args.where, deletedAt: null };
}
}
return next(params);
});
Prisma 的类型安全在编译时捕获模式变更,防止运行时数据库错误。