{T}

MVC架构与DTO-DAO概念

一、MVC 架构模式

1.1 什么是 MVC

概念说明

MVC(Model-View-Controller)是一种软件架构模式,用于将应用程序分离为三个核心组件。

三大组成部分

code
MVC 三层架构:
│
├──  Model(模型)
│   ├── 核心业务逻辑
│   ├── 数据处理
│   ├── 数据库操作
│   └── 数据状态管理
│
├──  View(视图)
│   ├── 用户界面
│   ├── 数据展示
│   └── 用户交互入口
│
└──  Controller(控制器)
    ├── 请求解析
    ├── 路由分发
    ├── 调用 Model
    └── 返回 View

1.2 MVC 工作流程

传统 MVC 流程

code
传统 MVC 工作流程:

┌──────────┐
│   用户   │
└────┬─────┘
     │ 操作
     ▼
┌──────────┐      1. 用户操作      ┌──────────┐
│   View   │ ───────────────────> │Controller│
│  视图层   │                      │  控制器   │
└────┬─────┘                      └─────┬────┘
     │                                  │
     │ 4. 数据变化通知                   │ 2. 调用业务逻辑
     │                                  │
     ▼                                  ▼
┌──────────┐                      ┌──────────┐
│   用户   │                      │  Model   │
│ 看到更新  │ <─────────────────── │   模型   │
└──────────┘   3. 数据处理完成      └──────────┘

流程说明:
1. 用户在 View 层进行操作(点击按钮、提交表单)
2. Controller 接收请求,调用对应的 Model
3. Model 处理业务逻辑,操作数据库
4. Model 处理完成后,通知 View 更新

现代前后端分离 MVC

code
现代前后端分离 MVC:

前端(View 层):
│
├── React / Vue / Angular
├── 用户界面
├── 用户交互
└── 发送 HTTP 请求

         │ HTTP 请求
         ▼

后端(Controller + Model):
│
├── Controller(控制器)
│   ├── 接收请求
│   ├── 解析参数
│   ├── 调用 Service
│   └── 返回响应
│
└── Model(模型)
    ├── Service 业务逻辑
    ├── Repository 数据访问
    └── Entity 数据实体

         │ SQL 查询
         ▼

数据库:
└── MySQL / PostgreSQL / MongoDB

流程说明:
1. 前端发送 HTTP 请求到后端
2. Controller 接收请求并解析
3. Controller 调用 Model 处理业务
4. Model 操作数据库并返回数据
5. Controller 将数据返回给前端
6. 前端 View 层渲染数据

1.3 MVC 的优势

code
MVC 架构优势:
│
├──  分层清晰
│   ├── 职责分离
│   ├── 代码组织清晰
│   └── 易于理解
│
├──  可复用性
│   ├── Model 可被多个 View 使用
│   ├── Controller 可复用
│   └── 减少重复代码
│
├──  可测试性
│   ├── 各层独立测试
│   ├── 易于模拟依赖
│   └── 提高测试覆盖率
│
├──  可维护性
│   ├── 修改不影响其他层
│   ├── 易于扩展
│   └── 降低维护成本
│
└──  团队协作
    ├── 前后端分离开发
    ├── 并行开发
    └── 提高开发效率

二、NestJS 中的 MVC

2.1 NestJS MVC 架构

NestJS 三层对应关系

code
NestJS 中的 MVC 实现:

MVC 概念              NestJS 实现
│                       │
├── View(视图)  ────> ├── 前端项目(React/Vue)
│                      ├── 模板引擎(Handlebars/EJS)
│                      └── 或不实现(前后端分离)
│
├── Controller ───────> ├── @Controller 装饰器
│                      ├── 处理 HTTP 请求
│                      └── 调用 Service
│
└── Model(模型) ────> ├── Service(业务逻辑)
                       ├── Repository(数据访问)
                       ├── Entity(数据实体)
                       └── ORM(TypeORM/Prisma)

2.2 NestJS 作为 View 层

模板引擎集成

typescript
// ========== 安装模板引擎 ==========

// npm install hbs

