当前位置: 首页 > article >正文

「一起学后端」Nest.js + MySQL 查询方法教学文档

Nest.js + MySQL 查询方法教学文档

文章目录

    • Nest.js + MySQL 查询方法教学文档
      • 1. 基础 CRUD 操作
        • 1.1 创建记录
        • 1.2 查询记录
        • 1.3 更新记录
        • 1.4 删除记录
      • 2. 复杂查询
        • 2.1 分页与排序
        • 2.2 关联查询(JOIN)
        • 2.3 聚合查询
        • 2.4 子查询
      • 3. 高级操作
        • 3.1 事务处理
        • 3.2 TypeORM 中实现事务的两种方式
        • 3.3 事务中的错误处理
        • 3.4 原生 SQL 查询
        • 3.5 软删除与恢复
      • 4. 装饰器(@)使用大全
        • 4.1 TypeORM 实体装饰器
        • 4.2 DTO 验证装饰器
        • 4.3 装饰器组合用法
      • 5. 实用技巧
        • 5.1 错误处理增强
        • 5.2 索引优化实战
        • 5.3 索引的作用
        • 5.4 索引的代价
        • 5.5 何时创建索引?
        • 5.6 TypeORM 中创建索引
        • 5.7 索引命名与高级配置
      • 6. 完整项目结构
      • 7. 完整示例代码
        • 实体类示例
        • 服务类示例
      • 总结


1. 基础 CRUD 操作

1.1 创建记录
// 创建一个用户对象(此时还未保存到数据库)
const user = this.userRepository.create({
  name: 'John',
  age: 25, 
});

// 将创建的对象保存到数据库
await this.userRepository.save(user);

// 批量创建示例
const users = this.userRepository.create([
  { name: 'Alice', age: 30 },
  { name: 'Bob', age: 28 },
]);
await this.userRepository.save(users);

1.2 查询记录
// 查询所有用户
const allUsers = await this.userRepository.find();

// 条件查询:年龄大于20的用户,带分页(第1页,每页10条)
const filteredUsers = await this.userRepository.find({
  where: { age: MoreThan(20) }, // MoreThan 是 TypeORM 的比较操作符
  skip: 0,  // 跳过0条 = 第一页
  take: 10, // 取10条
});

// 查询单个用户(根据ID)
const user = await this.userRepository.findOneBy({ id: 1 });

// 查询并按创建时间倒序排列
const sortedUsers = await this.userRepository.find({
  order: { createdAt: 'DESC' } // DESC 降序,ASC 升序
});

1.3 更新记录
// 直接更新(适合简单操作)
await this.userRepository.update(
  { id: 1 },  // 更新条件(找到id=1的用户)
  { age: 26 } // 更新内容
);

// 先查询后更新(推荐:可以处理复杂业务逻辑)
const user = await this.userRepository.findOneBy({ id: 1 });
user.age = 26; // 修改属性
await this.userRepository.save(user); // 保存修改

1.4 删除记录
// 物理删除(直接从数据库删除)
await this.userRepository.delete({ id: 1 });

// 软删除(需要实体类添加 @DeleteDateColumn)
await this.userRepository.softDelete({ id: 1 });
// 软删除后,数据不会被真正删除,而是标记删除时间

2. 复杂查询

2.1 分页与排序
// 分页查询用户列表(带总数统计)
const [users, total] = await this.userRepository.findAndCount({
  where: { isActive: true }, // 查询条件:仅活跃用户
  skip: (page - 1) * limit,  // 跳过前 N 条(计算页码偏移量)
  take: limit,               // 每页数量
  order: { createdAt: 'DESC' }, // 按创建时间倒序排列
});

// 返回结果示例:
// {
//   data: users,  // 当前页数据
//   total: 100,   // 总数据量
//   page: 2,      // 当前页码
//   totalPages: 10 // 总页数
// }

2.2 关联查询(JOIN)
// 方式1:自动关联查询(需在实体类中预先定义关联关系)
const orders = await this.orderRepository.find({
  relations: ['user'], // 加载关联的 user 数据
});

