在 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 表示该列可以为 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.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 数据库。