文章目录
- NodeJS MySQL2通用数据库CRUD操作封装:核心方法全解析与实战指南
NodeJS MySQL2通用数据库操作封装:核心方法全解析与实战指南
基于mysql2/promise的数据库操作封装是Node.js后端开发的基础组件,良好的封装能显著提升开发效率并降低出错风险。本文将系统拆解通用封装类的每个核心方法,详细说明其功能、参数、返回值及使用场景,帮助开发者深入理解数据库操作的封装逻辑与最佳实践。
一、封装类整体设计与初始化
1. 类结构概述
封装类MySQLDB基于连接池模式设计,核心目标是:
- 简化CRUD操作的调用方式
- 统一错误处理与返回格式
- 内置安全机制(防SQL注入、误操作保护)
- 支持事务与复杂查询场景
2. 构造函数(初始化连接池)
/**
* 初始化数据库连接池
* @param {Object} config - 数据库配置
* @param {string} config.host - 主机地址(必填)
* @param {string} config.user - 用户名(必填)
* @param {string} config.password - 密码(必填)
* @param {string} config.database - 数据库名(必填)
* @param {number} [config.port=3306] - 端口(默认3306)
* @param {number} [config.connectionLimit=10] - 连接池大小(默认10)
*/
constructor(config) {
this.pool = mysql.createPool({
host: config.host,
user: config.user,
password: config.password,
database: config.database,
port: config.port || 3306,
connectionLimit: config.connectionLimit || 10,
waitForConnections: true, // 连接池满时等待(而非报错)
queueLimit: 0 // 等待队列无上限
});
// 初始化时自动测试连接
this.testConnection().catch(err => {
console.error('数据库初始化失败:', err.message);
});
}
核心作用:
创建并配置MySQL连接池,避免频繁创建/关闭连接的性能损耗。连接池会自动管理连接的复用,是高并发场景的必备优化。
二、基础连接与查询方法
1. 连接测试(testConnection)
/**
* 测试数据库连接是否可用
* @returns {Promise<boolean>} 连接成功返回true,失败抛出错误
*/
async testConnection() {
try {
const connection = await this.pool.getConnection();
connection.release(); // 释放连接回池
console.log('数据库连接成功');
return true;
} catch (err) {
console.error('数据库连接失败:', err.message);
throw err; // 抛出错误供上层捕获
}
}
使用场景:
- 应用启动时验证数据库配置是否正确
- 监控系统定期检查数据库可用性
示例:
// 初始化后验证连接
db.testConnection().then(() => {
console.log('系统可正常访问数据库');
}).catch(() => {
console.error('数据库连接失败,请检查配置');
});
2. 通用SQL执行(query)
/**
* 执行任意SQL语句(核心基础方法)
* @param {string} sql - 待执行的SQL语句(支持参数化查询)
* @param {Array} [params=[]] - SQL参数(替换SQL中的?占位符)
* @returns {Promise<Object>} 结果对象
* - success: 布尔值(操作是否成功)
* - data: 查询结果(成功时)/ null(失败时)
* - error: 错误信息(失败时)/ null(成功时)
*/
async query(sql, params = []) {
try {
const [results] = await this.pool.execute(sql, params);
return {
success: true,
data: results,
error: null
};
} catch (err) {
console.error('SQL执行错误:', err.message, 'SQL:', sql, 'Params:', params);
return {
success: false,
data: null,
error: err.message
};
}
}
核心作用:
所有数据库操作的底层基础,负责执行SQL并统一处理结果与错误。支持参数化查询(?占位符),自动防止SQL注入。
使用场景:
- 执行复杂的自定义SQL(如联表查询、聚合查询)
- 封装类未覆盖的特殊操作
示例:
// 执行自定义联表查询
const sql = `
SELECT u.name, o.order_no
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.id = ?
`;
const result = await db.query(sql, [100]);
if (result.success) {
console.log('查询结果:', result.data);
}
三、查询类方法(Read)
1. 查询单条记录(getOne)
/**
* 查询单条记录(自动限制返回1条)
* @param {string} table - 表名
* @param {Object} where - 查询条件({ 字段名: 值 } 或 { 字段名: { op: 运算符, val: 值 } })
* @param {Array<string>} [fields=[]] - 需返回的字段(默认返回所有字段)
* @returns {Promise<Object>} 结果对象(data为单条记录或null)
*/
async getOne(table, where, fields = []) {
const { sql, params } = this.buildSelectSql(table, where, fields, 1);
const result = await this.query(sql, params);
if (result.success) {
result.data = result.data.length > 0 ? result.data[0] : null;
}
return result;
}
核心作用:
简化“查询单条记录”场景(如用户详情、订单详情),自动添加LIMIT 1,避免返回多余数据。
条件格式说明:
- 基础格式:
{ id: 100 }→WHERE id = 100 - 带运算符:
{ age: { op: '>', val: 18 } }→WHERE age > 18 - 数组格式(IN查询):
{ status: [1, 2] }→WHERE status IN (1, 2)
示例:
// 查询ID为100的用户
const result = await db.getOne(
'users',
{ id: 100 },
['id', 'name', 'email'] // 只返回指定字段
);
if (result.success) {
console.log('用户详情:', result.data); // { id: 100, name: '...', ... }
}
2. 查询多条记录(getList)
/**
* 查询多条记录(支持排序、分页)
* @param {string} table - 表名
* @param {Object} [where={}] - 查询条件(格式同getOne)
* @param {Array<string>} [fields=[]] - 需返回的字段
* @param {Object} [options={}] - 可选参数
* - orderBy: 排序规则({ 字段名: 'asc'|'desc' })
* - limit: 限制返回条数
* - offset: 偏移量(配合limit实现分页)
* @returns {Promise<Object>} 结果对象(data为数组)
*/
async getList(table, where = {}, fields = [], options = {}) {
const { sql, params } = this.buildSelectSql(
table, where, fields, options.limit, options.offset, options.orderBy
);
return this.query(sql, params);
}
核心作用:
查询满足条件的多条记录,支持排序和分页,适用于列表展示场景(如用户列表、商品列表)。
示例:
// 查询状态为1的前10个用户,按创建时间降序
const result = await db.getList(
'users',
{ status: 1 }, // 条件
['id', 'name', 'created_at'], // 字段
{
orderBy: { created_at: 'desc' }, // 排序
limit: 10 // 限制10条
}
);
3. 分页查询(getPage)
/**
* 分页查询(自动计算总数和总页数)
* @param {string} table - 表名
* @param {Object} [where={}] - 查询条件
* @param {number} [page=1] - 页码(默认第1页)
* @param {number} [pageSize=10] - 每页条数(默认10)
* @param {Array<string>} [fields=[]] - 需返回的字段
* @param {Object} [options={}] - 可选参数(主要用于排序)
* @returns {Promise<Object>} 结果对象
* - data: { list: 列表数据, total: 总条数, page: 当前页, pageSize: 每页条数, totalPages: 总页数 }
*/
async getPage(table, where = {}, page = 1, pageSize = 10, fields = [], options = {}) {
const offset = (page - 1) * pageSize;
// 查询当前页数据
const listResult = await this.getList(table, where, fields, {
...options,
limit: pageSize,
offset
});
if (!listResult.success) return listResult;
// 查询总条数
const countResult = await this.getCount(table, where);
if (!countResult.success) return countResult;
return {
success: true,
data: {
list: listResult.data,
total: countResult.data,
page: parseInt(page, 10),
pageSize: parseInt(pageSize, 10),
totalPages: Math.ceil(countResult.data / pageSize)
},
error: null
};
}
核心作用:
简化分页逻辑,自动计算总条数、总页数,避免手动编写分页SQL的重复工作,适用于分页列表场景(如后台管理系统)。
示例:
// 查询第2页用户数据,每页20条
const result = await db.getPage(
'users',
{ status: 1 }, // 条件
2, // 页码
20, // 每页条数
['id', 'name', 'email'],
{ orderBy: { id: 'asc' } }
);
if (result.success) {
console.log('分页数据:', result.data);
/*
{
list: [...], // 20条数据
total: 156, // 总条数
page: 2,
pageSize: 20,
totalPages: 8 // 总页数
}
*/
}
4. 统计记录数(getCount)
/**
* 统计满足条件的记录总数
* @param {string} table - 表名
* @param {Object} [where={}] - 查询条件(格式同getOne)
* @returns {Promise<Object>} 结果对象(data为数字类型的总数)
*/
async getCount(table, where = {}) {
const { sql, params } = this.buildCountSql(table, where);
const result = await this.query(sql, params);
if (result.success) {
result.data = result.data[0]?.count || 0; // 提取COUNT(*)结果
}
return result;
}
核心作用:
快速获取满足条件的记录总数,常用于分页、数据统计场景(如“共有120条评论”)。
示例:
// 统计状态为1的用户总数
const result = await db.getCount('users', { status: 1 });
if (result.success) {
console.log('有效用户总数:', result.data); // 数字类型
}
四、数据操作类方法(Create/Update/Delete)
1. 插入记录(insert)
/**
* 插入单条记录
* @param {string} table - 表名
* @param {Object} data - 插入的数据({ 字段名: 值 })
* @returns {Promise<Object>} 结果对象(data包含insertId)
*/
async insert(table, data) {
const fields = Object.keys(data);
const placeholders = fields.map(() => '?'); // 生成与字段数量匹配的?占位符
const values = fields.map(key => data[key]); // 提取字段值数组
const sql = `INSERT INTO \`${table}\` (\`${fields.join('`, `')}\`) VALUES (${placeholders.join(', ')})`;
const result = await this.query(sql, values);
if (result.success) {
result.data = { insertId: result.data.insertId }; // 返回插入的ID
}
return result;
}
核心作用:
简化插入操作,自动生成INSERT语句,无需手动拼接字段和值,避免SQL语法错误。
示例:
// 插入新用户
const result = await db.insert('users', {
name: '张三',
email: 'zhangsan@example.com',
age: 25
});
if (result.success) {
console.log('新用户ID:', result.data.insertId); // 新插入记录的自增ID
}
2. 更新记录(update)
/**
* 更新记录(强制要求where条件,防止全表更新)
* @param {string} table - 表名
* @param {Object} data - 待更新的数据({ 字段名: 值 })
* @param {Object} where - 更新条件(格式同getOne)
* @returns {Promise<Object>} 结果对象(data包含affectedRows)
*/
async update(table, data, where) {
// 安全校验:不允许无where条件的更新
if (Object.keys(where).length === 0) {
return {
success: false,
data: null,
error: '更新操作必须指定where条件'
};
}
// 构建SET子句(如`name` = ?, `age` = ?)
const setClauses = Object.keys(data).map(key => `\`${key}\` = ?`);
const { whereClause, whereParams } = this.buildWhereClause(where);
const sql = `UPDATE \`${table}\` SET ${setClauses.join(', ')} WHERE ${whereClause}`;
const params = [...Object.values(data), ...whereParams]; // 合并数据值和条件值
const result = await this.query(sql, params);
if (result.success) {
result.data = { affectedRows: result.data.affectedRows }; // 返回受影响的行数
}
return result;
}
核心作用:
安全更新记录,强制要求where条件(防止误操作更新全表),自动处理字段转义与参数化。
示例:
// 更新ID为100的用户信息
const result = await db.update(
'users',
{ name: '张三更新', age: 26 }, // 待更新数据
{ id: 100 } // 条件(只更新ID=100的记录)
);
if (result.success) {
console.log('更新影响行数:', result.data.affectedRows);
}
3. 删除记录(delete)
/**
* 删除记录(强制要求where条件,防止全表删除)
* @param {string} table - 表名
* @param {Object} where - 删除条件(格式同getOne)
* @returns {Promise<Object>} 结果对象(data包含affectedRows)
*/
async delete(table, where) {
// 安全校验:不允许无where条件的删除
if (Object.keys(where).length === 0) {
return {
success: false,
data: null,
error: '删除操作必须指定where条件'
};
}
const { whereClause, whereParams } = this.buildWhereClause(where);
const sql = `DELETE FROM \`${table}\` WHERE ${whereClause}`;
const result = await this.query(sql, whereParams);
if (result.success) {
result.data = { affectedRows: result.data.affectedRows }; // 返回受影响的行数
}
return result;
}
核心作用:
安全删除记录,强制要求where条件(防止误删全表数据),适用于单条或批量删除场景。
示例:
// 删除ID为100的用户
const result = await db.delete('users', { id: 100 });
if (result.success) {
console.log('删除影响行数:', result.data.affectedRows);
}
五、事务处理(transaction)
/**
* 执行事务操作(确保多步操作的原子性)
* @param {Function} callback - 事务回调函数,接收connection参数
* - 回调中可通过connection.execute()执行SQL
* - 回调返回结果将作为事务结果的data
* - 回调抛出错误时,事务会自动回滚
* @returns {Promise<Object>} 事务结果对象
*/
async transaction(callback) {
let connection;
try {
// 从连接池获取专属连接
connection = await this.pool.getConnection();
// 开始事务
await connection.beginTransaction();
// 执行回调中的事务逻辑(回调需返回Promise)
const result = await callback(connection);
// 提交事务
await connection.commit();
connection.release(); // 释放连接回池
return {
success: true,
data: result,
error: null
};
} catch (err) {
// 发生错误时回滚事务
if (connection) {
await connection.rollback();
connection.release();
}
console.error('事务执行失败:', err.message);
return {
success: false,
data: null,
error: err.message
};
}
}
核心作用:
处理需要原子性的多步操作(如转账、订单创建),确保所有步骤要么全部成功,要么全部失败,避免数据不一致。
示例:
// 转账事务(从A账户扣钱,向B账户加钱)
const result = await db.transaction(async (connection) => {
const fromId = 1;
const toId = 2;
const amount = 100;
// 步骤1:查询A账户余额
const [fromAccount] = await connection.execute(
'SELECT balance FROM accounts WHERE user_id = ?',
[fromId]
);
// 校验余额
if (fromAccount[0].balance < amount) {
throw new Error('余额不足'); // 抛出错误将触发回滚
}
// 步骤2:扣减A账户余额
await connection.execute(
'UPDATE accounts SET balance = balance - ? WHERE user_id = ?',
[amount, fromId]
);
// 步骤3:增加B账户余额
await connection.execute(
'UPDATE accounts SET balance = balance + ? WHERE user_id = ?',
[amount, toId]
);
// 事务成功,返回结果
return { message: '转账成功', amount };
});
if (result.success) {
console.log('事务结果:', result.data);
} else {
console.error('转账失败:', result.error);
}
六、SQL构建工具方法(私有)
1. 构建查询语句(buildSelectSql)
/**
* 构建SELECT语句(内部使用)
* @private
* @returns {Object} { sql: 生成的SQL语句, params: 参数数组 }
*/
buildSelectSql(table, where, fields, limit, offset, orderBy) {
const selectFields = fields.length > 0 ? `\`${fields.join('`, `')}\`` : '*';
const { whereClause, whereParams } = this.buildWhereClause(where);
const whereSql = whereClause ? `WHERE ${whereClause}` : '';
// 构建排序语句
let orderBySql = '';
if (orderBy && typeof orderBy === 'object') {
const orderClauses = Object.entries(orderBy).map(
([field, direction]) => `\`${field}\` ${direction.toUpperCase()}`
);
orderBySql = `ORDER BY ${orderClauses.join(', ')}`;
}
// 构建分页语句
let limitSql = '';
if (limit !== undefined) {
limitSql = `LIMIT ${limit}`;
if (offset !== undefined) {
limitSql += ` OFFSET ${offset}`;
}
}
const sql = `SELECT ${selectFields} FROM \`${table}\` ${whereSql} ${orderBySql} ${limitSql}`;
return { sql, params: whereParams };
}
2. 构建计数语句(buildCountSql)
/**
* 构建COUNT语句(内部使用)
* @private
* @returns {Object} { sql: 生成的SQL语句, params: 参数数组 }
*/
buildCountSql(table, where) {
const { whereClause, whereParams } = this.buildWhereClause(where);
const whereSql = whereClause ? `WHERE ${whereClause}` : '';
const sql = `SELECT COUNT(*) AS count FROM \`${table}\` ${whereSql}`;
return { sql, params: whereParams };
}
3. 构建WHERE子句(buildWhereClause)
/**
* 构建WHERE子句(内部使用)
* @private
* @returns {Object} { whereClause: WHERE子句字符串, whereParams: 参数数组 }
*/
buildWhereClause(where) {
if (Object.keys(where).length === 0) {
return { whereClause: '', whereParams: [] };
}
const whereClauses = [];
const whereParams = [];
Object.entries(where).forEach(([field, value]) => {
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
// 带运算符的条件(如{ age: { op: '>', val: 18 } })
const { op, val } = value;
whereClauses.push(`\`${field}\` ${op} ?`);
whereParams.push(val);
} else if (Array.isArray(value)) {
// IN查询(如{ id: [1,2,3] })
whereClauses.push(`\`${field}\` IN (${value.map(() => '?').join(', ')})`);
whereParams.push(...value);
} else {
// 相等条件(如{ name: '张三' })
whereClauses.push(`\`${field}\` = ?`);
whereParams.push(value);
}
});
return {
whereClause: whereClauses.join(' AND '),
whereParams
};
}
4. 关闭连接池(close)
/**
* 关闭数据库连接池(应用退出时调用)
*/
async close() {
await this.pool.end();
console.log('数据库连接池已关闭');
}
使用场景:
应用正常退出时调用,释放资源(如Node.js进程终止前)。
七、封装类的优势与最佳实践
核心优势
-
安全性:
- 强制参数化查询,防止SQL注入
- 更新/删除操作必须带where条件,避免误操作全表
-
易用性:
- 统一返回格式,简化错误处理
- 无需手动编写重复SQL,通过对象参数快速实现CRUD
-
性能:
- 基于连接池管理连接,减少IO开销
- 分页查询自动优化,避免一次性加载大量数据
-
可扩展性:
- 模块化设计,便于添加新方法(如批量插入、联表查询)
- 事务支持复杂业务场景
最佳实践
-
配置管理:
生产环境中通过dotenv管理数据库配置,避免硬编码:// 配置示例(.env文件) DB_HOST=localhost DB_USER=root DB_PASS=your_password DB_NAME=test_db -
错误处理:
业务层需捕获封装类返回的错误,并转化为用户友好提示:const result = await db.getOne('users', { id: 100 }); if (!result.success) { return res.status(500).json({ message: '获取用户失败: ' + result.error }); } -
复杂查询:
对于联表查询等复杂场景,直接使用query方法编写原生SQL,平衡灵活性与开发效率。 -
连接池配置:
根据服务器性能和并发量调整connectionLimit(建议:每核CPU配置10-20个连接)。
总结
本文详细拆解了基于mysql2/promise的通用数据库封装类,涵盖从连接池初始化到CRUD操作、事务处理的全流程方法。该封装通过抽象重复工作、统一处理安全与错误问题,大幅提升了Node.js数据库操作的开发效率。
核心价值在于:将开发者从繁琐的SQL编写中解放出来,同时内置安全机制降低出错风险。实际使用中,可根据项目需求扩展方法(如添加批量操作、复杂查询支持),使其更贴合业务场景。掌握这类封装的设计思想,也是后端开发者从“实现功能”向“构建可靠系统”进阶的关键一步。
327

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