// 方式2:使用 QueryBuilder 手动关联(更灵活)
const orders = await this.orderRepository
  .createQueryBuilder('order')          // 主表别名
  .leftJoinAndSelect('order.user', 'user') // 关联 user 表,别名为 user
  .where('user.age > :age', { age: 18 })   // 条件:用户年龄大于18岁
  .getMany();                          // 获取最终结果

2.3 聚合查询
// 计算用户平均年龄
const result = await this.userRepository
  .createQueryBuilder('user')        // 使用别名 'user' 代表用户表
  .select('AVG(user.age)', 'avgAge') // 计算年龄平均值,结果命名为 avgAge
  .getRawOne();                      // 获取原始数据(不经过实体转换)

console.log(result.avgAge); // 直接通过别名访问结果


// 统计活跃用户数量
const activeUserCount = await this.userRepository.count({ 
  where: { isActive: true } 
});

2.4 子查询
// 步骤1:构建子查询(获取年龄大于20的用户ID)
const subQuery = this.userRepository
  .createQueryBuilder('user')
  .select('user.id')
  .where('user.age > :age', { age: 20 });

// 步骤2:在主查询中使用子查询
const users = await this.userRepository
  .createQueryBuilder('user')
  .where(`user.id IN (${subQuery.getQuery()})`) // 嵌入子查询
  .setParameters(subQuery.getParameters())     // 传递子查询参数(防SQL注入)
  .getMany();

3. 高级操作

3.1 事务处理

想象你要完成以下操作:

  1. 从A账户扣除100元
  2. 向B账户增加100元

如果步骤1成功但步骤2失败,A账户的钱会凭空消失!事务的作用就是确保这两个步骤绑定成一个原子操作:要么全部成功,要么全部回滚(撤销所有操作)。

当需要同时执行多个数据库操作,并确保它们全部成功或全部失败时(比如转账操作),使用事务。

// 使用 DataSource 对象管理事务
await this.dataSource.transaction(async (transactionManager) => {
  // 在事务中创建用户
  const user = transactionManager.create(User, { name: 'Alice' });
  await transactionManager.save(user);

  // 在事务中更新用户关联的 profile
  await transactionManager.update(
    Profile, 
    { userId: user.id }, 
    { status: 'active' }
  );
});
// 如果过程中任何一步出错,所有操作自动回滚

3.2 TypeORM 中实现事务的两种方式

方式1:transaction 方法(推荐)
直接使用 TypeORM 的 transaction API,自动管理事务的生命周期。

import { DataSource } from 'typeorm';

// 在 Service 类中注入 DataSource
constructor(private dataSource: DataSource) {}

// 示例:转账事务
async transferMoney(fromUserId: number, toUserId: number, amount: number) {
  await this.dataSource.transaction(async (transactionManager) => {
    // 注意:事务内的所有操作必须使用 transactionManager,而不是普通 repository
    
    // 1. 扣款
    await transactionManager.decrement(
      User, 
      { id: fromUserId }, 
      'balance', 
      amount
    );

    // 2. 收款
    await transactionManager.increment(
      User, 
      { id: toUserId }, 
      'balance', 
      amount
    );

    // 3. 记录交易日志(事务内操作)
    await transactionManager.insert(TransactionLog, {
      fromUser: fromUserId,
      toUser: toUserId,
      amount,
    });
  });

  // 如果以上任何一步出错,所有操作自动回滚
}

关键点解释:

代码部分说明
transactionManager事务管理器对象,在事务内必须用它代替普通 repository 执行数据库操作
decrement / incrementTypeORM 内置的快捷方法,用于字段的增减操作
自动错误回滚如果事务内抛出任何错误(如数据库错误、手动 throw),所有操作自动撤销

方式2:QueryRunner 手动控制(更灵活)
适合需要精细控制事务(如中途提交或回滚)的场景。

