第七部分:第四节 - 在 NestJS 应用中集成 MySQL (使用 TypeORM):结构化厨房的原材料管理系统

发布于:2025-06-02 ⋅ 阅读:(30) ⋅ 点赞:(0)

在 NestJS 这样一个结构化的框架中,我们更倾向于使用 ORM (Object-Relational Mapper) 来与关系型数据库交互。ORM 就像中央厨房里一套智能化的原材料管理系统,它将数据库中的表格和行映射到我们熟悉的对象和类的实例。我们可以使用面向对象的方式来操作数据,ORM 会负责将其转换为底层的 SQL 语句。这大大提高了开发效率和代码的可读性,并且 TypeORM 对 TypeScript 有很好的支持。

ORM 的优势:

  1. 面向对象的操作: 使用对象而不是 SQL 语句来操作数据库。
  2. 提高开发效率: 减少手写重复的 SQL 代码。
  3. 更好的类型安全: 结合 TypeScript,可以在编译时检查数据结构的匹配。
  4. 数据库无关性(一定程度): 更换数据库类型(如从 MySQL 换到 PostgreSQL)时,改动可能较小。

TypeORM 简介:

TypeORM 是一个功能强大的 ORM,支持多种数据库(MySQL, PostgreSQL, SQLite, MongoDB 等),并且对 TypeScript 和 JavaScriptES6+/ES7+ 有很好的支持,与 NestJS 集成非常方便。

安装 TypeORM 和 MySQL 连接器:

在你的 NestJS 项目目录中:

npm install typeorm mysql2 @nestjs/typeorm @types/node --save
# 或者 yarn add typeorm mysql2 @nestjs/typeorm @types/node
  • typeorm: TypeORM 核心库
  • mysql2: MySQL 数据库驱动
  • @nestjs/typeorm: NestJS 与 TypeORM 集成的模块
  • @types/node: Node.js 类型定义(如果项目还没有)

在 NestJS 中配置 TypeORM 模块:

通常在根模块 (AppModule) 中配置数据库连接。

src/app.module.ts (修改):

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; // 导入 TypeOrmModule
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module'; // 假设你已经有了 UsersModule

