await 方式
我们是否可以用 await 的方式实现一下开启事务的代码呢?尝试了一下,也是可以的,虽然这么做没有什么实际意义。
/** * 开启事务,await 的方式 */ async beginTransaction() { console.log('★ 开启事务,await 模式') let _cn = null try { _cn = awaitthis._poolCreateConnection() awaitthis._beginStran(_cn) } catch(e) { console.log('async 开启事务出错:', e) } return _cn }
没有了回调方式,看起来是不是舒服多了?为啥说没啥实际意义呢?因为这种方式,要求调用者也必须使用 await 的方式,有点强制性。而上面那个函数(begin)既可以用 Promise 的方式,也可以用 await 的方式,即满足需求也比较灵活。
提交事务
开启事务,执行各种操作后,需要提交事务,那么我们再做一个提交事务的功能。
/** * 提交一个事务 * @param { connection } cn 开启事务时创建的连接对象 */ commit(_cn) { const myPromise = newPromise((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') } }) }
封装SQL语句
核心操作,上面的 help 就可以实现了,下面要封装SQL,避免手撸SQL的尴尬。思路有点像ORM,但是又不完全是ORM,采用 meta + model 的形式拼接参数化的SQL语句。
insert —— addModel
从SQL语句的角度来看,是 insert into table
,从对象的角度来看,就是添加了一个model。不管怎么说,都是需要一个SQL语句才行,如果手撸的话,既麻烦又容易出错,那么怎么办呢?我们可以先设定一个 meta 进行描述,然后写个函数拼接即可。
- meta
{ "tableName": "node_user", "idKey": "id", "cols": { "name": "", "age": "" } }
由表名、主键字段名、需要的字段集合组成,这个看起来好像是一个表、字段的结构,其实并不是,区别在于字段集合的组成方式。
一般情况是基于表建立的 model,需要把表的字段都加上,不能少。
但是这里的 meta 并不要求把表里面的字段都加上,而是根据业务需求设置字段,业务需要哪些字段就放置哪些字段,不需要的可以不放。
另外一个表可以设置多种这样的 meta,完全依据业务需求来决定。
- 实现代码
/data-add.js
/** * 实现添加数据的功能。拼接 insert 的 SQL语句 * @param { MySQLHelp } help 访问数据库的实例 * @param { Object } meta 表、字段 * @param { Object } model 数据 * @param { connection } cn 如果使用事务的话,需要传递开启事务时创建的连接对象 * @returns 添加记录的ID * * meta 结构: * * * tableName:'', 表名 * * * cols:{colName: '类型'}, josn 字段需要标记 * * model 结构: * * * colName: value */ function addData(help, meta, model, cn = null) { // 拼接添加用的SQL语句, // 提交SQL语句 const myPromise = newPromise((resolve, reject) => { // sql = 'INSERT INTO aaa set aaacol = ? ' // 获取字段名称和值的数组 const { colNames, params } = help.createTableCols(meta, model) const sql = `INSERT INTO ${meta.tableName} SET ${colNames.join(',')} ` console.log('addData --- sql:', sql, params) const _cn = cn === null ? help.db : cn help.query(sql, params, _cn) .then((res) => { // console.log('添加数据成功:', res.insertId) resolve(res.insertId) }) .catch((res) => { // 出错了 reject(res) }) }) return myPromise } module.exports = addData
如果使用事务的话,需要传递一个链接对象进来,否则使用内部默认的连接对象。
- help 实现基础功能的实例。
- meta 上面说到的表、字段的描述
- model 要添加的数据
update
同上(代码雷同就不贴了),拼接 update 的SQL语句,加上where即可。
每一个小功能都做成了独立的 js 文件。其实一开始想做一个大的class,后来觉得代码太长不好维护,于是想做成子类的形式,但是想想似乎没啥必要,除了少传递一个help参数之外好像没啥区别。所以最后决定采用这种方式,
delete
同上,只需要表名和主键字段即可。
/** * 实现删除数据的功能。拼接 DELETE FROM 的 SQL语句 * @param { MySQLHelp } help 访问数据库的实例 * @param { Object } info 表、字段 * @param { number|string } id 数据 * @returns 影响的记录数 * * info 结构: * * * tableName:'', 表名 * * * idKey '', 主键名称 * * * cols:{colName: '类型'}, josn 字段需要标记 * * id :number | string */ function deleteData(help, info, id, cn = null) { // 拼接 修改 用的SQL语句, const myPromise = newPromise((resolve, reject) => { const sql = `DELETE FROM ${info.tableName} WHERE ${info.idKey} = ? ` const _cn = cn === null ? help.db : cn help.query(sql, [id], _cn) .then((res) => { // 删除成功,返回影响行数 resolve(res.affectedRows) }) .catch((res) => { // 出错了 reject(res) }) }) return myPromise } module.exports = deleteData