技术栈
- 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 = newPromise((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 = newPromise((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 = newPromise((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 = newPromise((resolve, reject) => { console.log('★ 开启事务,promise 模式') this._poolCreateConnection().then((_cn) => { // 开启事务 this._beginStran(_cn) .then(() => {resolve(_cn)}) // 返回连接对象 .catch((err) => {reject(err)}) }) }) return myPromise }
其实一个有三个函数,两个内部函数一个对外的函数。这么做是为了代码可以更简洁一些,看起来好看一点,否则各种回调地狱,保证你想哭。