// ========== 配置模板引擎 ==========

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);

  // 配置模板引擎
  app.useStaticAssets(join(__dirname, '..', 'public'));
  app.setBaseViewsDir(join(__dirname, '..', 'views'));
  app.setViewEngine('hbs'); // 使用 Handlebars 模板引擎

  await app.listen(3000);
}
bootstrap();

// ========== 控制器渲染视图 ==========

// src/app.controller.ts
import { Controller, Get, Render } from '@nestjs/common';

@Controller()
export class AppController {
  @Get()
  @Render('index') // 渲染 index.hbs 模板
  root() {
    return { message: 'Hello World!' }; // 传递数据给模板
  }

  @Get('users')
  @Render('users')
  getUsers() {
    return {
      users: [
        { id: 1, name: '张三' },
        { id: 2, name: '李四' },
      ],
    };
  }
}

// ========== 模板文件 ==========

<!-- views/index.hbs -->
<!DOCTYPE html>
<html>
<head>
  <title>NestJS MVC</title>
</head>
<body>
  <h1>{{ message }}</h1>
</body>
</html>

<!-- views/users.hbs -->
<!DOCTYPE html>
<html>
<head>
  <title>用户列表</title>
</head>
<body>
  <h1>用户列表</h1>
  <ul>
    {{#each users}}
      <li>{{this.id}} - {{this.name}}</li>
    {{/each}}
  </ul>
</body>
</html>

常用模板引擎

模板引擎说明适用场景
Handlebars (hbs)简单、轻量传统服务端渲染
EJS嵌入式 JavaScript简单项目
Pug (Jade)缩进语法追求简洁
Nunjucks功能强大复杂模板

2.3 前后端分离模式

现代推荐方式

code
前后端分离架构:

前端项目(独立):
│
├── React / Vue / Angular
├── 单独部署(静态资源服务器)
├── 调用后端 API
└── 用户界面渲染

         │ RESTful API / GraphQL
         ▼

后端项目(NestJS):
│
├── Controller(API 接口)
├── Service(业务逻辑)
├── Repository(数据访问)
└── Entity(数据实体)

         │ SQL
         ▼

数据库

优势:
• 前后端独立开发
• 前端可使用现代化框架
• 后端只提供 API
• 部署灵活

三、DTO(Data Transfer Object)

3.1 DTO 概念

概念说明

DTO(Data Transfer Object,数据传输对象)用于在不同层之间传输数据,重点在于"传输"过程。

核心特点

code
DTO 核心特点:
│
├──  数据传输
│   ├── 前端 → 后端(请求 DTO)
│   ├── 后端 → 前端(响应 DTO)
│   └── 服务层 → 数据层
│
├──  数据筛选
│   ├── 只接收部分字段
│   ├── 过滤敏感字段
│   └── 控制数据暴露
│
├──  数据校验
│   ├── 字段类型验证
│   ├── 字段格式验证
│   └── 字段约束验证
│
└──  属性约束
    └── 属性 ≤ 实体属性(部分数据)

3.2 DTO 的作用

三大核心作用

code
DTO 三大核心作用:

1⃣ 接收部分数据
│
├── 场景:创建用户
│   ├── 实体字段:id, name, email, password, createdAt, updatedAt
│   ├── DTO 字段:name, email, password
│   └── 说明:id、时间戳由后端生成,不需要前端传

2⃣ 数据筛选
│
├── 场景:更新用户
│   ├──  危险:允许前端传 id
│   │   └── A 用户可以修改 B 用户数据(传 B 的 id)
│   ├──  安全:DTO 不包含 id
│   │   └── 从 Token 获取当前用户 id
│   └── 说明:敏感字段不能通过 DTO 传递

3⃣ 数据校验
│
├── 场景:创建用户
│   ├── name:必填,长度 2-100
│   ├── email:必填,邮箱格式
│   └── password:必填,长度 ≥ 6
│   └── 说明:在 DTO 中定义校验规则

3.3 DTO vs Entity 对比

维度DTO(数据传输对象)Entity(实体类)
用途层间数据传输数据库映射
字段部分字段完整字段
包含敏感信息不包含包含
数据校验强校验可选
对应关系API 接口数据库表
示例CreateUserDtoUser Entity

代码示例对比

typescript
// ========== Entity(实体类)==========

// src/users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number; //  数据库自动生成

  @Column({ length: 100 })
  name: string;

  @Column({ unique: true })
  email: string;

  @Column()
  password: string; //  敏感字段

  @Column({ default: true })
  isActive: boolean;

  @CreateDateColumn()
  createdAt: Date; //  数据库自动生成

  @UpdateDateColumn()
  updatedAt: Date; //  数据库自动生成
}

// ========== DTO(数据传输对象)==========

// src/users/dto/create-user.dto.ts
import { IsString, IsEmail, MinLength, MaxLength, IsOptional } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(2, { message: '用户名至少 2 个字符' })
  @MaxLength(100, { message: '用户名最多 100 个字符' })
  name: string;

  @IsEmail({}, { message: '邮箱格式不正确' })
  email: string;

  @IsString()
  @MinLength(6, { message: '密码至少 6 个字符' })
  password: string;

  //  不包含 id(数据库自动生成)
  //  不包含 createdAt(数据库自动生成)
  //  不包含 updatedAt(数据库自动生成)
}

