文章目录
Node.js 中 MySQL 数据模型实现指南:从自定义封装到主流 ORM 框架应用
在 Node.js 开发中,MySQL 数据库交互是常见需求。数据模型类作为应用程序与数据库之间的桥梁,其设计直接影响代码质量和开发效率。本文将从基础的自定义封装开始,逐步深入到 Sequelize 和 TypeORM 等主流 ORM 框架的应用,全面解析 Node.js 中 MySQL 数据模型类的声明方式。
一、自定义 MySQL 数据模型封装
对于简单项目,我们可以手动封装数据模型类,直接使用 mysql2 库进行数据库操作。
1. 基础数据库连接类
首先创建一个数据库连接基础类,封装连接逻辑:
const mysql = require('mysql2/promise');
class BaseModel {
constructor() {
// 数据库配置
this.config = {
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'test',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
};
// 连接池
this.pool = mysql.createPool(this.config);
}
/**
* 获取数据库连接
* @returns {Promise} 数据库连接对象
*/
async getConnection() {
try {
return await this.pool.getConnection();
} catch (error) {
console.error('获取数据库连接失败:', error);
throw error;
}
}
/**
* 释放数据库连接
* @param {Object} connection - 数据库连接对象
*/
releaseConnection(connection) {
if (connection) {
connection.release();
}
}
}
module.exports = BaseModel;
2. 具体数据模型类实现
基于基础连接类,实现具体的数据模型,例如用户模型:
const BaseModel = require('../db/BaseModel');
class User extends BaseModel {
constructor() {
super();
this.table = 'users';
}
/**
* 根据ID查询用户
* @param {number} id - 用户ID
* @returns {Promise<Object>} 用户信息
*/
async findById(id) {
let connection = null;
try {
connection = await this.getConnection();
const [rows] = await connection.execute(
`SELECT * FROM ${this.table} WHERE id = ?`,
[id]
);
return rows[0] || null;
} catch (error) {
console.error(`查询ID为${id}的用户失败:`, error);
throw error;
} finally {
this.releaseConnection(connection);
}
}
/**
* 查询所有用户
* @param {Object} options - 查询选项
* @param {number} options.limit - 限制数量
* @param {number} options.offset - 偏移量
* @returns {Promise<Array>} 用户列表
*/
async findAll(options = {}) {
const { limit = 10, offset = 0 } = options;
let connection = null;
try {
connection = await this.getConnection();
const [rows] = await connection.execute(
`SELECT * FROM ${this.table} LIMIT ? OFFSET ?`,
[limit, offset]
);
return rows;
} catch (error) {
console.error('查询所有用户失败:', error);
throw error;
} finally {
this.releaseConnection(connection);
}
}
/**
* 创建新用户
* @param {Object} userData - 用户数据
* @returns {Promise<Object>} 新增用户信息
*/
async create(userData) {
const { username, email, password, age } = userData;
let connection = null;
try {
connection = await this.getConnection();
const [result] = await connection.execute(
`INSERT INTO ${this.table} (username, email, password, age, created_at, updated_at)
VALUES (?, ?, ?, ?, NOW(), NOW())`,
[username, email, password, age]
);
// 返回新创建的用户
return this.findById(result.insertId);
} catch (error) {
console.error('创建用户失败:', error);
throw error;
} finally {
this.releaseConnection(connection);
}
}
/**
* 更新用户信息
* @param {number} id - 用户ID
* @param {Object} userData - 要更新的用户数据
* @returns {Promise<boolean>} 更新是否成功
*/
async update(id, userData) {
// 构建更新字段和参数
const fields = [];
const params = [];
Object.entries(userData).forEach(([key, value]) => {
if (key !== 'id') { // 不允许更新ID
fields.push(`${key} = ?`);
params.push(value);
}
});
// 添加更新时间和ID参数
fields.push('updated_at = NOW()');
params.push(id);
let connection = null;
try {
connection = await this.getConnection();
const [result] = await connection.execute(
`UPDATE ${this.table} SET ${fields.join(', ')} WHERE id = ?`,
params
);
return result.affectedRows > 0;
} catch (error) {
console.error(`更新ID为${id}的用户失败:`, error);
throw error;
} finally {
this.releaseConnection(connection);
}
}
/**
* 删除用户
* @param {number} id - 用户ID
* @returns {Promise<boolean>} 删除是否成功
*/
async delete(id) {
let connection = null;
try {
connection = await this.getConnection();
const [result] = await connection.execute(
`DELETE FROM ${this.table} WHERE id = ?`,
[id]
);
return result.affectedRows > 0;
} catch (error) {
console.error(`删除ID为${id}的用户失败:`, error);
throw error;
} finally {
this.releaseConnection(connection);
}
}
}
module.exports = new User();
3. 使用自定义数据模型
使用上述用户模型进行数据库操作:
// 使用用户模型
const userModel = require('./models/User');
async function userOperations() {
try {
// 创建用户
const newUser = await userModel.create({
username: 'john_doe',
email: 'john@example.com',
password: 'hashed_password',
age: 30
});
console.log('创建的新用户:', newUser);
// 查询用户
const user = await userModel.findById(newUser.id);
console.log('查询到的用户:', user);
// 更新用户
const updateSuccess = await userModel.update(newUser.id, {
age: 31,
email: 'john.updated@example.com'
});
console.log('更新成功:', updateSuccess);
// 查询所有用户
const users = await userModel.findAll({ limit: 5 });
console.log('用户列表:', users);
// 删除用户
const deleteSuccess = await userModel.delete(newUser.id);
console.log('删除成功:', deleteSuccess);
} catch (error) {
console.error('操作失败:', error);
}
}
userOperations();
二、使用 ORM 框架:Sequelize
对于复杂项目,使用 ORM(对象关系映射)框架可以大大提高开发效率。Sequelize 是 Node.js 中最流行的 ORM 框架之一。
1. Sequelize 配置与初始化
const { Sequelize } = require('sequelize');
// 初始化 Sequelize 实例
const sequelize = new Sequelize(
process.env.DB_NAME || 'test',
process.env.DB_USER || 'root',
process.env.DB_PASSWORD || '',
{
host: process.env.DB_HOST || 'localhost',
dialect: 'mysql',
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
},
logging: process.env.NODE_ENV === 'development' ? console.log : false
}
);
// 测试数据库连接
async function testConnection() {
try {
await sequelize.authenticate();
console.log('数据库连接已成功建立。');
} catch (error) {
console.error('无法连接到数据库:', error);
}
}
testConnection();
module.exports = sequelize;
2. 使用 Sequelize 定义数据模型
const { DataTypes, Model } = require('sequelize');
const sequelize = require('../db/sequelize');
const bcrypt = require('bcrypt');
class User extends Model {
// 实例方法:验证密码
async isValidPassword(password) {
return await bcrypt.compare(password, this.password);
}
// 实例方法:转换为JSON时隐藏敏感信息
toJSON() {
const values = { ...this.get() };
delete values.password;
return values;
}
}
User.init({
// 定义模型属性
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
validate: {
notEmpty: true,
len: [3, 50]
}
},
email: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING(100),
allowNull: false,
validate: {
notEmpty: true,
len: [6, 100]
}
},
age: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {
min: 0,
max: 150
}
},
createdAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
updatedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
// 其他模型选项
sequelize, // 我们需要传递连接实例
modelName: 'User', // 模型名称
tableName: 'users', // 表名
timestamps: true, // 自动添加 createdAt 和 updatedAt 字段
underscored: true, // 使用下划线命名法
hooks: {
// 密码加密钩子
beforeCreate: async (user) => {
if (user.password) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
},
beforeUpdate: async (user) => {
if (user.changed('password')) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
}
}
});
module.exports = User;
3. Sequelize 模型关联
Sequelize 强大之处在于处理表之间的关系,例如用户和文章的一对多关系:
const { DataTypes, Model } = require('sequelize');
const sequelize = require('../db/sequelize');
const User = require('./SequelizeUser');
class Article extends Model {}
Article.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
title: {
type: DataTypes.STRING(200),
allowNull: false,
validate: {
notEmpty: true
}
},
content: {
type: DataTypes.TEXT,
allowNull: false
},
userId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: User,
key: 'id'
},
field: 'user_id'
}
}, {
sequelize,
modelName: 'Article',
tableName: 'articles',
timestamps: true,
underscored: true
});
// 建立关联:一个用户可以有多个文章
User.hasMany(Article, {
foreignKey: 'userId',
as: 'articles'
});
// 建立关联:一篇文章属于一个用户
Article.belongsTo(User, {
foreignKey: 'userId',
as: 'author'
});
module.exports = Article;
4. 使用 Sequelize 模型进行操作
const User = require('./models/SequelizeUser');
const Article = require('./models/Article');
async function ormOperations() {
try {
// 创建用户
const user = await User.create({
username: 'jane_smith',
email: 'jane@example.com',
password: 'secure_password',
age: 28
});
console.log('创建的用户:', user.toJSON());
// 创建文章
const article = await Article.create({
title: 'Sequelize 教程',
content: '这是一篇关于 Sequelize ORM 的教程文章...',
userId: user.id
});
console.log('创建的文章:', article.toJSON());
// 查询用户及其文章
const userWithArticles = await User.findByPk(user.id, {
include: [{ model: Article, as: 'articles' }]
});
console.log('用户及其文章:', userWithArticles.toJSON());
// 查询所有用户
const users = await User.findAll({
limit: 10,
order: [['createdAt', 'DESC']]
});
console.log('用户列表:', users.map(u => u.toJSON()));
// 更新用户
user.age = 29;
await user.save();
console.log('更新后的用户年龄:', user.age);
// 删除文章
await article.destroy();
console.log('文章已删除');
} catch (error) {
console.error('ORM 操作失败:', error);
}
}
ormOperations();
三、使用 ORM 框架:TypeORM
TypeORM 是另一个流行的 ORM 框架,特别适合与 TypeScript 一起使用,提供了类似 JPA/Hibernate 的体验。
1. TypeORM 配置与初始化
module.exports = {
type: 'mysql',
host: process.env.DB_HOST || 'localhost',
port: 3306,
username: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'test',
synchronize: process.env.NODE_ENV === 'development', // 开发环境自动同步数据库结构
logging: process.env.NODE_ENV === 'development',
entities: [
'src/entities/**/*.ts',
'dist/entities/**/*.js'
],
migrations: [
'src/migrations/**/*.ts',
'dist/migrations/**/*.js'
],
subscribers: [
'src/subscribers/**/*.ts',
'dist/subscribers/**/*.js'
],
cli: {
entitiesDir: 'src/entities',
migrationsDir: 'src/migrations',
subscribersDir: 'src/subscribers'
},
poolSize: 10
};
初始化数据库连接:
const { createConnection } = require('typeorm');
const config = require('../ormconfig');
let connection;
async function getConnection() {
if (!connection) {
try {
connection = await createConnection(config);
console.log('数据库连接已成功建立');
} catch (error) {
console.error('数据库连接失败:', error);
throw error;
}
}
return connection;
}
module.exports = { getConnection };
2. 使用 TypeORM 定义实体
const { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, BeforeInsert, BeforeUpdate } = require('typeorm');
const bcrypt = require('bcrypt');
const Article = require('./Article');
@Entity({ name: 'users' })
class User {
@PrimaryGeneratedColumn()
id;
@Column({
type: 'varchar',
length: 50,
unique: true,
nullable: false
})
username;
@Column({
type: 'varchar',
length: 100,
unique: true,
nullable: false
})
email;
@Column({
type: 'varchar',
length: 100,
nullable: false
})
password;
@Column({
type: 'int',
nullable: true
})
age;
@CreateDateColumn({ name: 'created_at' })
createdAt;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt;
@OneToMany(() => Article, article => article.author)
articles;
@BeforeInsert()
@BeforeUpdate()
async hashPassword() {
if (this.password) {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
}
}
async isValidPassword(password) {
return await bcrypt.compare(password, this.password);
}
toJSON() {
const { password, ...user } = this;
return user;
}
}
module.exports = User;
3. TypeORM 实体关联
const { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } = require('typeorm');
const User = require('./User');
@Entity({ name: 'articles' })
class Article {
@PrimaryGeneratedColumn()
id;
@Column({
type: 'varchar',
length: 200,
nullable: false
})
title;
@Column({
type: 'text',
nullable: false
})
content;
@Column({ name: 'user_id' })
userId;
@ManyToOne(() => User, user => user.articles)
@JoinColumn({ name: 'user_id' })
author;
@CreateDateColumn({ name: 'created_at' })
createdAt;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt;
}
module.exports = Article;
4. 使用 TypeORM 进行数据库操作
const { getConnection } = require('./db/connection');
const User = require('./entities/User');
const Article = require('./entities/Article');
async function typeormOperations() {
try {
const connection = await getConnection();
const userRepository = connection.getRepository(User);
const articleRepository = connection.getRepository(Article);
// 创建用户
const user = new User();
user.username = 'bob_williams';
user.email = 'bob@example.com';
user.password = 'typeorm_password';
user.age = 35;
const savedUser = await userRepository.save(user);
console.log('创建的用户:', savedUser.toJSON());
// 创建文章
const article = new Article();
article.title = 'TypeORM 教程';
article.content = '这是一篇关于 TypeORM 的教程文章...';
article.author = savedUser;
const savedArticle = await articleRepository.save(article);
console.log('创建的文章:', savedArticle);
// 查询用户及其文章
const userWithArticles = await userRepository.findOne({
where: { id: savedUser.id },
relations: ['articles']
});
console.log('用户及其文章:', userWithArticles.toJSON());
// 查询所有用户
const users = await userRepository.find({
take: 10,
order: { createdAt: 'DESC' }
});
console.log('用户列表:', users.map(u => u.toJSON()));
// 更新用户
savedUser.age = 36;
await userRepository.save(savedUser);
console.log('更新后的用户年龄:', savedUser.age);
// 删除文章
await articleRepository.remove(savedArticle);
console.log('文章已删除');
} catch (error) {
console.error('TypeORM 操作失败:', error);
}
}
typeormOperations();
四、自定义封装 vs Sequelize vs TypeORM 特性功能对比
| 特性 | 自定义封装 | Sequelize | TypeORM |
|---|---|---|---|
| 学习曲线 | 低 | 中等 | 中高 |
| 代码量 | 多(需手动实现所有功能) | 少 | 少 |
| 灵活性 | 极高(完全可控) | 中等(框架约束) | 中等(框架约束) |
| 功能丰富度 | 基础功能(需自行扩展) | 丰富 | 丰富 |
| TypeScript 支持 | 需手动定义类型 | 良好(有类型定义) | 原生支持(最佳体验) |
| 关联处理 | 需手动编写 JOIN 语句 | 内置强大的关联系统 | 内置强大的关联系统,类似 JPA |
| 迁移工具 | 需自行实现 | 内置迁移系统 | 内置迁移系统 |
| 查询能力 | 原生 SQL,灵活但繁琐 | 强大的查询构建器 + 原始 SQL | 强大的查询构建器 + 原始 SQL |
| 钩子/生命周期 | 需自行实现 | 丰富的钩子系统 | 丰富的装饰器钩子系统 |
| 自动验证 | 需自行实现 | 内置验证系统 | 内置验证系统 |
| 社区活跃度 | 无(自建) | 非常活跃 | 活跃 |
| 文档完善度 | 无(自建) | 完善 | 完善 |
| 适用场景 | 小型项目、简单查询、特殊需求 | 中大型项目、快速开发 | TypeScript 项目、企业级应用 |
| 性能 | 高(可针对性优化) | 中(略低于原生) | 中(略低于原生) |
| 维护成本 | 高(需自行维护所有功能) | 低(框架维护) | 低(框架维护) |
五、注意事项
-
安全性考虑
- 始终使用参数化查询或 ORM 框架防止 SQL 注入攻击
- 敏感数据(如密码)必须加密存储,绝不能明文保存
- 实现适当的输入验证,无论是自定义封装还是使用 ORM
- 遵循最小权限原则配置数据库用户权限
-
连接管理
- 使用连接池管理数据库连接,避免频繁创建和关闭连接
- 合理设置连接池大小,根据应用负载调整
- 确保所有连接在操作完成后正确释放,防止连接泄漏
-
事务处理
- 对于多步数据库操作,使用事务确保数据一致性
- 了解并正确实现各 ORM 框架的事务机制
- 确保事务在发生错误时能够正确回滚
-
性能优化
- 合理使用索引提升查询性能
- 避免 SELECT *,只查询需要的字段
- 使用适当的分页机制处理大量数据
- 了解 ORM 框架的查询缓存机制并合理利用
-
迁移与版本控制
- 使用 ORM 提供的迁移工具管理数据库结构变更
- 为所有数据库变更建立版本控制,便于团队协作和环境部署
- 测试环境与生产环境的数据库迁移流程保持一致
-
日志与监控
- 记录数据库操作日志,特别是错误日志
- 监控慢查询,及时优化
- 生产环境中避免打印敏感信息到日志
-
环境配置
- 不同环境(开发、测试、生产)使用不同的数据库配置
- 通过环境变量管理配置,避免硬编码敏感信息
- 开发环境禁用自动同步数据库结构到生产环境
六、总结
本文详细介绍了 Node.js 中 MySQL 数据模型的三种实现方式:自定义封装、Sequelize 和 TypeORM,每种方式都有其适用场景:
-
自定义封装:适合小型项目或对数据库操作有特殊需求的场景。优点是灵活性高、性能好、学习成本低;缺点是需要手动实现大量功能,代码量大,维护成本高。
-
Sequelize:作为最成熟的 Node.js ORM 之一,适合大多数中大型项目。提供了丰富的功能和良好的社区支持,API 设计直观,同时支持 JavaScript 和 TypeScript。
-
TypeORM:特别适合使用 TypeScript 的项目,提供了类似 JPA 的编程体验,对实体关系的处理非常强大,迁移系统完善,适合需要复杂数据模型的企业级应用。
选择合适的方案应考虑项目规模、团队熟悉度、性能需求和长期维护成本。小型项目可以从自定义封装开始,随着项目增长再迁移到 ORM 框架;中大型项目建议直接采用成熟的 ORM 框架,以提高开发效率和代码可维护性。
无论选择哪种方式,核心原则是确保代码的可读性、可维护性和安全性,同时兼顾性能需求。良好的数据模型设计是构建稳定、高效 Node.js 应用的基础。


被折叠的 条评论
为什么被折叠?