@Module({
  imports: [
    TypeOrmModule.forRoot({ // 配置数据库连接
      type: 'mysql',      // 数据库类型
      host: 'localhost',
      port: 3306,
      username: 'your_mysql_username',
      password: 'your_mysql_password',
      database: 'my_webapp_db',
      entities: [__dirname + '/**/*.entity{.ts,.js}'], // 实体文件路径
      synchronize: true, // 生产环境中不要使用这个!它会在每次应用启动时同步数据库结构。开发环境方便。
    }),
    UsersModule, // 导入你的用户模块
    // ... 导入其他模块
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

__dirname + '/**/*.entity{.ts,.js}' 告诉 TypeORM 在当前文件所在目录及其子目录下查找所有以 .entity.ts.entity.js 结尾的文件作为实体文件。

定义实体 (Entities):

实体是用于映射数据库表的 TypeScript 类。使用 TypeORM 提供的装饰器来定义表的结构。

创建一个文件 src/users/entities/user.entity.ts

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

@Entity('users') // @Entity() 装饰器指定这个类映射到哪个数据库表 (如果省略,默认为类名的小写)
export class User {
  @PrimaryGeneratedColumn() // @PrimaryGeneratedColumn() 装饰器指定这是主键,并且是自动递增的
  id: number;

  @Column({ length: 50, unique: true }) // @Column() 装饰器指定这是一个普通列,可以配置属性 (长度、唯一性等)
  username: string;

  @Column({ unique: true, nullable: true }) // nullable: true 表示该列可以为 NULL
  email: string;

  @Column({ nullable: true })
  age: number;

  @CreateDateColumn() // @CreateDateColumn() 装饰器指定这是一个创建时间列,通常自动生成
  created_at: Date;
}

仓库 (Repositories):

Repository 是 TypeORM 提供的一个模式,用于对特定实体进行数据操作。每个实体都有一个对应的 Repository。Repository 提供了常用的数据操作方法(查找、保存、删除等),以及 Query Builder 等更高级的查询工具。这就像仓库管理员提供了一套标准的工具(Repository)来获取特定类型的原材料(Entity)。

在 Service 中使用 Repository:

在 Service 中,通过依赖注入获取对应实体的 Repository 实例,然后使用 Repository 的方法进行数据操作。

修改 src/users/users.module.ts,注册 User 实体:

// src/users/users.module.ts (修改)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; // 导入 TypeOrmModule
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './entities/user.entity'; // 导入 User 实体

@Module({
  imports: [
    TypeOrmModule.forFeature([User]) // TypeOrmModule.forFeature() 用于在功能模块中注册实体
  ],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService] // 如果其他模块需要 UsersService,需要导出
})
export class UsersModule {}

修改 src/users/users.service.ts,注入 UserRepository 并使用它:

// src/users/users.service.ts (修改)
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; // 导入 InjectRepository
import { Repository } from 'typeorm'; // 导入 Repository
import { User } from './entities/user.entity'; // 导入 User 实体
import { CreateUserDto } from './dto/create-user.dto'; // 导入 DTO
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UsersService {
  // 通过 @InjectRepository() 装饰器注入 User 实体对应的 Repository
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  // 获取所有用户
  findAll(): Promise<User[]> { // 返回 Promise<User[]>
    return this.usersRepository.find(); // 使用 Repository 的 find 方法
  }

  // 获取单个用户
  findOne(id: number): Promise<User | undefined> { // 返回 Promise<User | undefined>
    return this.usersRepository.findOne({ where: { id } }); // 使用 findOne 方法根据 ID 查找
  }

  // 创建用户
  async create(createUserDto: CreateUserDto): Promise<User> {
    const newUser = this.usersRepository.create(createUserDto); // 创建一个实体对象 (在内存中)
    return this.usersRepository.save(newUser); // 保存到数据库,返回保存后的实体 (包含生成的 ID)
  }

  // 更新用户
  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    const userToUpdate = await this.usersRepository.findOne({ where: { id } });
    if (!userToUpdate) {
      throw new NotFoundException(`找不到 ID 为 ${id} 的用户`);
    }
    // 合并更新数据到实体对象
    this.usersRepository.merge(userToUpdate, updateUserDto);
    // 保存更新后的实体到数据库
    return this.usersRepository.save(userToUpdate);
  }

  // 删除用户
  async remove(id: number): Promise<void> {
    const deleteResult = await this.usersRepository.delete(id); // 使用 delete 方法

    if (deleteResult.affected === 0) { // deleteResult.affected 表示受影响的行数
       throw new NotFoundException(`找不到 ID 为 ${id} 的用户,无法删除`);
    }
  }
}

注意 Service 中的方法现在返回的是 Promise,因为数据库操作是异步的。控制器中调用 Service 方法时,需要使用 await

小结: TypeORM 是在 NestJS 中集成 MySQL 的常用 ORM。它通过实体 (Entities) 将数据库表映射为对象,通过仓库 (Repositories) 提供面向对象的数据操作方法。通过 TypeOrmModule.forRootTypeOrmModule.forFeature 在 NestJS 中配置和注册 TypeORM。在 Service 中注入 Repository 并使用其方法进行数据访问是 NestJS + TypeORM 的标准模式,这使得数据层逻辑清晰、类型安全且易于测试。

练习:

  1. 确保你的 NestJS 项目已安装 TypeORM 和 mysql2
  2. 修改 app.module.ts,配置 TypeORM 连接你的 MySQL 数据库 my_bookstore_db,并指定实体文件路径。
  3. 为你的“书籍”或“任务”资源,在相应的模块中创建一个实体文件 (book.entity.tstask.entity.ts),使用 TypeORM 装饰器映射到数据库表。
  4. 修改相应的模块文件 (books.module.tstasks.module.ts),使用 TypeOrmModule.forFeature() 注册你的实体。
  5. 修改相应的 Service 文件 (books.service.tstasks.service.ts),注入实体对应的 Repository。
  6. 使用 Repository 的方法(find, findOne, create, save, delete)替换之前使用内存数组实现的 CRUD 逻辑。确保方法返回 Promise,并在 Service 中处理找不到数据的情况(抛出 NotFoundException)。
  7. 修改相应的 Controller 文件,确保在调用 Service 方法时使用了 await
  8. 运行应用,并使用 Postman 等工具测试你的 API,验证数据是否正确地存储和读取自 MySQL 数据库。