// src/users/dto/update-user.dto.ts
import { PartialType, OmitType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

// PartialType:所有字段变为可选
// OmitType:排除某些字段
export class UpdateUserDto extends PartialType(
  OmitType(CreateUserDto, ['password'] as const),
) {
  // 更新时不允许修改密码
  // 密码修改应该有专门的接口
}

// src/users/dto/response-user.dto.ts
import { Exclude, Expose } from 'class-transformer';

export class ResponseUserDto {
  id: number;
  name: string;
  email: string;
  isActive: boolean;
  createdAt: Date;

  @Exclude() //  响应时不返回密码
  password: string;

  constructor(partial: Partial<ResponseUserDto>) {
    Object.assign(this, partial);
  }
}

// ========== 使用示例 ==========

// src/users/users.controller.ts
import { Controller, Post, Body, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ResponseUserDto } from './dto/response-user.dto';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  async create(@Body() createUserDto: CreateUserDto): Promise<ResponseUserDto> {
    //  DTO 自动验证字段
    //  只接收 name, email, password
    const user = await this.usersService.create(createUserDto);
    
    //  响应 DTO 过滤敏感字段
    return new ResponseUserDto(user);
  }
}

3.4 NestJS DTO 完整示例

安装依赖

bash
npm install class-validator class-transformer

完整 DTO 示例

typescript
// src/users/dto/create-user.dto.ts
import {
  IsString,
  IsEmail,
  MinLength,
  MaxLength,
  IsInt,
  Min,
  Max,
  IsOptional,
  IsBoolean,
  Matches,
} from 'class-validator';

export class CreateUserDto {
  // 用户名:必填,2-100 字符
  @IsString({ message: '用户名必须是字符串' })
  @MinLength(2, { message: '用户名至少 2 个字符' })
  @MaxLength(100, { message: '用户名最多 100 个字符' })
  name: string;

  // 邮箱:必填,邮箱格式
  @IsEmail({}, { message: '邮箱格式不正确' })
  email: string;

  // 密码:必填,至少 6 位,包含数字和字母
  @IsString()
  @MinLength(6, { message: '密码至少 6 个字符' })
  @Matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,}$/, {
    message: '密码必须包含数字和字母',
  })
  password: string;

  // 年龄:可选,0-150
  @IsOptional()
  @IsInt({ message: '年龄必须是整数' })
  @Min(0, { message: '年龄不能小于 0' })
  @Max(150, { message: '年龄不能大于 150' })
  age?: number;

  // 是否激活:可选,布尔值
  @IsOptional()
  @IsBoolean({ message: 'isActive 必须是布尔值' })
  isActive?: boolean;
}

// ========== 启用全局验证 ==========

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 全局启用验证管道
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true, // 过滤未定义的属性
      forbidNonWhitelisted: true, // 如果有未定义属性,抛出错误
      transform: true, // 自动转换类型
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );
  
  await app.listen(3000);
}
bootstrap();

// ========== 验证效果 ==========

