背景
承接前面一篇介绍 node.js 封装数据库操作的文章,本文继续封装 node.js 中通过oracledb进行事务操作的实现过程。oracledb 相比 mysql 好一点的是它的所有操作同时支持回调和 Promise 两种处理方式,所以编写同一个事务中执行多个 SQL 的逻辑,其代码相比 mysql 更简洁。
async 和 await 的控制流程
首先,需要了解下 async 和 await 结合使用时,如果 await 返回的是一个 reject 结果,那么我们的同步代码该怎么处理呢?从执行过程来看,只要进行 catch 操作,那么 try 分支中所有接受到 reject 的 Promise 对象的操作都会进入到 catch 分支。
简单写个例子验证下:
async function test(){
try {
await hello1();
await hello2();
console.log("OK finish");
}catch (err) {
console.log(err);
}
}
function hello1(){
return new Promise(async(resolve, reject) => {
console.log("hello1 bad");
reject({
status: 0,
message: '操作失败'
});
});
}
function hello2(){
return new Promise(async(resolve, reject) => {
console.log("hello2 ok");
resolve({
status: 1,
message: '操作成功'
});
});
}
test();
执行结果为:
oracledb 事务操作封装
oracledb 如果在同一个 connection 中调用 execute 操作,那么这些操作都是通过一个事务处理的,所以实现思路是将所有待执行的 SQL 语句放在一个 connection 调用方法中完成,结合 await ,在 try 分支中最后显式调用 commit ,异常分支调用 rollback 回滚。
const oracledb = require('oracledb');
/**
* 创建执行SQL
* @param sql
* @param param
* @returns {{sql: *, param: *}}
*/
function buildSqlParamEntity(sql, param) {
return {
sql: sql,
param: param
}
}
/**
* 在同一个事务中执行多条SQL语句,都是异步方式
* @param sqlparamsEntities
* @param callback
* @returns {Promise.<void>}
*/
async function oracleExecTrans(sqlparamsEntities,callback){
logger.info("Oracle execute transaction");
if(sqlparamsEntities==null || sqlparamsEntities.length==0){
logger.info("Statement is null,do nothing.")
return;
}
let connection;
try {
// Get a connection from the default pool
connection = await oracledb.getConnection();
//遍历语句进行执行:在同一个connection中执行的SQl,都是默认为一个事务的。
for (var i = 0; i < sqlparamsEntities.length; i++) {
var sql = sqlparamsEntities[i].sql;
var params = sqlparamsEntities[i].param;
let results = await connection.execute(sql, params);
logger.info(sql+"results is:"+results);
}
//提交事务
await connection.commit();
//释放连接
connection.release();
//执行回调:传入成功消息
callback('操作成功',null);
} catch (err) {
console.error(err);
//异常:则进行事务回滚
if(connection!=undefined && connection!=null){
await connection.rollback();
await connection.release();
}
//执行回调:传入异常信息
callback(null,err);
}
}
进入 catch 分支的可能是由 getConnection() 引起的,也可能是 execute 或者 commit 导致的,所以在异常分支执行回滚操作需要判断连接对象是否存在。以同步的逻辑编写事务处理操作,总的来说代码还是比较简洁的。
至于回调方式实现事务的过程,实在是理不清楚,层层回调下来整个人都凌乱了,还是不浪费脑细胞了吧。
对比 mysql 事务操作实现
网络上搜到的比较完整的 mysql 的事务插入逻辑为:
http://www.cnblogs.com/bk-your/p/7884729.html
由于 mysql 只能通过回调方式调用,所以它在事务处理中使用了 async 模块完成后续的提交或者回滚逻辑:
async.series(funcAry, function (err, result) {
console.log("transaction error: " + err);
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);
}
})
}
})
启示录
oracledb 的资料也真是少,就连它在 github上 的 api 文档中都没有关于事务的例子,所以只能自己动手编写了。上述代码没有测试环境,所以未经测试,但是已经目测过了,姑且先做参考吧。