数据库操作:mysql2(直接 SQL) vs TypeORM(ORM 方式)
大家好,今天我们来聊聊 Node.js 后端开发中的核心话题——数据库操作。在 NestJS 框架里,我们操作数据库主要有两种方式:一种是直接用 SQL 驱动(比如 mysql2)发送原生 SQL 语句,另一种是使用 ORM(对象关系映射)框架,比如 TypeORM,通过类和装饰器来映射数据库表。这两种方式各有千秋,今天我们就来详细对比一下,帮你根据项目需求做出选择。
一、文件里的原话
先回顾一下文件里是怎么说的:
Node 里操作数据库的两种方式:
一种是直接用 mysql2 连接数据库,发送 sql 来执行。
一种是用 ORM 库,比如 typeorm,它是基于 class 和 class 上的装饰器来声明和表的映射关系的,然后对表的增删改查就变成了对象的操作以及 save、find 等方法的调用。它会自动生成对应的 sql。
这段话非常精炼地概括了两种方式的核心区别。下面我们展开细说。
二、方式一:直接用 mysql2 发送 SQL
是什么?
mysql2 是 Node.js 中一个流行的 MySQL 客户端驱动,它是 mysql 包的升级版,支持 Promise、预处理语句等现代特性。用 mysql2 操作数据库,你需要自己写 SQL 语句,自己处理结果集,自己管理连接池。
在 NestJS 中怎么用?
你可以创建一个服务,封装数据库连接和查询方法。例如:
import { Injectable } from '@nestjs/common';
import * as mysql from 'mysql2/promise';
@Injectable()
export class DatabaseService {
private pool;
constructor() {
this.pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test',
waitForConnections: true,
connectionLimit: 10,
});
}
async query(sql: string, params?: any[]) {
const [rows] = await this.pool.execute(sql, params);
return rows;
}
}然后在你的服务里注入 DatabaseService,直接调用 query 方法执行 SQL:
@Injectable()
export class UserService {
constructor(private db: DatabaseService) {}
async findOne(id: number) {
const rows = await this.db.query('SELECT * FROM users WHERE id = ?', [id]);
return rows[0];
}
}优点
- 完全控制:你写什么 SQL,数据库就执行什么 SQL,没有中间层帮你“翻译”,性能最高。
- 学习曲线低:如果你已经熟悉 SQL,上手极快,不需要学习 ORM 的规则。
- 适合复杂查询:对于多表关联、子查询、窗口函数等复杂 SQL,原生 SQL 写起来更直接。
- 轻量:不需要额外的 ORM 依赖,项目更轻。
缺点
- 手动映射:查询结果返回的是普通的 JavaScript 对象,你需要手动把它转换成业务对象,或者手动处理关系。
- 类型安全差:TypeScript 类型需要自己定义,容易出错。
- 重复代码:每个查询都要写 SQL,表名、字段名硬编码,容易写错且难以维护。
- 数据库迁移麻烦:表结构变更需要手动维护 SQL 脚本。
三、方式二:使用 TypeORM(ORM 框架)
是什么?
TypeORM 是一个 TypeScript 友好的 ORM 框架,它让你用类和装饰器来定义实体(Entity),每个实体对应数据库中的一张表。然后你就可以通过 Repository 提供的 API 进行 CRUD 操作,而不需要写 SQL。
在 NestJS 中怎么用?
Nest 官方提供了 @nestjs/typeorm 包,可以无缝集成 TypeORM。
首先安装依赖:
npm install @nestjs/typeorm typeorm mysql2然后在 AppModule 中配置连接:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
entities: [User],
synchronize: true, // 开发环境自动建表,生产环境建议关闭
}),
],
})
export class AppModule {}定义一个实体类:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
age: number;
}在模块中引入实体的 Repository:
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UserService],
})
export class UserModule {}然后在服务里注入 Repository 进行操作:
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async findOne(id: number) {
return this.userRepository.findOne({ where: { id } });
}
async create(data: Partial<User>) {
const user = this.userRepository.create(data);
return this.userRepository.save(user);
}
}优点
- 开发效率高:不用写 SQL,所有操作通过方法调用完成,代码量少。
- 类型安全:实体类就是 TypeScript 类,字段类型明确,IDE 自动提示。
- 关系映射方便:可以用
@OneToMany、@ManyToOne等装饰器定义表关系,查询时自动关联。 - 迁移工具:TypeORM 提供了 CLI 工具,可以根据实体变化生成迁移脚本,方便版本控制。
- 数据库无关:切换数据库(比如从 MySQL 换到 PostgreSQL)只需要改配置,代码几乎不用动。
缺点
- 学习曲线:需要学习 TypeORM 的 API 和装饰器,理解它的工作原理。
- 性能开销:ORM 会做一些额外的操作,比如解析实体、生成 SQL,比原生 SQL 稍慢,但对于大多数应用来说可以忽略。
- 复杂查询不够直观:对于非常复杂的 SQL,用 ORM 的方法链可能会变得晦涩难懂,不如直接写 SQL。
- 灵活性受限:有些数据库特性 ORM 可能不支持,需要回退到原生查询。
四、如何选择?
其实没有绝对的好坏,主要看你的项目场景和团队偏好。
- 如果你喜欢完全掌控,SQL 功底扎实,或者项目中有大量复杂查询,直接用
mysql2会更灵活。 - 如果你追求开发效率,希望代码更易于维护,团队有 TypeScript 规范,TypeORM 是更好的选择。
- 混合使用:即使用了 TypeORM,遇到复杂查询也可以通过
createQueryBuilder或者直接执行原生 SQL,两种方式可以并存。Nest 里你可以同时使用 TypeORM 和原生查询。
文件里也提到两种方式,没有偏向哪一种,只是客观介绍。在实际 Nest 项目中,官方推荐 TypeORM 因为集成度高,但如果你有特殊需求,完全可以用 mysql2 自己封装。
五、总结
无论是用 mysql2 直接写 SQL,还是用 TypeORM 操作实体,都是 Node.js 操作 MySQL 的成熟方案。mysql2 给你自由,TypeORM 给你效率。在 NestJS 里,你可以根据项目阶段和复杂度灵活选择,甚至两者结合使用。关键是理解它们的适用场景,做出最适合项目的决策。

Comments | NOTHING