// 请求示例(验证失败)
POST /users
{
  "name": "A",              //  至少 2 个字符
  "email": "invalid-email", //  邮箱格式不正确
  "password": "123456",     //  必须包含数字和字母
  "age": -5,                //  年龄不能小于 0
  "hobby": "coding"         //  未定义的字段
}

// 响应
{
  "statusCode": 400,
  "message": [
    "用户名至少 2 个字符",
    "邮箱格式不正确",
    "密码必须包含数字和字母",
    "年龄不能小于 0",
    "property hobby should not exist"
  ],
  "error": "Bad Request"
}

四、DAO(Data Access Object)

4.1 DAO 概念

概念说明

DAO(Data Access Object,数据访问对象)用于封装数据库访问逻辑,重点在于"访问"数据库。

核心特点

code
DAO 核心特点:
│
├──  数据库访问
│   ├── 封装数据库操作
│   ├── 隐藏 SQL 细节
│   └── 提供统一接口
│
├──  封装性
│   ├── 不暴露数据库内部信息
│   ├── 对外提供方法
│   └── 降低耦合度
│
├──  单一职责
│   ├── 只负责数据访问
│   ├── 不包含业务逻辑
│   └── 可复用性强
│
└──  可替换性
    ├── 更换数据库只需修改 DAO
    ├── 不影响上层业务
    └── 灵活切换实现

4.2 DAO 的作用

DAO 层的职责

code
DAO 层职责:

┌──────────────────────────────────────┐
│           Service 层(业务逻辑)        │
│    调用 DAO 方法,不关心数据库细节      │
└──────────────┬───────────────────────┘
               │ 调用
               ▼
┌──────────────────────────────────────┐
│            DAO 层(数据访问)           │
│  ┌────────────────────────────────┐  │
│  │ • 实体类(Entity)               │  │
│  │ • 数据库操作(CRUD)             │  │
│  │ • 数据校验                      │  │
│  │ • 错误处理                      │  │
│  └────────────────────────────────┘  │
└──────────────┬───────────────────────┘
               │ SQL 查询
               ▼
         ┌──────────┐
         │  数据库   │
         └──────────┘

DAO 层包含:
│
├──  Entity(实体类)
│   └── 对应数据库表
│
├──  Repository(仓库)
│   └── 数据库 CRUD 操作
│
├──  Validation(校验)
│   └── 数据库约束校验
│
└──  Error Handling(错误处理)
    └── 数据库异常处理

4.3 DAO 在 NestJS 中的实现

DAO 层的多种实现方式

code
NestJS 中 DAO 层的实现:

1⃣ TypeORM(推荐)
│
├── Entity:实体类
├── Repository:数据访问
└── 支持:MySQL, PostgreSQL, MongoDB, SQLite

2⃣ Prisma
│
├── Schema:数据模型
├── Client:数据访问
└── 支持:MySQL, PostgreSQL, MongoDB, SQLite

3⃣ Mongoose(MongoDB)
│
├── Schema:数据模型
├── Model:数据访问
└── 支持:MongoDB

4⃣ Sequelize
│
├── Model:数据模型
├── 支持:MySQL, PostgreSQL, SQLite
└── 传统的 ORM 库

4.4 TypeORM 实现 DAO 层

完整示例

typescript
// ========== 安装依赖 ==========

// npm install @nestjs/typeorm typeorm mysql2

// ========== 配置数据库连接 ==========

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'test',
      entities: [], // 自动加载实体
      synchronize: true, // 自动同步表结构(生产环境设为 false)
    }),
    UsersModule,
  ],
})
export class AppModule {}

// ========== Entity(实体类)==========

// src/users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';

@Entity('users') // 对应数据库表名
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 100 })
  name: string;

  @Index() // 添加索引
  @Column({ unique: true }) // 唯一约束
  email: string;

  @Column()
  password: string;

  @Column({ default: true })
  isActive: boolean;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

// ========== Repository(数据访问层)==========

// src/users/users.repository.ts(可选,使用 TypeORM Repository)
import { EntityRepository, Repository } from 'typeorm';
import { User } from './entities/user.entity';

@EntityRepository(User)
export class UsersRepository extends Repository<User> {
  // 自定义查询方法
  async findActiveUsers(): Promise<User[]> {
    return this.createQueryBuilder('user')
      .where('user.isActive = :isActive', { isActive: true })
      .getMany();
  }

