封装 MySQL(一)做个help先

Node.js 环境里面访问 MySQL 的默认方式,采用了古老的回调方式,这样很容易产生回调地狱。那么如何避免呢?
这里介绍一种基于 Promise 的封装方式,可以避免回调地狱,并且支持事务访问。

技术栈

  • Node.js V14.16.0
  • MySQL V 8.0.15
  • ES6
  • 基于对象

思路和结构

MySQL.help

实现基础访问

建立一个help,实现基础功能。

 /**
 * 基于 promise 封装 MySQL 的基本操作,支持事务
 * * 创建数据库连接
 * * 事务相关
 * * 提交SQL给数据库执行,得到返回结果
 * * 共用函数
 */
class MySQLHelp {
  constructor (info) {
    // 创建普通连接的参数
    this._info = {
      host: 'localhost',
      port: '3306',
      user: 'root',
      password: '',
      database: ''
    }
    // 创建池子的参数
    this._infoTran = {
      host: 'localhost',
      port: '3306',
      user: 'root',
      password: '',
      database: '',
      connectionLimit: 20
    }
    
    // 加参数
    Object.assign(this._info, info)
    Object.assign(this._infoTran, info)
   
    // 预定一个池子,用于事务
    this.pool = null
    
    console.log(' ★ 初始化:不使用事务!')
    // 不使用事务,开启连接获得数据库对象,其实也支持事务
    this.db = mysql.createConnection(this._info)
    //启动连接
    this.db.connect((err) => {
      if(err) {
        console.error('连接数据发生错误:', err)
      } else {
        console.log('★ [MySQL 已经打开数据库,threadId:]', this.db.threadId)
      }
    })
  }

非事务模式

默认不使用事务,内部创建一个链接数据库的对象,用于实现各种操作。
好吧,其实只有一个操作,提交SQL到数据库,然后等待返回结果。

其实我试了一下,这个默认的连接对象,也可以使用事务,只是看到网上提到事务,都是用 pool 的方式。
所以事务用的连接对象也采用从 pool 里面获取,因为对比了一下两种连接对象,确实不太一样。

提交SQL给MySQL执行

  /**
   * 把 SQL 提交给数据库。支持事务。
   * @param { string } sql sql语句
   * @param { array } params 需要的参数,数组形式
   * @param { connection } cn 如果使用事务的话,需要传递一个链接对象进来
   * @returns Promise 形式
   */
  query(sql, params=[], cn = this.db) {
    const myPromise = new Promise((resolve, reject) => {
      // 把SQL语句提交给数据库执行,然后等待回调
      cn.query(sql, params, (err, res, fields) => {
        if (err) { //失败
          // 如果开始事务,自动回滚
          if (cn !== this.db) {
            cn.rollback((err) => {
              console.log('执行SQL失败,数据回滚:', err)
            })
          }
          reject(err)
          return
        }
        resolve(res)
      })
    })
    return myPromise
  }
  • sql
    要执行的SQL语句,建议参数化的SQL。

  • params
    SQL语句的参数,强烈建议采用参数化的方式,因为可以避免SQL注入攻击。

  • cn
    连接对象,如果不用事务,则使用默认的内部连接对象;
    如果使用事务,则需要传递一个链接对象进来,以便于区分不同的事务操作。

  • 异常默认回滚
    如果出错,在开启事务的情况下,默认回滚事务。

MySQL的基础操作非常简单,就这一个。
其他的就是事务如何开启、提交,SQL如何管理的问题。
下面来一一介绍。

实现事务

建立池子,获取连接对象,然后把这个连接对象作为参数,这样就可以非常灵活的实现各种各样的操作了。

建立连接池,获取连接对象

建立连接池不是回调函数,但是从中获取连接对象却是一个回调函数,所以只好封装一个内部函数,便于后续操作。

  /**
   * 在池子里面获取一个链接,创建对象
   */
  _poolCreateConnection() {
    const myPromise = new Promise((resolve, reject) => {
      console.log('初始化:使用事务')
      // 如果没有池子则创建一个。好在不是异步
      if (this.pool === null) {
        this.pool = mysql.createPool(this._infoTran)
      }
      // 从池子里面获取一个链接对象,这个是异步
      this.pool.getConnection((err, connection) => {
        if(err) {
          reject(err)
        } else {
          resolve(connection)
        }
      })
    })
    return myPromise
  }

开启事务

因为开启事务又是一个异步操作,所以只好继续写个内部函数,实现开始事务的功能,然后再做一个对外的函数实现事务操作。

内部事务函数

