{T}

Prisma集成与Docker部署PostgreSQL

Prisma集成与Docker部署PostgreSQL

学习目标:掌握 Prisma ORM 在 NestJS 中的集成,学会使用 Docker 部署 PostgreSQL 数据库。


一、为什么选择 Prisma

1.1 Prisma vs 其他 ORM

ORM 库优点缺点TypeScript 支持
Prisma简洁易用、类型安全、迁移工具强大性能略低于 TypeORM完美
TypeORMNestJS 官方推荐、性能好配置复杂、装饰器多良好
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.toml

2.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:

配置项说明

配置项作用说明
imageDocker 镜像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 pgdb

3.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数据库类型
服务器postgresDocker 服务名或 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:8080

5.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/tcp

5.3 连接方式对比

环境连接地址端口
本地localhost5432
服务器服务器IP5432
Docker 内部postgres(服务名)5432

六、NestJS 集成 Prisma

6.1 安装依赖

bash
# 安装 Prisma CLI 和 Client
npm install prisma --save-dev
npm install @prisma/client

# 初始化 Prisma
npx prisma init

6.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=public

6.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 studio

6.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 push

7.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=development

src/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 restart

9.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 deploy

9.3 Prisma Client 未生成

问题Cannot find module '@prisma/client'

解决方案

bash
# 重新生成 Prisma Client
npx prisma generate

# 重新安装依赖
npm install @prisma/client

9.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" 或 "NestJS Prisma"
  • B站:搜索 "Prisma 教程"

实战项目


十一、学习要点总结

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 studio

11.2 重点知识清单

知识点重要程度掌握程度
Prisma 的优势未掌握 / 已掌握
Docker 部署 PostgreSQL未掌握 / 已掌握
Prisma Schema 定义未掌握 / 已掌握
数据库迁移未掌握 / 已掌握
Prisma Client 使用未掌握 / 已掌握
NestJS 集成 Prisma未掌握 / 已掌握
Adminer 连接数据库未掌握 / 已掌握
事务处理未掌握 / 已掌握

11.3 课后思考题

  1. 为什么选择 Prisma 而不是 TypeORM?
  2. 如何使用 Docker 部署 PostgreSQL?
  3. Prisma Schema 的基本语法是什么?
  4. 如何在 NestJS 中集成 Prisma?
  5. Prisma 的数据库迁移流程是什么?

十二、扩展阅读

12.1 相关技术栈

code
Prisma 相关技术栈:
│
├── 数据库
│   ├── PostgreSQL  推荐
│   ├── MySQL
│   ├── SQLite
│   └── MongoDB
│
├── ORM
│   ├── Prisma 
│   ├── TypeORM
│   └── Sequelize
│
├── 工具
│   ├── Docker
│   ├── Adminer
│   └── Prisma Studio
│
└── 框架
    └── NestJS

12.2 进阶学习方向

  1. 深入 Prisma

    • 复杂查询
    • 性能优化
    • 数据库索引
  2. 数据库设计

    • 范式设计
    • 反范式设计
    • 分库分表
  3. Docker 进阶

    • Docker Compose 高级用法
    • 容器编排
    • CI/CD 集成
  4. NestJS 进阶

    • 模块化设计
    • 依赖注入
    • AOP 编程

参考资料


上一章20-ORM对象关系映射详解

下一章22-TypeORM集成与实体定义