众所周知,我自己写了一个基于 Node.js 的 仿 jdbc 数据库连接适配器 [bluewater](https://github.com/undeadway/bluewater)
。
然后,因为这东西没有实现错误处理,或者说实现的不完整,所以进行了一次修改,算是彻底考虑到了各方面的错误处理。
然后,然后就悲剧了。
原因就在于之前的设计上,并没有处理非数据库错误时发生的错误。 比如
var obj = stmt.prepare(sql);
execute(obj); // 这里出错了stmt.close();
一旦执行时发生业务逻辑错误,在进行数据库回滚的时候,代码就会崩掉。
之前没有做崩掉的处理,所以就算崩掉了,也看不出来。而这次所增加的,就是对错误的处理。
于是,之前没有暴露的问题一下子就暴露了出来。但不暴露倒无所谓,一暴露却让我觉得很奇怪,因为它的错误信息是
TypeError: Cannot call method 'prepare' of null
于是怪之,prepare 这个方法是从 connection 里吊用的,而 connection 是连接数据库最基本的要素,不可能为空啊,这里说 null,实在让人不解,除非在不知道的时候 connection 被关闭了才会出现这种情况。
然后就是一点点查找定位这个错误发生的原因。
最后的原因竟然是写了两遍 connection 的关闭操作。
首先,就是在每个数据库自身的 connection 和 statement 中,有过关闭数据库连接的代码,比如
select : function(callback, args) {
if (!isArray(args)) {
args = slice.call(arguments, 1);
}
stmt = conn.prepare(sql);
stmt.all(args, function(err, rows) {
// 关闭数据库连接
close();
// 执行回调函数
callback(err, rows);
});
},
而到了框架层,又来这么一次数据库关闭操作。
if(autoCommit) { if(stmt !== null) {
stmt.close(function() {
stmt = null;
close();
});
}
}
而一旦执行到第二次关闭,那就必定会抛出 之前的空指针错误。
最后的解决方案 把数据库自身连接适配器里面的关闭操作给删掉。从数据库连接设计角度来说,这段操作确实是多余的。 jdbc也没有说自动关闭 connection 和 statement,向来都是要求用户自己关闭的。 而框架层,因为这已经属于非驱动范围了,所以保留操作,也达到了设计之初不用手动关闭数据库连接的设计目的。
而发生这个错误的原因,很大程度上,还是因为思维停留在用数据库的阶段,而不是设计的阶段。所以在写数据库操作的时候,想到一旦完毕就要关闭数据库,很顺手的就把
if(stmt !== null) {
stmt.close(function() {
stmt = null;
close();
});
}
这样的代码给加了进来,造成了这种隐藏非常深的错误。