在 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 数据库。