  async findByEmail(email: string): Promise<User | undefined> {
    return this.findOne({ where: { email } });
  }

  async findWithPagination(page: number, limit: number): Promise<[User[], number]> {
    return this.findAndCount({
      skip: (page - 1) * limit,
      take: limit,
      order: { createdAt: 'DESC' },
    });
  }
}

// ========== Service(业务逻辑层)==========

// src/users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  // 创建用户
  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.userRepository.create(createUserDto);
    return this.userRepository.save(user);
  }

  // 查询所有用户
  async findAll(): Promise<User[]> {
    return this.userRepository.find();
  }

  // 查询单个用户
  async findOne(id: number): Promise<User> {
    const user = await this.userRepository.findOne({ where: { id } });
    if (!user) {
      throw new NotFoundException(`用户 #${id} 不存在`);
    }
    return user;
  }

  // 更新用户
  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id);
    Object.assign(user, updateUserDto);
    return this.userRepository.save(user);
  }

  // 删除用户
  async remove(id: number): Promise<void> {
    const user = await this.findOne(id);
    await this.userRepository.remove(user);
  }
}

// ========== Module(模块注册)==========

// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])], // 注册实体
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

4.5 DAO 层的优势

code
DAO 层优势:

1⃣ 封装数据库细节
│
├── 上层不知道数据库类型
├── 上层不知道 SQL 语句
└── 更换数据库只需修改 DAO

2⃣ 提高可测试性
│
├── 可以模拟 DAO 层
├── Service 层独立测试
└── 不需要真实数据库

3⃣ 代码复用
│
├── 多个 Service 可复用 DAO
├── 统一的数据访问接口
└── 减少重复代码

4⃣ 易于维护
│
├── 数据库操作集中管理
├── 修改影响范围小
└── 降低维护成本

五、DTO vs DAO 对比

5.1 核心区别

code
DTO vs DAO 核心区别:

┌──────────────────────────────────────────┐
│              HTTP 请求                    │
└────────────────┬─────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────┐
│          DTO(数据传输对象)               │
│  ┌────────────────────────────────────┐  │
│  │ • 接收请求数据                      │  │
│  │ • 数据校验                          │  │
│  │ • 数据筛选                          │  │
│  │ • 部分字段                          │  │
│  └────────────────────────────────────┘  │
└────────────────┬─────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────┐
│            Controller 控制器              │
└────────────────┬─────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────┐
│            Service 业务逻辑               │
└────────────────┬─────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────┐
│          DAO(数据访问对象)               │
│  ┌────────────────────────────────────┐  │
│  │ • 数据库访问                        │  │
│  │ • CRUD 操作                         │  │
│  │ • 实体类映射                        │  │
│  │ • 完整字段                          │  │
│  └────────────────────────────────────┘  │
└────────────────┬─────────────────────────┘
                 │
                 ▼
           ┌──────────┐
           │  数据库   │
           └──────────┘

5.2 对比表

维度DTODAO
全称Data Transfer ObjectData Access Object
关键词Transfer(传输)Access(访问)
位置Controller 层Service → 数据库层
作用数据传输、校验、筛选数据库访问、CRUD
字段部分字段完整字段
对应关系API 接口数据库表
包含敏感信息不包含包含
NestJS 实现class-validatorTypeORM/Prisma
示例CreateUserDtoUser Entity + Repository

5.3 完整流程示例

从请求到数据库的完整流程

typescript
// ========== 1. HTTP 请求 ==========

// 前端发送请求
POST /users
{
  "name": "张三",
  "email": "zhangsan@example.com",
  "password": "abc123"
}

// ========== 2. DTO 验证 ==========

// src/users/dto/create-user.dto.ts
export class CreateUserDto {
  @IsString()
  @MinLength(2)
  name: string; //  接收

  @IsEmail()
  email: string; //  接收

  @IsString()
  @MinLength(6)
  password: string; //  接收

  //  不接收 id、createdAt、updatedAt
}

// ========== 3. Controller 处理 ==========

