prisma-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePrisma ORM Patterns
Prisma ORM 实践模式
Schema Definition
Schema 定义
prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
environments Environment[]
sessions Session[]
@@map("users")
}
model Environment {
id String @id @default(cuid())
name String
description String?
status EnvironmentStatus @default(PENDING)
ownerId String @map("owner_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
tags Tag[]
@@unique([ownerId, name])
@@index([status])
@@index([createdAt(sort: Desc)])
@@map("environments")
}
model Tag {
id String @id @default(cuid())
key String
value String
environmentId String @map("environment_id")
createdAt DateTime @default(now()) @map("created_at")
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
@@unique([environmentId, key])
@@map("tags")
}
enum EnvironmentStatus {
PENDING
RUNNING
STOPPED
FAILED
}prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
environments Environment[]
sessions Session[]
@@map("users")
}
model Environment {
id String @id @default(cuid())
name String
description String?
status EnvironmentStatus @default(PENDING)
ownerId String @map("owner_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
tags Tag[]
@@unique([ownerId, name])
@@index([status])
@@index([createdAt(sort: Desc)])
@@map("environments")
}
model Tag {
id String @id @default(cuid())
key String
value String
environmentId String @map("environment_id")
createdAt DateTime @default(now()) @map("created_at")
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
@@unique([environmentId, key])
@@map("tags")
}
enum EnvironmentStatus {
PENDING
RUNNING
STOPPED
FAILED
}Prisma Client Setup
Prisma Client 配置
typescript
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
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
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
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
}Basic CRUD Operations
基础CRUD操作
typescript
// Create
const environment = await prisma.environment.create({
data: {
name: 'dev-env',
description: 'Development environment',
ownerId: userId,
},
})
// Read
const environment = await prisma.environment.findUnique({
where: { id: envId },
})
const environments = await prisma.environment.findMany({
where: { status: 'RUNNING' },
orderBy: { createdAt: 'desc' },
take: 10,
})
// Update
const updated = await prisma.environment.update({
where: { id: envId },
data: { status: 'STOPPED' },
})
// Delete
await prisma.environment.delete({
where: { id: envId },
})
// Upsert
const env = await prisma.environment.upsert({
where: { id: envId },
update: { status: 'RUNNING' },
create: {
name: 'new-env',
ownerId: userId,
},
})typescript
// 创建
const environment = await prisma.environment.create({
data: {
name: 'dev-env',
description: 'Development environment',
ownerId: userId,
},
})
// 读取
const environment = await prisma.environment.findUnique({
where: { id: envId },
})
const environments = await prisma.environment.findMany({
where: { status: 'RUNNING' },
orderBy: { createdAt: 'desc' },
take: 10,
})
// 更新
const updated = await prisma.environment.update({
where: { id: envId },
data: { status: 'STOPPED' },
})
// 删除
await prisma.environment.delete({
where: { id: envId },
})
// 插入或更新
const env = await prisma.environment.upsert({
where: { id: envId },
update: { status: 'RUNNING' },
create: {
name: 'new-env',
ownerId: userId,
},
})Relations
关联关系
typescript
// Include relations
const envWithOwner = await prisma.environment.findUnique({
where: { id: envId },
include: {
owner: true,
tags: true,
},
})
// Select specific fields
const envPartial = await prisma.environment.findUnique({
where: { id: envId },
select: {
id: true,
name: true,
owner: {
select: {
name: true,
email: true,
},
},
},
})
// Nested create
const envWithTags = await prisma.environment.create({
data: {
name: 'tagged-env',
ownerId: userId,
tags: {
create: [
{ key: 'team', value: 'platform' },
{ key: 'tier', value: 'production' },
],
},
},
include: { tags: true },
})
// Connect existing relation
const env = await prisma.environment.create({
data: {
name: 'new-env',
owner: {
connect: { id: userId },
},
},
})typescript
// 包含关联数据
const envWithOwner = await prisma.environment.findUnique({
where: { id: envId },
include: {
owner: true,
tags: true,
},
})
// 选择特定字段
const envPartial = await prisma.environment.findUnique({
where: { id: envId },
select: {
id: true,
name: true,
owner: {
select: {
name: true,
email: true,
},
},
},
})
// 嵌套创建
const envWithTags = await prisma.environment.create({
data: {
name: 'tagged-env',
ownerId: userId,
tags: {
create: [
{ key: 'team', value: 'platform' },
{ key: 'tier', value: 'production' },
],
},
},
include: { tags: true },
})
// 关联已有数据
const env = await prisma.environment.create({
data: {
name: 'new-env',
owner: {
connect: { id: userId },
},
},
})Filtering
数据过滤
typescript
// Complex filters
const environments = await prisma.environment.findMany({
where: {
AND: [
{ status: 'RUNNING' },
{
OR: [
{ name: { contains: 'prod', mode: 'insensitive' } },
{ tags: { some: { key: 'tier', value: 'production' } } },
],
},
],
createdAt: {
gte: new Date('2024-01-01'),
},
owner: {
email: { endsWith: '@jetbrains.com' },
},
},
})
// NOT filter
const nonFailedEnvs = await prisma.environment.findMany({
where: {
NOT: { status: 'FAILED' },
},
})typescript
// 复杂过滤
const environments = await prisma.environment.findMany({
where: {
AND: [
{ status: 'RUNNING' },
{
OR: [
{ name: { contains: 'prod', mode: 'insensitive' } },
{ tags: { some: { key: 'tier', value: 'production' } } },
],
},
],
createdAt: {
gte: new Date('2024-01-01'),
},
owner: {
email: { endsWith: '@jetbrains.com' },
},
},
})
// NOT过滤
const nonFailedEnvs = await prisma.environment.findMany({
where: {
NOT: { status: 'FAILED' },
},
})Pagination
分页处理
typescript
// Offset pagination
async function getEnvironmentsPage(page: number, pageSize: number) {
const [items, total] = await Promise.all([
prisma.environment.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma.environment.count(),
])
return {
items,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
}
}
// Cursor pagination
async function getEnvironmentsCursor(cursor?: string, take: number = 10) {
const items = await prisma.environment.findMany({
take: take + 1, // Fetch one extra to check if there's more
...(cursor && {
cursor: { id: cursor },
skip: 1, // Skip the cursor
}),
orderBy: { createdAt: 'desc' },
})
const hasMore = items.length > take
const data = hasMore ? items.slice(0, -1) : items
return {
items: data,
nextCursor: hasMore ? data[data.length - 1].id : null,
}
}typescript
// 偏移量分页
async function getEnvironmentsPage(page: number, pageSize: number) {
const [items, total] = await Promise.all([
prisma.environment.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma.environment.count(),
])
return {
items,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
}
}
// 游标分页
async function getEnvironmentsCursor(cursor?: string, take: number = 10) {
const items = await prisma.environment.findMany({
take: take + 1, // 多获取一条以判断是否还有更多数据
...(cursor && {
cursor: { id: cursor },
skip: 1, // 跳过游标数据
}),
orderBy: { createdAt: 'desc' },
})
const hasMore = items.length > take
const data = hasMore ? items.slice(0, -1) : items
return {
items: data,
nextCursor: hasMore ? data[data.length - 1].id : null,
}
}Transactions
事务处理
typescript
// Interactive transaction
const result = await prisma.$transaction(async (tx) => {
// Create environment
const env = await tx.environment.create({
data: { name: 'new-env', ownerId: userId },
})
// Create associated tags
await tx.tag.createMany({
data: [
{ key: 'team', value: 'platform', environmentId: env.id },
{ key: 'cost-center', value: '12345', environmentId: env.id },
],
})
// Update user's environment count (if tracking)
await tx.user.update({
where: { id: userId },
data: { environmentCount: { increment: 1 } },
})
return env
})
// Sequential transaction (batch)
const [deletedEnvs, deletedTags] = await prisma.$transaction([
prisma.environment.deleteMany({ where: { status: 'FAILED' } }),
prisma.tag.deleteMany({ where: { environment: { status: 'FAILED' } } }),
])typescript
// 交互式事务
const result = await prisma.$transaction(async (tx) => {
// 创建环境
const env = await tx.environment.create({
data: { name: 'new-env', ownerId: userId },
})
// 创建关联标签
await tx.tag.createMany({
data: [
{ key: 'team', value: 'platform', environmentId: env.id },
{ key: 'cost-center', value: '12345', environmentId: env.id },
],
})
// 更新用户的环境数量(如果有追踪)
await tx.user.update({
where: { id: userId },
data: { environmentCount: { increment: 1 } },
})
return env
})
// 顺序事务(批量)
const [deletedEnvs, deletedTags] = await prisma.$transaction([
prisma.environment.deleteMany({ where: { status: 'FAILED' } }),
prisma.tag.deleteMany({ where: { environment: { status: 'FAILED' } } }),
])Aggregations
数据聚合
typescript
// Count by status
const statusCounts = await prisma.environment.groupBy({
by: ['status'],
_count: { status: true },
})
// Aggregate functions
const stats = await prisma.environment.aggregate({
_count: { id: true },
_min: { createdAt: true },
_max: { createdAt: true },
})typescript
// 按状态统计数量
const statusCounts = await prisma.environment.groupBy({
by: ['status'],
_count: { status: true },
})
// 聚合函数
const stats = await prisma.environment.aggregate({
_count: { id: true },
_min: { createdAt: true },
_max: { createdAt: true },
})Raw Queries (When Needed)
原生查询(必要时使用)
typescript
// Raw query
const result = await prisma.$queryRaw<Environment[]>`
SELECT * FROM environments
WHERE status = ${status}
AND created_at > NOW() - INTERVAL '7 days'
`
// Raw execute
await prisma.$executeRaw`
UPDATE environments
SET status = 'STOPPED'
WHERE status = 'RUNNING'
AND updated_at < NOW() - INTERVAL '24 hours'
`typescript
// 原生查询
const result = await prisma.$queryRaw<Environment[]>`
SELECT * FROM environments
WHERE status = ${status}
AND created_at > NOW() - INTERVAL '7 days'
`
// 原生执行
await prisma.$executeRaw`
UPDATE environments
SET status = 'STOPPED'
WHERE status = 'RUNNING'
AND updated_at < NOW() - INTERVAL '24 hours'
`Migrations
数据迁移
bash
undefinedbash
undefinedCreate migration
创建迁移
npx prisma migrate dev --name add_environment_type
npx prisma migrate dev --name add_environment_type
Apply migrations (production)
应用迁移(生产环境)
npx prisma migrate deploy
npx prisma migrate deploy
Reset database (development)
重置数据库(开发环境)
npx prisma migrate reset
npx prisma migrate reset
Generate client
生成客户端
npx prisma generate
undefinednpx prisma generate
undefined