第七部分:第四节 - 在 NestJS 应用中集成 MySQL (使用 TypeORM):结构化厨房的原材料管理系统
在 NestJS 这样一个结构化的框架中,我们更倾向于使用 ORM (Object-Relational Mapper) 来与关系型数据库交互。ORM 就像中央厨房里一套智能化的原材料管理系统,它将数据库中的表格和行映射到我们熟悉的对象和类的实例。我们可以使用面向对象的方式来操作数据,ORM 会负责将其转换为底层的 SQL 语句。这大大提高了开发效率和代码的可读性,并且 TypeORM 对 TypeScript 有很好的支持。
ORM 的优势:
- 面向对象的操作: 使用对象而不是 SQL 语句来操作数据库。
- 提高开发效率: 减少手写重复的 SQL 代码。
- 更好的类型安全: 结合 TypeScript,可以在编译时检查数据结构的匹配。
- 数据库无关性(一定程度): 更换数据库类型(如从 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 表示该列可以为 NULLemail: 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 实体对应的 Repositoryconstructor(@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.forRoot
和 TypeOrmModule.forFeature
在 NestJS 中配置和注册 TypeORM。在 Service 中注入 Repository 并使用其方法进行数据访问是 NestJS + TypeORM 的标准模式,这使得数据层逻辑清晰、类型安全且易于测试。
练习:
- 确保你的 NestJS 项目已安装 TypeORM 和
mysql2
。 - 修改
app.module.ts
,配置 TypeORM 连接你的 MySQL 数据库my_bookstore_db
,并指定实体文件路径。 - 为你的“书籍”或“任务”资源,在相应的模块中创建一个实体文件 (
book.entity.ts
或task.entity.ts
),使用 TypeORM 装饰器映射到数据库表。 - 修改相应的模块文件 (
books.module.ts
或tasks.module.ts
),使用TypeOrmModule.forFeature()
注册你的实体。 - 修改相应的 Service 文件 (
books.service.ts
或tasks.service.ts
),注入实体对应的 Repository。 - 使用 Repository 的方法(
find
,findOne
,create
,save
,delete
)替换之前使用内存数组实现的 CRUD 逻辑。确保方法返回 Promise,并在 Service 中处理找不到数据的情况(抛出NotFoundException
)。 - 修改相应的 Controller 文件,确保在调用 Service 方法时使用了
await
。 - 运行应用,并使用 Postman 等工具测试你的 API,验证数据是否正确地存储和读取自 MySQL 数据库。