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进程终止前)。

七、封装类的优势与最佳实践

核心优势

  1. 安全性

    • 强制参数化查询,防止SQL注入
    • 更新/删除操作必须带where条件,避免误操作全表
  2. 易用性

    • 统一返回格式,简化错误处理
    • 无需手动编写重复SQL,通过对象参数快速实现CRUD
  3. 性能

    • 基于连接池管理连接,减少IO开销
    • 分页查询自动优化,避免一次性加载大量数据
  4. 可扩展性

    • 模块化设计,便于添加新方法(如批量插入、联表查询)
    • 事务支持复杂业务场景

最佳实践

  1. 配置管理
    生产环境中通过dotenv管理数据库配置,避免硬编码:

    // 配置示例(.env文件)
    DB_HOST=localhost
    DB_USER=root
    DB_PASS=your_password
    DB_NAME=test_db
    
  2. 错误处理
    业务层需捕获封装类返回的错误,并转化为用户友好提示:

    const result = await db.getOne('users', { id: 100 });
    if (!result.success) {
      return res.status(500).json({ message: '获取用户失败: ' + result.error });
    }
    
  3. 复杂查询
    对于联表查询等复杂场景,直接使用query方法编写原生SQL,平衡灵活性与开发效率。

  4. 连接池配置
    根据服务器性能和并发量调整connectionLimit(建议:每核CPU配置10-20个连接)。

总结

本文详细拆解了基于mysql2/promise的通用数据库封装类,涵盖从连接池初始化到CRUD操作、事务处理的全流程方法。该封装通过抽象重复工作、统一处理安全与错误问题,大幅提升了Node.js数据库操作的开发效率。

核心价值在于:将开发者从繁琐的SQL编写中解放出来,同时内置安全机制降低出错风险。实际使用中,可根据项目需求扩展方法(如添加批量操作、复杂查询支持),使其更贴合业务场景。掌握这类封装的设计思想,也是后端开发者从“实现功能”向“构建可靠系统”进阶的关键一步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值