async manualTransactionDemo() {
  // 1. 创建 QueryRunner
  const queryRunner = this.dataSource.createQueryRunner();
  
  try {
    // 2. 建立数据库连接并启动事务
    await queryRunner.connect();
    await queryRunner.startTransaction();

    // 3. 执行事务操作(通过 queryRunner.manager)
    await queryRunner.manager.update(User, { id: 1 }, { balance: 100 });
    await queryRunner.manager.delete(Order, { userId: 1 });

    // 4. 手动提交事务(只有提交后操作才生效)
    await queryRunner.commitTransaction();
  } catch (error) {
    // 5. 出错时回滚
    await queryRunner.rollbackTransaction();
    throw new Error('事务执行失败');
  } finally {
    // 6. 释放连接(重要!)
    await queryRunner.release();
  }
}

两种方式对比:

方式优点缺点适用场景
transaction自动管理连接和事务,代码简洁灵活性较低大多数常规场景
QueryRunner完全手动控制,灵活性高代码量大,需手动释放连接需要分段提交或复杂事务控制

3.3 事务中的错误处理

确保回滚的两种方法

  1. 自动回滚:事务内任何未捕获的异常(如数据库错误、手动 throw)都会触发回滚。
  2. 手动回滚:在 catch 块中调用 rollbackTransaction()(仅限 QueryRunner 方式)。
// 示例:在事务中检查业务条件
await this.dataSource.transaction(async (manager) => {
  const user = await manager.findOneBy(User, { id: 1 });
  
  // 业务逻辑错误也触发回滚
  if (user.balance < 100) {
    throw new Error('余额不足'); // 抛出错误 → 自动回滚
  }

  // ...其他操作
});

实际应用场景

  1. 金融交易:转账、支付、退款
  2. 订单系统:创建订单 → 扣减库存 → 生成物流单
  3. 用户注册:创建用户 → 初始化配置 → 发送欢迎消息(注意:外部API调用需额外处理)

注意事项

  1. 避免长事务:事务执行时间过长会导致数据库锁表,影响性能。
  2. 事务嵌套:TypeORM 支持嵌套事务,但不同数据库行为可能不同(需谨慎使用)。
  3. 隔离级别:默认使用数据库的隔离级别,可通过 SET TRANSACTION ISOLATION LEVEL 调整。

3.4 原生 SQL 查询

当 TypeORM 的 API 无法满足复杂查询需求时,可以直接写 SQL。

// 执行原生 SQL 查询(参数化查询防止 SQL 注入)
const rawData = await this.userRepository.query(
  'SELECT * FROM users WHERE age > ? AND is_active = ?', 
  [18, true] // 参数按 ? 顺序传递
);

// 示例结果:返回原始数据库结果(字段名与数据库一致)
// [ { id: 1, name: 'John', age: 25, is_active: 1 }, ... ]

3.5 软删除与恢复

软删除不会真正删除数据,而是通过标记时间字段实现“伪删除”。

步骤 1:在实体类中添加 @DeleteDateColumn

import { DeleteDateColumn } from 'typeorm';

@Entity()
export class User {
  // ...其他字段

  @DeleteDateColumn({ name: 'deleted_at' }) // 字段名可自定义
  deletedAt: Date; // 当调用 softDelete 时,此字段会自动记录删除时间
}

步骤 2:操作示例

// 软删除用户(id=1)
await this.userRepository.softDelete({ id: 1 });

// 恢复被软删除的用户
await this.userRepository.restore({ id: 1 });

// 查询时默认排除已软删除的数据
const activeUsers = await this.userRepository.find();

// 查询包含已删除的数据
const allData = await this.userRepository.find({ 
  withDeleted: true, // 显式包含已删除数据
});

4. 装饰器(@)使用大全

以下整理 Nest.js 项目中常见的装饰器用法,涵盖 TypeORM 实体定义DTO 验证 两大场景:


4.1 TypeORM 实体装饰器

用于定义数据库表结构的装饰器,需在实体类(Entity Class)中使用。

