在最近项目中,由于业务需求,导致需要同时操作12张表。由于之前在node开发中没有使用过事务,所以就去了解了一下。其实node中事务和java是一样的,只不过java中我们可以通过注解的方式来实现事务的回滚,但是在node中,想要实现事务就需要将SQL一条一条的执行,执行完成之后再commit。于是乎就有了这个工具类,逻辑有点傻,但是很实用。
1、创建一个Tool文件夹,再创建一个MySQL_dbHelper.js事务工具类,代码如下:
/**
* 数据库事务工具
*/
var mysql = require('promise-mysql');
var config = require('../../../config');
var SqlString = require('sqlstring');
var async = require("async");
module.exports = {
/**
* 组装SQL_map
* @param sql sql语句
* @param params 数组,如果存在占位符,传入[xx,xx,xx],如果没有传入[]
* @param callback
* @returns {*}
* @private
*/
_getNewSqlParamEntity:function(sql, params, callback) {
if (callback) {
return callback(null, {
sql: sql,
params: params
});
}
return {
sql: sql,
params: params
};
},
/**
* 事务管理
* @param sqlparamsEntities SQL_map {sql: param:}
* @param callback 回调
*/
execTrans:function (sqlparamsEntities, callback) {
var name='';
if(!name) {
name = 'default';
}
var db_config = config['mysql'][name];
db_config.queryFormat = function (query, values) {
//console.log(query, values);
if (query.indexOf('?') != -1) {
return SqlString.format(query, values, db_config.stringifyObjects, db_config.timezone);
}
else {
return query.replace(/\:(\w+)/g, function (txt, key) {
if (values && values.hasOwnProperty(key)) {
return this.escape(values[key]);
}
return txt;
}.bind(this));
}
};
var pool = mysql.createPool(db_config);
pool.getConnection(function (err, connection) {
if (err) {
return callback(err, null);
}
connection.beginTransaction(function (err) {
if (err) {
return callback(err, null);
}
//console.log("开始执行transaction,共执行" + sqlparamsEntities.length + "条数据");
var funcAry = [];
sqlparamsEntities.forEach(function (sql_param) {
var temp = function (cb) {
var sql = sql_param.sql;
var param = sql_param.params;
connection.query(sql, param, function (tErr, rows, fields) {
if (tErr) {
connection.rollback(function () {
return callback(tErr,"事务失败," + sql_param + ",ERROR:" + tErr);
});
} else {
return cb (null, 'ok');
}
})
};
funcAry.push(temp);
});
async.series(funcAry, function (err, result) {
if (err) {
connection.rollback(function (err) {
console.log("transaction error: " + err);
connection.release();
return callback(err, null);
});
} else {
connection.commit(function (err, info) {
//console.log("transaction info: " + JSON.stringify(info));
if (err) {
console.log("执行事务失败," + err);
connection.rollback(function (err) {
console.log("transaction error: " + err);
connection.release();
return callback(err, null);
});
} else {
connection.release();
return callback(null, info);
}
})
}
})
});
});
}
};
如果没有promise-mysql,sqlstring,async的,就手动npm install一下。
其中config是我的配置文件,你也可以将MySQL的配置写在MySQL_dbHelper.js里面。
config.js代码如下:
var config = {
mysql: {
// 默认配置
default: {
host: 'localhost',
port: 3306,
user: 'root',
password: '***',
database: '数据库名',
acquireTimeout: 2000,
connectionLimit: 100
},
}
2、数据交互层调用事务工具
var dbHelpers = require('../../Tool/MySQL_dbHelper');
exports.To_dbHelper=function (param,cb) {
var sqlParamsEntity = [];//装载所有待执行的SQL和数据
/****解析map里面不同的SQL并匹配对应的占位数据***/
for(var column in param){
/***第一层取出,map{map{}}***/
var keyName =[];
for(var columns in param[column]){//第二层map为{xx:[],xx:[]}
keyName.push(columns);
}
for(var i in param[column][keyName[0]]){
console.log('sql====='+param[column][keyName[0]][i]);
console.log('whereParam====='+param[column][keyName[1]][i]);
sqlParamsEntity.push(dbHelpers._getNewSqlParamEntity(param[column][keyName[0]][i],param[column][keyName[1]][i]));
}
}
dbHelpers.execTrans(sqlParamsEntity, function (err, info) {
if (err) {
console.error("事务执行失败" + err);
cb(err, '失败')
} else {
cb(null, '成功')
}
});
};
3、控制层封装SQL及其占位数据。
router.post('/To_examine.do',async function (req,res) {
try{
var sql1 = [];//执行的SQL
var sql2 = [];
var sql3 = [];
var sql1_param = []; //SQL中找占位符对应的数据集[ [占位数据,占位数据], [占位数据,占位数据] ]
var sql2_param = [];
var sql3_param = []
//这里就简单的封装一下3个SQL,并不是真实的
for(var i=0;i<=5:i++){
sql1.push('insert into user_info (id,name,age,sex) value(?,?,?,?)');
sql1_param.push([3+i+1,‘张三’+i],22,'男']);
sql2.push('update user_info name=? where id =?');
sql2_param.push(['女',i+12]);
sql3.push('delect user_Score where id =?');
sql3_param.push([i+1]);
}
var param = {sql1:{sql1:sql1,sql1_param},sql2:{sql2:sql2,sql2_param},sql3:{sql3:sql3,sql3_param}};//封装成这样是为了module成循环解析
moudle.To_dbHelper(params,function (err,data) {
if(err){
console.log(err);
app_utils.respMsg(res, false, 4000, err, null, null);
}else{
app_utils.respMsg(res, true, 4000, '成功', null, null);//这里记得一定要返回结果给页面,不然会重复执行一次
}
});
}catch(err){
console.log(err);
}
});
好了,整个事务工具类的创建和使用就已经完成了。按照步骤做就可以了,在控制层调用module层方法的时候,一定要对视图层返回结果,不然事务会执行两次,我之前写好的时候为了看效果就没有返回视图层,导致执行了两次事务,找了半天都没有找到原因,结果加了返回就可以了。
如有什么错误的地方,欢迎留言。如果你有更好的方法,那请留言告诉我。