  /**
   * 内部开启事务
   */
  _beginStran(_cn) {
    const myPromise = new Promise((resolve, reject) => {
      _cn.beginTransaction((err) => {
        if (err) { //失败
          console.log('[★ ★ MySQL 开启事务时报错:] --- ', err)
          reject(err)
          return
        }
        console.log('[★ MySQL 事务已经开启:] - ')
        resolve(_cn)
      })
    })
    return myPromise
  }

对外的事务函数:

  /**
   * 开启一个事务,Promise 的方式
   */
  begin() {
    const myPromise = new Promise((resolve, reject) => {
      console.log('★ 开启事务,promise 模式')
      this._poolCreateConnection().then((_cn) => {
        // 开启事务
        this._beginStran(_cn)
          .then(() => {resolve(_cn)}) // 返回连接对象
          .catch((err) => {reject(err)})
      })
    })
    return myPromise
  }

其实一个有三个函数,两个内部函数一个对外的函数。
这么做是为了代码可以更简洁一些,看起来好看一点,否则各种回调地狱,保证你想哭。

await 方式

我们是否可以用 await 的方式实现一下开启事务的代码呢?尝试了一下,也是可以的,虽然这么做没有什么实际意义。

  /**
   * 开启事务,await 的方式
   */
  async beginTransaction() {
    console.log('★ 开启事务,await 模式')
    let _cn = null
    try {
      _cn = await this._poolCreateConnection()
      await this._beginStran(_cn)
    } catch(e) {
      console.log('async 开启事务出错:', e)
    }
    return _cn
  }

没有了回调方式,看起来是不是舒服多了?
为啥说没啥实际意义呢?因为这种方式,要求调用者也必须使用 await 的方式,有点强制性。
而上面那个函数(begin)既可以用 Promise 的方式,也可以用 await 的方式,即满足需求也比较灵活。

提交事务

开启事务,执行各种操作后,需要提交事务,那么我们再做一个提交事务的功能。

  /**
   * 提交一个事务
   * @param { connection } cn 开启事务时创建的连接对象
  */
  commit(_cn) {
    const myPromise = new Promise((resolve, reject) => {
      // 提交事务
      _cn.commit((err) => {
        if(err) {
          console.log('事务提交失败', err)
          reject(err)
        } else {
          resolve()
        }
      })
    })
    return myPromise
  }

关闭连接、归还连接对象

如果没有开始事务,直接关闭连接即可,如果开启事务,需要把链接对象放回池子。二者写法有点差别。

  /**
   * 关闭数据库
   * @param { connection } cn 开启事务时创建的连接对象
   */
  close(_cn = null) {
    if (_cn !== null ) {
      // 归还连接对象。
      _cn.release()
    } else {
      // 关闭连接
      this.db.end((err) => {
        if(err) {
          console.error('关闭连接发生错误:', err)
        } else {
          console.log('\n[MySQL 已经关闭数据库:]\n')
        }
      })
    }
  }

关闭连接池

如果开启事务的话,还需要关闭连接池。

  /**
   * 关闭池子
   */
  closePool() {
    this.pool.end((err) => {
      if(err) {
        console.error('关闭连接发生错误:', err)
      } else {
        console.log('\n[MySQL 已经关闭连接池:]\n')
      }
    })
  }

未完待续。。。

源码:

https://gitee.com/naturefw/node-services

https://gitee.com/naturefw/node-services/tree/master/packages

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • 首先,webSQL并不在HTML的标准内,这里封装 webSQL 主要是为了后端的 MySQL。 因为想在前端做一...
    自然框架阅读 409评论 0 2
  • 为什么需要数据库操作系统,历史上最开始的数据库是怎么样的,有什么问题? 最开始的数据库其实和现在没有什么区别,都是...
    吴晗君阅读 197评论 0 0
  • 内存模型以及分区 JVM分为虚拟机栈、堆、方法区、本地方法区堆,用来存放实例化对象、非static成员变量,属于线...
    北京黄小胖阅读 1,230评论 0 0
  • 设计模式 一.六大设计原则 1.开闭原则:针对扩展开放,修改关闭; 2.里氏替换原则:任何父类出现的地方都可由其子...
    说好的蔚蓝天空呢阅读 545评论 0 0
  • Hadoop 常见面试题 mr 工作原理 ☆☆☆☆mr 将得到的split 分配对应的 task,每个任务处理相对...
    hdn040083阅读 3,601评论 0 1