封装 MySQL(一)做个help先

作者: 自然框架 | 来源:发表于2021-07-30 18:17 被阅读0次

    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

    相关文章

      网友评论

        本文标题:封装 MySQL(一)做个help先

        本文链接:https://www.haomeiwen.com/subject/iuscvltx.html