@Post()
async create(@Body() createUserDto: CreateUserDto) {
  // DTO 已验证,调用 Service
  return this.usersService.create(createUserDto);
}

// ========== 4. Service 业务逻辑 ==========

async create(createUserDto: CreateUserDto): Promise<User> {
  // 业务逻辑:密码加密
  const hashedPassword = await this.hashPassword(createUserDto.password);
  
  // 调用 DAO 层
  const user = this.userRepository.create({
    ...createUserDto,
    password: hashedPassword,
  });
  
  return this.userRepository.save(user);
}

// ========== 5. DAO 数据访问 ==========

// Entity 映射到数据库表
@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number; //  数据库生成

  @Column()
  name: string; //  完整字段

  @Column()
  email: string; //  完整字段

  @Column()
  password: string; //  完整字段

  @CreateDateColumn()
  createdAt: Date; //  数据库生成

  @UpdateDateColumn()
  updatedAt: Date; //  数据库生成
}

// Repository 执行 SQL
INSERT INTO users (name, email, password, createdAt, updatedAt)
VALUES ('张三', 'zhangsan@example.com', 'hashed_password', NOW(), NOW());

// ========== 6. 数据库存储 ==========

// users 表
+----+------+---------------------------+----------------+-----------+---------------------+---------------------+
| id | name | email                     | password       | isActive  | createdAt           | updatedAt           |
+----+------+---------------------------+----------------+-----------+---------------------+---------------------+
| 1  | 张三 | zhangsan@example.com      | hashed_******  | 1         | 2026-03-07 10:00:00 | 2026-03-07 10:00:00 |
+----+------+---------------------------+----------------+-----------+---------------------+---------------------+

六、ORM 库详解

6.1 ORM 概念

概念说明

ORM(Object-Relational Mapping,对象关系映射)是一种技术,用于在面向对象编程语言和关系型数据库之间建立映射关系。

ORM 的作用

code
ORM 的作用:

传统方式(SQL):
│
├── 编写 SQL 语句
│   └── INSERT INTO users (name, email) VALUES ('张三', '...')
│
├── 手动映射结果
│   └── const user = { id: result[0].id, name: result[0].name }
│
└── 数据库差异
    └── MySQL、PostgreSQL 语法不同

ORM 方式:
│
├── 操作对象
│   └── const user = await userRepository.save({ name: '张三', email: '...' })
│
├── 自动映射
│   └── 返回 User 对象
│
└── 数据库无关
    └── 切换数据库无需修改代码

优势:
• 不用写 SQL
• 代码可读性强
• 类型安全
• 数据库无关

6.2 主流 ORM 库对比

ORM 库支持数据库特点适用场景
TypeORMMySQL, PostgreSQL, MongoDB, SQLiteTypeScript 优先,装饰器语法NestJS 官方推荐
PrismaMySQL, PostgreSQL, MongoDB, SQLite类型安全,自动生成客户端现代项目
SequelizeMySQL, PostgreSQL, SQLite成熟稳定,社区活跃传统项目
MongooseMongoDBMongoDB 专用MongoDB 项目

6.3 TypeORM vs Prisma

TypeORM 示例

typescript
// ========== TypeORM ==========

// 定义实体
@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;
}

// 使用
const user = await this.userRepository.findOne({ where: { id: 1 } });

Prisma 示例

typescript
// ========== Prisma ==========

// 定义 Schema(schema.prisma)
model User {
  id   Int    @id @default(autoincrement())
  name String
}

// 使用
const user = await prisma.user.findUnique({ where: { id: 1 } });

七、最佳实践

7.1 DTO 最佳实践

typescript
//  推荐:DTO 与 Entity 分离

// Entity:数据库映射
@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  password: string; // 敏感字段
}

// DTO:API 接口
export class CreateUserDto {
  @IsString()
  name: string;

  @IsString()
  @MinLength(6)
  password: string;

  //  不包含 id
}

// Response DTO:响应过滤
export class ResponseUserDto {
  id: number;
  name: string;

  @Exclude()
  password: string; // 过滤敏感字段
}

7.2 DAO 最佳实践

typescript
//  推荐:Repository 模式

