Prisma集成与Docker部署PostgreSQL
Prisma集成与Docker部署PostgreSQL
学习目标:掌握 Prisma ORM 在 NestJS 中的集成,学会使用 Docker 部署 PostgreSQL 数据库。
一、为什么选择 Prisma
1.1 Prisma vs 其他 ORM
| ORM 库 | 优点 | 缺点 | TypeScript 支持 |
|---|---|---|---|
| Prisma | 简洁易用、类型安全、迁移工具强大 | 性能略低于 TypeORM | 完美 |
| TypeORM | NestJS 官方推荐、性能好 | 配置复杂、装饰器多 | 良好 |
| Sequelize | 成熟稳定、社区大 | 配置复杂、TS 支持弱 | 一般 |
1.2 选择 Prisma 的原因
核心优势:
code
Prisma 优势:
│
├── 简洁性
│ └── Schema 定义简单,声明式语法
│
├── 类型安全
│ └── 自动生成 TypeScript 类型
│
├── 工具链完善
│ ├── Prisma Studio(可视化管理)
│ ├── Prisma Migrate(数据库迁移)
│ └── Prisma Client(类型安全查询)
│
└── 数据库支持广泛
└── PostgreSQL、MySQL、SQLite、SQL Server、MongoDB、CockroachDB性能说明:
- Prisma 和 TypeORM 的性能差异不大
- 主要性能差异在硬件层面和数据库层面
- ORM 层面的性能差异可以忽略
1.3 Prisma 支持的数据库
code
Prisma 支持的数据库:
│
├── 关系型数据库
│ ├── PostgreSQL 推荐
│ ├── MySQL
│ ├── SQLite
│ ├── SQL Server
│ └── CockroachDB
│
└── 非关系型数据库
└── MongoDB二、Prisma 集成流程
2.1 完整集成步骤
code
Prisma 集成流程:
│
├── 第一步:环境准备
│ ├── 安装 TypeScript
│ └── 初始化 TypeScript 项目
│
├── 第二步:安装 Prisma
│ ├── 安装 Prisma CLI
│ └── 安装 Prisma Client
│
├── 第三步:初始化 Prisma
│ ├── 运行 prisma init
│ ├── 选择数据库类型
│ └── 配置数据库连接
│
├── 第四步:定义 Schema
│ ├── 创建数据模型
│ └── 定义字段和关系
│
├── 第五步:创建迁移
│ ├── 生成迁移文件
│ └── 同步到数据库
│
└── 第六步:使用 Prisma Client
├── 导入 PrismaClient
└── 执行 CRUD 操作2.2 Schema 定义语法
基本语法:
prisma
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
createdAt DateTime @default(now())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
}字段属性说明:
| 属性 | 作用 | 示例 |
|---|---|---|
@id | 主键 | id Int @id |
@default(autoincrement()) | 自增 | id Int @id @default(autoincrement()) |
@unique | 唯一约束 | email String @unique |
? | 可选字段(允许 null) | name String? |
@relation | 关系映射 | author User @relation(fields: [authorId], references: [id]) |
@default(now()) | 默认当前时间 | createdAt DateTime @default(now()) |
@map("_id") | 映射数据库字段名 | id Int @id @map("_id") |
2.3 数据库迁移
迁移命令:
bash
# 生成迁移文件
npx prisma migrate dev --name init
# 应用迁移到生产环境
npx prisma migrate deploy
# 重置数据库
npx prisma migrate reset
# 查看迁移状态
npx prisma migrate status迁移文件结构:
code
prisma/
├── schema.prisma # 数据模型定义
└── migrations/ # 迁移文件目录
├── 20231001000000_init/
│ └── migration.sql # SQL 迁移脚本
├── 20231002000000_add_user_table/
│ └── migration.sql
└── migration_lock.toml2.4 Prisma Client 使用
导入 PrismaClient:
typescript
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// 查询所有用户
const users = await prisma.user.findMany();
// 创建用户
const user = await prisma.user.create({
data: {
email: 'test@example.com',
name: '张三',
},
});
// 查询单个用户
const user = await prisma.user.findUnique({
where: { id: 1 },
});
// 更新用户
const user = await prisma.user.update({
where: { id: 1 },
data: { name: '李四' },
});
// 删除用户
await prisma.user.delete({
where: { id: 1 },
});三、Docker 部署 PostgreSQL
3.1 Docker Compose 文件详解
创建 docker-compose.yml:
yaml
version: '3.8'
services:
# PostgreSQL 数据库
postgres:
image: postgres:15-alpine
container_name: pgdb
restart: unless-stopped
environment:
POSTGRES_USER: pguser # 用户名
POSTGRES_PASSWORD: example # 密码
POSTGRES_DB: pgdb # 数据库名
ports:
- "5432:5432" # 端口映射
volumes:
- postgres_data:/var/lib/postgresql/data
# Adminer 图形化管理界面
adminer:
image: adminer:latest
container_name: adminer
restart: unless-stopped
ports:
- "8080:8080" # Web 管理界面端口
depends_on:
- postgres
volumes:
postgres_data:配置项说明:
| 配置项 | 作用 | 说明 |
|---|---|---|
image | Docker 镜像 | postgres:15-alpine 是轻量级版本 |
container_name | 容器名称 | 方便识别和管理 |
restart: unless-stopped | 重启策略 | 除非手动停止,否则自动重启 |
environment | 环境变量 | 设置用户名、密码、数据库名 |
ports | 端口映射 | 本地端口:容器端口 |
volumes | 数据卷 | 持久化数据库数据 |
depends_on | 依赖关系 | 确保 postgres 先启动 |
3.2 Docker Compose 常用命令
bash
# 启动所有服务(后台运行)
docker compose up -d
# 查看运行中的容器
docker compose ps
# 查看日志
docker compose logs
# 停止所有服务
docker compose down
# 停止并删除数据卷
docker compose down -v
# 重启所有服务
docker compose restart
# 进入容器
docker compose exec postgres bash
# 连接数据库
docker compose exec postgres psql -U pguser -d pgdb3.3 端口冲突解决方案
问题:8080 端口已被占用
解决方案:
yaml
# 修改本地端口映射
ports:
- "8082:8080" # 使用本地 8082 端口映射到容器的 8080 端口访问地址:http://localhost:8082
3.4 防火墙配置
Linux 服务器防火墙放行:
bash
# 查看防火墙状态
sudo ufw status
# 放行端口
sudo ufw allow 5432/tcp # PostgreSQL
sudo ufw allow 8080/tcp # Adminer
# 重载防火墙
sudo ufw reload四、Adminer 连接数据库
4.1 Adminer 登录配置
登录信息:
| 配置项 | 值 | 说明 |
|---|---|---|
| 系统 | PostgreSQL | 数据库类型 |
| 服务器 | postgres | Docker 服务名或 IP |
| 用户名 | pguser | 环境变量设置的用户名 |
| 密码 | example | 环境变量设置的密码 |
| 数据库 | pgdb | 环境变量设置的数据库名 |
本地访问:
- 地址:
http://localhost:8080 - 如果端口冲突,使用修改后的端口:
http://localhost:8082
服务器访问:
- 地址:
http://服务器IP:8080 - 需要防火墙放行 8080 端口
4.2 Adminer 界面功能
code
Adminer 功能:
│
├── 数据库管理
│ ├── 创建数据库
│ ├── 删除数据库
│ └── 备份数据库
│
├── 表管理
│ ├── 创建表
│ ├── 修改表结构
│ ├── 删除表
│ └── 清空表数据
│
├── 数据管理
│ ├── 插入数据
│ ├── 查询数据
│ ├── 更新数据
│ └── 删除数据
│
└── SQL 执行
└── 执行自定义 SQL 语句五、本地 vs 服务器部署
5.1 本地开发环境
适用场景:
- 本地开发调试
- 学习测试
- 没有 Linux 服务器
步骤:
bash
# 1. 安装 Docker Desktop
# 下载地址:https://www.docker.com/products/docker-desktop
# 2. 创建 docker-compose.yml
touch docker-compose.yml
# 3. 启动服务
docker compose up -d
# 4. 访问 Adminer
open http://localhost:80805.2 服务器生产环境
适用场景:
- 生产环境部署
- 团队协作开发
- 持续集成/持续部署
步骤:
bash
# 1. 连接服务器
ssh user@your-server-ip
# 2. 创建项目目录
mkdir -p ~/postgres-db && cd ~/postgres-db
# 3. 创建 docker-compose.yml
vim docker-compose.yml
# 4. 启动服务
docker compose up -d
# 5. 检查服务状态
docker compose ps
# 6. 防火墙放行(如需要)
sudo ufw allow 5432/tcp
sudo ufw allow 8080/tcp5.3 连接方式对比
| 环境 | 连接地址 | 端口 |
|---|---|---|
| 本地 | localhost | 5432 |
| 服务器 | 服务器IP | 5432 |
| Docker 内部 | postgres(服务名) | 5432 |
六、NestJS 集成 Prisma
6.1 安装依赖
bash
# 安装 Prisma CLI 和 Client
npm install prisma --save-dev
npm install @prisma/client
# 初始化 Prisma
npx prisma init6.2 配置数据库连接
环境变量配置(.env):
env
# PostgreSQL 连接字符串
DATABASE_URL="postgresql://pguser:example@localhost:5432/pgdb?schema=public"
# 服务器环境
# DATABASE_URL="postgresql://pguser:example@服务器IP:5432/pgdb?schema=public"连接字符串格式:
code
postgresql://用户名:密码@主机:端口/数据库名?schema=public6.3 创建 Prisma 服务
src/prisma/prisma.service.ts:
typescript
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
constructor() {
super({
log: [
{ emit: 'event', level: 'query' },
{ emit: 'stdout', level: 'info' },
{ emit: 'stdout', level: 'warn' },
{ emit: 'stdout', level: 'error' },
],
});
}
async onModuleInit() {
await this.$connect();
console.log(' Prisma connected to database');
}
async onModuleDestroy() {
await this.$disconnect();
console.log(' Prisma disconnected from database');
}
}6.4 注册 Prisma 模块
src/prisma/prisma.module.ts:
typescript
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}在 app.module.ts 中导入:
typescript
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [PrismaModule],
// ...
})
export class AppModule {}6.5 定义数据模型
prisma/schema.prisma:
prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
password String
type Int @default(0) // 用户类型:0-普通,1-会员,2-高级会员
expire DateTime? // 会员过期时间
status Int @default(0) // 状态:0-正常,1-禁用
phone String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 关系
posts Post[]
comments Comment[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 关系
author User @relation(fields: [authorId], references: [id])
comments Comment[]
}
model Comment {
id Int @id @default(autoincrement())
content String
userId Int
postId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 关系
user User @relation(fields: [userId], references: [id])
post Post @relation(fields: [postId], references: [id])
}6.6 数据库迁移
bash
# 生成迁移文件并应用
npx prisma migrate dev --name init
# 查看 Prisma Studio(可视化管理工具)
npx prisma studio6.7 在 Service 中使用 Prisma
src/user/user.service.ts:
typescript
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { User, Prisma } from '@prisma/client';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
// 创建用户
async create(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({ data });
}
// 查询所有用户
async findAll(): Promise<User[]> {
return this.prisma.user.findMany();
}
// 查询单个用户
async findOne(id: number): Promise<User | null> {
return this.prisma.user.findUnique({
where: { id },
include: { posts: true, comments: true },
});
}
// 更新用户
async update(id: number, data: Prisma.UserUpdateInput): Promise<User> {
return this.prisma.user.update({
where: { id },
data,
});
}
// 删除用户
async remove(id: number): Promise<User> {
return this.prisma.user.delete({
where: { id },
});
}
}七、Prisma CLI 常用命令
7.1 数据库操作命令
bash
# 初始化 Prisma
npx prisma init
# 生成 Prisma Client
npx prisma generate
# 创建迁移并应用
npx prisma migrate dev --name <migration-name>
# 应用迁移到生产环境
npx prisma migrate deploy
# 重置数据库
npx prisma migrate reset
# 查看迁移状态
npx prisma migrate status
# 打开 Prisma Studio
npx prisma studio
# 格式化 schema.prisma
npx prisma format
# 验证 schema.prisma
npx prisma validate
# 拉取数据库结构到 schema
npx prisma db pull
# 推送 schema 到数据库(不创建迁移)
npx prisma db push7.2 开发工作流
code
Prisma 开发工作流:
│
├── 1. 修改 schema.prisma
│ └── 定义或修改数据模型
│
├── 2. 格式化 schema
│ └── npx prisma format
│
├── 3. 验证 schema
│ └── npx prisma validate
│
├── 4. 创建迁移
│ └── npx prisma migrate dev --name <name>
│
├── 5. 生成 Prisma Client
│ └── npx prisma generate
│
└── 6. 使用 Prisma Client
└── 在代码中使用 prisma.user.findMany() 等八、最佳实践
8.1 环境变量管理
.env.example:
env
# 数据库配置
DATABASE_URL="postgresql://pguser:example@localhost:5432/pgdb?schema=public"
# JWT 密钥
JWT_SECRET="your-secret-key"
# 应用端口
PORT=3000
# 环境
NODE_ENV=developmentsrc/config/database.config.ts:
typescript
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({
url: process.env.DATABASE_URL,
}));8.2 错误处理
src/prisma/prisma-exception.filter.ts:
typescript
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpStatus,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { Response } from 'express';
@Catch(Prisma.PrismaClientKnownRequestError)
export class PrismaExceptionFilter implements ExceptionFilter {
catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Database error';
// 处理不同的错误类型
switch (exception.code) {
case 'P2002':
// 唯一约束冲突
status = HttpStatus.CONFLICT;
message = 'Record already exists';
break;
case 'P2025':
// 记录不存在
status = HttpStatus.NOT_FOUND;
message = 'Record not found';
break;
case 'P2003':
// 外键约束失败
status = HttpStatus.BAD_REQUEST;
message = 'Foreign key constraint failed';
break;
}
response.status(status).json({
statusCode: status,
message,
error: exception.message,
});
}
}8.3 事务处理
typescript
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class OrderService {
constructor(private prisma: PrismaService) {}
async createOrder(orderData: any) {
// 使用事务
const result = await this.prisma.$transaction(async (tx) => {
// 创建订单
const order = await tx.order.create({
data: orderData,
});
// 扣减库存
await tx.product.update({
where: { id: orderData.productId },
data: {
stock: { decrement: orderData.quantity },
},
});
// 创建支付记录
const payment = await tx.payment.create({
data: {
orderId: order.id,
amount: orderData.totalAmount,
},
});
return { order, payment };
});
return result;
}
}8.4 分页查询
typescript
async findWithPagination(page: number, pageSize: number) {
const skip = (page - 1) * pageSize;
const [users, total] = await Promise.all([
this.prisma.user.findMany({
skip,
take: pageSize,
orderBy: { createdAt: 'desc' },
}),
this.prisma.user.count(),
]);
return {
data: users,
meta: {
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
},
};
}九、常见问题与解决方案
9.1 数据库连接失败
问题:Can't reach database server at localhost:5432
解决方案:
bash
# 1. 检查 Docker 容器状态
docker compose ps
# 2. 检查容器日志
docker compose logs postgres
# 3. 检查端口是否被占用
lsof -i :5432
# 4. 重启容器
docker compose restart9.2 迁移冲突
问题:Migration failed
解决方案:
bash
# 1. 重置数据库(开发环境)
npx prisma migrate reset
# 2. 强制推送 schema(不创建迁移)
npx prisma db push --force-reset
# 3. 手动解决冲突后重新迁移
npx prisma migrate dev --create-only
npx prisma migrate deploy9.3 Prisma Client 未生成
问题:Cannot find module '@prisma/client'
解决方案:
bash
# 重新生成 Prisma Client
npx prisma generate
# 重新安装依赖
npm install @prisma/client9.4 端口冲突
问题:port is already allocated
解决方案:
yaml
# 修改 docker-compose.yml 中的端口映射
ports:
- "5433:5432" # 使用本地 5433 端口
- "8082:8080" # Adminer 使用 8082 端口十、学习路径
10.1 学习阶段规划
code
Prisma 学习路径:
│
├── 第一阶段:环境搭建(1-2 天)
│ ├── 安装 Docker
│ ├── 部署 PostgreSQL
│ ├── 安装 Prisma
│ └── 连接数据库
│
├── 第二阶段:基础使用(1 周)
│ ├── 定义 Schema
│ ├── 数据库迁移
│ ├── CRUD 操作
│ └── 关系映射
│
└── 第三阶段:深入应用(持续)
├── 事务处理
├── 查询优化
├── 性能调优
└── 生产部署10.2 学习资源推荐
官方文档:
- Prisma 官方文档:https://www.prisma.io/docs
- Prisma GitHub:https://github.com/prisma/prisma
- NestJS Prisma 集成:https://docs.nestjs.com/recipes/prisma
视频教程:
- 慕课网:搜索 "Prisma" 或 "NestJS Prisma"
- B站:搜索 "Prisma 教程"
实战项目:
- NestJS + Prisma 示例:https://github.com/nestjs/nest/tree/master/sample
十一、学习要点总结
11.1 核心概念速记
code
Prisma 核心概念:
│
├── 选择 Prisma
│ ├── 简洁易用
│ ├── TypeScript 友好
│ └── 工具链完善
│
├── Docker 部署 PostgreSQL
│ ├── docker-compose.yml
│ ├── 环境变量配置
│ └── Adminer 管理界面
│
├── Prisma 集成步骤
│ ├── 安装依赖
│ ├── 初始化 Prisma
│ ├── 定义 Schema
│ ├── 创建迁移
│ └── 使用 Prisma Client
│
└── 常用命令
├── prisma init
├── prisma migrate dev
├── prisma generate
└── prisma studio11.2 重点知识清单
| 知识点 | 重要程度 | 掌握程度 |
|---|---|---|
| Prisma 的优势 | 未掌握 / 已掌握 | |
| Docker 部署 PostgreSQL | 未掌握 / 已掌握 | |
| Prisma Schema 定义 | 未掌握 / 已掌握 | |
| 数据库迁移 | 未掌握 / 已掌握 | |
| Prisma Client 使用 | 未掌握 / 已掌握 | |
| NestJS 集成 Prisma | 未掌握 / 已掌握 | |
| Adminer 连接数据库 | 未掌握 / 已掌握 | |
| 事务处理 | 未掌握 / 已掌握 |
11.3 课后思考题
- 为什么选择 Prisma 而不是 TypeORM?
- 如何使用 Docker 部署 PostgreSQL?
- Prisma Schema 的基本语法是什么?
- 如何在 NestJS 中集成 Prisma?
- Prisma 的数据库迁移流程是什么?
十二、扩展阅读
12.1 相关技术栈
code
Prisma 相关技术栈:
│
├── 数据库
│ ├── PostgreSQL 推荐
│ ├── MySQL
│ ├── SQLite
│ └── MongoDB
│
├── ORM
│ ├── Prisma
│ ├── TypeORM
│ └── Sequelize
│
├── 工具
│ ├── Docker
│ ├── Adminer
│ └── Prisma Studio
│
└── 框架
└── NestJS12.2 进阶学习方向
-
深入 Prisma
- 复杂查询
- 性能优化
- 数据库索引
-
数据库设计
- 范式设计
- 反范式设计
- 分库分表
-
Docker 进阶
- Docker Compose 高级用法
- 容器编排
- CI/CD 集成
-
NestJS 进阶
- 模块化设计
- 依赖注入
- AOP 编程
参考资料
上一章:20-ORM对象关系映射详解