装饰器名称用途示例代码备注
@Entity()标记一个类为数据库实体@Entity('users')可指定表名,如不写则默认使用类名(小写)
@Column()定义普通字段@Column({ type: 'varchar', length: 50 })支持配置类型、长度、是否可为空等
@PrimaryGeneratedColumn定义自增主键@PrimaryGeneratedColumn()默认类型为 int,可选 uuid 类型
@CreateDateColumn自动记录记录创建时间@CreateDateColumn({ name: 'create_time' })由数据库自动填充时间
@UpdateDateColumn自动记录记录更新时间@UpdateDateColumn()每次更新时自动刷新
@DeleteDateColumn软删除标记字段(需配合 softDelete 使用)@DeleteDateColumn()删除时自动填充时间,未删除时为 null
@Index()为字段创建数据库索引@Index(['email'], { unique: true })提升查询效率,unique 表示唯一约束
@OneToMany / @ManyToOne定义表关联关系(一对多/多对一)@OneToMany(() => Order, (order) => order.user)
@ManyToOne(() => User)
需在关联的实体类中双向定义

4.2 DTO 验证装饰器

用于请求参数验证(搭配 class-validator),需在 DTO 类中使用。

装饰器名称用途示例代码可配置选项
@IsString()验证字段是否为字符串@IsString({ message: '必须为字符串' })message: 自定义错误消息
@Length()验证字符串长度范围@Length(3, 20, { message: '长度3-20字符' })min: 最小长度
max: 最大长度
@IsEmail()验证是否为合法邮箱格式@IsEmail({}, { message: '邮箱格式错误' })可配置邮箱格式规则
@IsInt()验证是否为整数@IsInt()-
@Min() / @Max验证数字最小值/最大值@Min(18) @Max(100)配置数值范围
@IsOptional()允许字段为 nullundefined@IsOptional()通常用于可选参数
@IsNotEmpty()验证字段不能为空(空字符串、null、undefined)@IsNotEmpty()确保必填项
@IsBoolean()验证是否为布尔值@IsBoolean()-
@IsDate()验证是否为日期格式@IsDate()-

4.3 装饰器组合用法
// 示例:用户注册 DTO
export class CreateUserDto {
  @IsString()
  @Length(3, 20)
  name: string;

  @IsEmail()
  email: string;

  @IsInt()
  @Min(18)
  age: number;

  @IsOptional()
  @IsString()
  address?: string;
}

5. 实用技巧

5.1 错误处理增强

在全局过滤器(Global Filter)中统一处理数据库错误:

// 示例:捕获 TypeORM 错误并返回友好提示
@Catch(QueryFailedError)
export class DatabaseExceptionFilter implements ExceptionFilter {
  catch(exception: QueryFailedError, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse();
    
    // 处理重复键错误(如唯一约束冲突)
    if (exception.driverError?.code === 'ER_DUP_ENTRY') {
      response.status(400).json({ message: '数据已存在' });
    } else {
      response.status(500).json({ message: '数据库操作失败' });
    }
  }
}

5.2 索引优化实战

合理使用索引可显著提升查询性能:

// 在用户实体中为常用查询字段添加索引
@Entity()
@Index(['email'], { unique: true }) // 唯一索引
@Index(['age', 'createdAt'])       // 复合索引
export class User {
  // ...
}

5.3 索引的作用

索引相当于数据库的“目录”,能快速定位数据,核心作用

  • 加速查询:特别是 WHEREJOINORDER BY 等操作
  • 保证唯一性:唯一索引防止重复数据(如用户名、手机号)
  • 减少全表扫描:避免逐行查找,降低数据库负载

5.4 索引的代价
  • 占用存储空间:索引需要额外的磁盘空间
  • 影响写入速度:每次增删改操作需同步更新索引
  • 维护成本:不合理的索引会拖慢性能

5.5 何时创建索引?
场景示例字段索引类型推荐
高频查询条件WHERE age > 18普通索引
唯一性约束字段email手机号唯一索引
排序或分组字段ORDER BY created_at DESC普通索引
多条件联合查询WHERE age > 18 AND city='北京'复合索引
外键关联字段user_id(关联查询)普通索引