// 自定义 Repository 方法
@EntityRepository(User)
export class UsersRepository extends Repository<User> {
  async findActiveUsers(): Promise<User[]> {
    return this.createQueryBuilder('user')
      .where('user.isActive = :isActive', { isActive: true })
      .getMany();
  }
}

// Service 调用 Repository
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  async getActiveUsers() {
    return this.userRepository.find({ where: { isActive: true } });
  }
}

7.3 分层设计原则

code
分层设计原则:
│
├── Controller 层
│   ├── 只处理 HTTP 请求/响应
│   ├── 不包含业务逻辑
│   └── 调用 Service
│
├── Service 层
│   ├── 业务逻辑
│   ├── 数据处理
│   └── 调用 Repository
│
├── Repository 层(DAO)
│   ├── 数据库操作
│   ├── 不包含业务逻辑
│   └── 返回 Entity
│
└── DTO 层
    ├── 数据传输
    ├── 数据校验
    └── 数据过滤

八、常见问题与解决方案

问题原因解决方案
DTO 验证不生效未启用 ValidationPipe在 main.ts 中启用全局管道
DTO 字段过多未区分创建和更新创建 CreateUserDto 和 UpdateUserDto
密码泄露响应返回敏感字段使用 Response DTO 过滤字段
数据库操作重复未使用 Repository提取公共方法到 Repository
SQL 注入直接拼接 SQL使用 ORM 或参数化查询
类型不匹配DTO 与 Entity 字段不一致明确区分 DTO 和 Entity
循环依赖DTO 相互引用使用 OmitType 或 PartialType
更新覆盖字段更新 DTO 包含所有字段使用 PartialType 使字段可选

九、学习要点总结

核心要点

  1. MVC 架构:Model-View-Controller,分层架构模式
  2. DTO 本质:数据传输对象,重点在"传输",用于数据校验和筛选
  3. DAO 本质:数据访问对象,重点在"访问",封装数据库操作
  4. DTO vs DAO:DTO 对应 API 接口,DAO 对应数据库表
  5. ORM 库:TypeORM、Prisma 等,简化数据库操作

记忆技巧

code
快速记忆:

MVC:
• Model = 业务逻辑 + 数据
• View = 用户界面
• Controller = 路由分发

DTO vs DAO:
• DTO = Transfer = 传输 = API 接口
• DAO = Access = 访问 = 数据库

关键词:
• DTO → 传输 → Controller 层
• DAO → 访问 → 数据库层

行动建议

code
学习路径:
│
├──  第一阶段:理解概念(1 天)
│   ├── 理解 MVC 架构
│   ├── 理解 DTO 和 DAO 区别
│   └── 理解 ORM 库的作用
│
├──  第二阶段:实践练习(3 天)
│   ├── 创建 Entity 和 DTO
│   ├── 实现数据验证
│   └── 实现 CRUD 操作
│
└──  第三阶段:深入应用(持续)
    ├── 学习 TypeORM 高级特性
    ├── 学习 Prisma
    └── 性能优化

十、延伸学习资源

官方资源

练习建议

  1. 练习 1:创建一个完整的 CRUD 模块,包含 DTO 和 Entity
  2. 练习 2:实现数据验证,包含各种验证规则
  3. 练习 3:使用 Response DTO 过滤敏感字段
  4. 练习 4:对比 TypeORM 和 Prisma 的使用方式
  5. 练习 5:实现分页查询功能

延伸思考

code
思考题:
│
├──  为什么需要区分 DTO 和 Entity?
├──  DTO 和 VO(Value Object)的区别是什么?
├──  如何设计合理的分层架构?
├──  TypeORM 和 Prisma 如何选择?
└──  如何防止 SQL 注入?

附录:概念速查表

概念全称说明NestJS 实现
MVCModel-View-Controller架构模式Controller + Service + Entity
DTOData Transfer Object数据传输对象class-validator
DAOData Access Object数据访问对象TypeORM/Prisma
Entity-数据库实体@Entity()
Repository-数据仓库@EntityRepository()
ORMObject-Relational Mapping对象关系映射TypeORM/Prisma

笔记整理完成时间:2026-03-07
下一章节预告:NestJS 数据库实战(TypeORM)