5.6 TypeORM 中创建索引

单字段索引
直接在实体类字段上使用 @Index

@Entity()
export class User {
  @Column()
  @Index() // 为 email 字段创建普通索引
  email: string;

  @Column()
  @Index({ unique: true }) // 创建唯一索引
  username: string;
}

复合索引
为多个字段联合创建索引(注意字段顺序):

@Entity()
@Index(['lastName', 'firstName']) // 联合索引:lastName + firstName
export class User {
  @Column()
  firstName: string;

  @Column()
  lastName: string;
}

5.7 索引命名与高级配置
@Index('IDX_USER_AGE', ['age'], { unique: false, where: 'age > 18' })
@Entity()
export class User {
  @Column()
  age: number;
}
参数说明
'IDX_USER_AGE'自定义索引名称(推荐统一命名风格)
['age']索引字段(数组形式,复合索引填多个字段)
where条件索引(仅对符合条件的数据创建索引,减少索引大小)

6. 完整项目结构

src/
├── modules/
│   └── user/
│       ├── dto/
│       │   ├── create-user.dto.ts      # 创建用户DTO(带验证装饰器)
│       │   └── update-user.dto.ts
│       ├── entities/
│       │   └── user.entity.ts          # 用户实体(TypeORM装饰器)
│       ├── user.controller.ts          # API路由(@Get、@Post等)
│       └── user.service.ts             # 业务逻辑(包含所有查询方法)
├── common/
│   ├── filters/                        # 全局异常过滤器
│   └── decorators/                     # 自定义装饰器
└── app.module.ts                       # 主模块(数据库配置、模块注册)

7. 完整示例代码

实体类示例
// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, Index } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

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

  @Column()
  name: string;

  @Column()
  age: number;

  @CreateDateColumn()
  createdAt: Date;
}
服务类示例
// user.service.ts
@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  // 查询用户列表(分页)
  async getUsers(page: number, limit: number) {
    const [data, total] = await this.userRepository.findAndCount({
      skip: (page - 1) * limit,
      take: limit,
    });
    return { data, total, page, totalPages: Math.ceil(total / limit) };
  }
}

总结

通过 TypeORM 和 Nest.js 的结合,可以高效实现:

  • 安全的数据操作(事务、软删除)
  • 清晰的代码结构(实体、DTO、服务分层)
  • 强大的参数验证(装饰器组合)

建议在实际开发中:

  1. 优先使用 TypeORM 的高级查询方法(如 QueryBuilder)
  2. 始终验证用户输入(DTO + 装饰器)
  3. 合理使用索引优化查询性能

http://www.kler.cn/a/598677.html

相关文章:

  • 【设计模式】常用的设计模式详解
  • 【机器人】论文分析 ASGrasp 通用透明物体重建、6-DoF抓取预测
  • 基于 C++ 类的程序设计模式与应用研究
  • OpenCV平滑处理:图像去噪与模糊技术详解
  • 【Node.js入门笔记10---http 模块】
  • S32K144入门笔记(二十三):FTM宏观介绍
  • Unity Shader 的编程流程和结构
  • 机房布局和布线的最佳实践:如何打造高效、安全的机房环境
  • 基于SpringBoot+Vue3实现的宠物领养管理平台功能七
  • hadoop集群配置-创建xsync脚本命令
  • Django框架视图与路由(一)
  • Ruby语言的控制结构
  • 计算机二级web易错点(6)-选择题
  • 图像处理篇:图像预处理——从数据到模型的桥梁
  • Linux__之__基于UDP的Socket编程网络通信
  • 智慧座椅无线手机充电器:开启便捷充电新时代
  • SQLServer列转行操作及union all用法
  • 雪花算法生成分布式唯一ID
  • 从零构建大语言模型全栈开发指南:第一部分:数学与理论基础-1.2.3层归一化(LayerNorm)与残差连接的原理与代码实现
  • 用selenium+ChromeDriver豆瓣电影 肖申克的救赎 短评爬取(pycharm 爬虫)