nodejs集成sqlite使用示例

2017-06-05 19 laden666666

正在物色node上面的轻量级嵌入式数据库,作为嵌入式数据库的代表,sqlite无疑是个理想的选择方案。npm上集成sqlite的库主要有两个——sqlite3和realm。

realm是一个理想的选择方案,它最初是为移动app设计的,在node也可以运行的,但是不支持Windows系统。sqlite3是一个专为nodejs设计的,在nodejs上面生态更健壮,因此最终选择sqlite3。

sqlite3几乎支持所有版本的nodejs,同时也可以和nwjs集成。

安装

基于npm安装

npm install sqlite3

这样除了安装完sqlite3的npm包,最主要的是也装完了sqlite数据库,因为sqlite是嵌入式数据库,嵌入到客户端中。sqlite3使用node-pre-gyp为各个平台下载指定的预编译的二进制文件。如果无法下载到预编译的二进制文件,sqlite3将使用node-gyp和源代码来构建扩展。

这个过程出现两个的库——node-pre-gyp和node-gyp。他们究竟是什么呢?

node-gyp是一个跨平台的命令行工具,用于编译C++编写的nodejs扩展,首先gyp是为Chromium项目创建的项目生成工具,可以从平台无关的配置生成平台相关的Visual Studio、Xcode、Makefile的项目文件,node-gyp就是将其集成到nodejs中。因为linux的二进制分发快平台做的并不好,所有npm为了方便干脆就直接源码分发,用户装的时候再现场编译。不过对有些项目二进制分发就比源码分发简单多了,所以还有个node-pre-gyp来直接二进制扩展的分发。

两者区别在于node-gyp是发布扩展的源码,然后安装时候编译;node-pre-gyp是直接发布编译后的二级制形式的扩展。

和sqlite3一样的需要基于node-gyp安装的npm模块也有很多,比如node-sass等,都是发布源代码,然后编译安装。

基础api 

sqlite3的api都是基于函数回调的,因为nodejs中没有像java的jdbc那种官方的数据库客户端接口,因此每个数据库的api都不一样,这里简单介绍几个sqlite3重要的api。

新建并打开数据库

new sqlite3.Database(filename, [mode], [callback])

该方法返回一个自动打开的数据库对象,参数:

filename:有效值是一个文件名,如:“mydatebase.db”,数据库打开之后会创建一个“mydatebase.db”的文件用于保存数据。如果文件名是“:memory:”,表示是一个内存数据库(类似h2那种),数据不会持久化保存,当关闭数据库时,内容将丢失。

mode(可选):数据库的模式,共3种值:sqlite3.OPEN_READONLY(只读),sqlite3.OPEN_READWRITE(可读写)和sqlite3.OPEN_CREATE(可以创建)。 默认值为OPEN_READWRITE |OPEN_CREATE。

callback(可选):则当数据库成功打开或发生错误时,将调用此函数。 第一个参数是一个错误对象,当它为空时,表示打开成功。

打开一个数据库,如:

//数据库的名字是"mydatebase.db"
var database;
database = new sqlite3.Database("mydatebase.db", function(e){
 if (err) throw err;
});
//也可以使用内存型,数据不会永久保存
database = new sqlite3.Database(":memory:", function(e){
 if (err) throw err;
});

执行后会在项目的根目录生成一个“mydatebase.db”文件,这就是sqlite保存数据的文件了。

关闭数据库

Database#close([callback])

该方法可以关闭一个数据库连接对象,参数:

callback(可选):关闭成功的回调。 第一个参数是一个错误对象,当它为“null”时,表示关闭成功。

执行DDL和DML语句 

Database#run(sql, [param, ...], [callback])

该方法可以执行DDL和DML语句,如建表、删除表、删除行数据、插入行数据等,参数:

sql:要运行的SQL字符串。sql的类型是DDL和DML,DQL不能使用这个命令。执行后返回值不包含任何结果,必须通过callback回调函数获取执行结果。

param,...(可选):当SQL语句包含占位符(?)时,这里可以传对应的参数。 这里有三种传值方法,如:

// 直接通过参数传值.
db.run("UPDATE tbl SET name = ? WHERE id = ?", "bar", 2);



// 将值封装为一个数组传值.
db.run("UPDATE tbl SET name = ? WHERE id = ?", [ "bar", 2 ]);



// 使用一个json传值.参数的前缀可以是“:name”,“@name”和“$name”。推荐用“$name”形式
db.run("UPDATE tbl SET name = $name WHERE id = $id", {
 $id: 2,
 $name: "bar"
});

关于占位符的命名,sqlite3还支持更复杂的形式,这里不再扩展,有兴趣了解的话请查看官方文档。

callback(可选):如果执行成功,则第一个参数为null,否则就是出错。

如果执行成功,上下文this包含两个属性:lastID和changes。lastID表示在执行INSERT命令语句时,最后一条数据的id;changes表示UPADTE命令和DELETE命令时候,影响的数据行数。

db.run("UPDATE foo SET id = 1 WHERE id <= 500", function(err) {
if (err) throw err;
 //使用this.changes获取改变的行数
 assert.equal(500, this.changes);
 done();
});

执行多条语句

Database#exec(sql, [callback])

Database#exec与Database#run函数一样,都是DDL和DML语句,但是Database#exec可以执行多条语句,并且不支持占位符参数。

database.run("CREATE TABLE foo (id INT)", function(e){
 if(e !== null){
 throw e;
}
 //循环生成sql语句,批次插入多条数据
 var sql = "";
 for(var i = 0 ; i < 500; i ++){
 sql += 'INSERT INTO foo VALUES(' + i + ');'
}
 database.exec(sql, done)
});

查询一条数据

Database#get(sql, [param, ...], [callback])

sql:要运行的SQL字符串。sql的类型是DQL。这里仅返回第一条查询到的数据。

param,...(可选):同Database#run的param参数

callback(可选):同样是返回null代表执行成功。回调的签名是function(err,row)。如果查询结果集为空,则第二个参数为undefined;否则第二个参数值是查询到的第一个对象,他是个json对象,属性名称对应于结果集的列名称,因此查询的每一列都应该给出一个列表名。

查询所有数据

Database#all(sql, [param, ...], [callback])

sql:要运行的SQL字符串。sql的类型是DQL。和Database#get不同,Database#all会返回所有查询到的语句。

param,...(可选):同Database#run的param参数

callback(可选):同样是返回null代表执行成功。回调的签名是function(err, rows) 。rows是一个数组,如果查询结果集为空数组。

! 注意,Database#all首先检索所有结果行并将其存储在内存中。 对于数据量可能很大的查询命令时候,请使用Database#each函数或Database#prepare代替这个方法。

遍历数据

Database#each(sql, [param, ...], [callback], [complete])

与Database#run函数相同,都是查询多条数据,但是具有以下区别:

回调的签名是function(err,row)。如果结果集成功但为空,则不会调用回调。对于每个检索到的行,该方法都会调用一次回调。执行顺序与结果集中的行顺序完全对应。

调用所有行回调后,如果存在complete回调函数,将调用这个回调。第一个参数是一个错误对象,第二个参数是检索行数。

语句执行顺序 

sqlite3的API都是异步的,这就会出现可能有若干个命令同时进行的情况,因此sqlite3提供了两个函数来帮助控制语句的执行流程。默认是并行模式。

序列化执行

Database#serialize([callback])

如果提供回调,它将立即被调用,即此方法的回调不是异步回调。在该回调中调度的所有数据库语句将被序列化运行,即一个接一个地执行。 函数返回后,数据库将再次设置为其原始模式。

// 这里执行的命令是并行的
db.serialize(function() {
 // 这里执行的命令是串行的
 db.serialize(function() {
 // 这里执行的命令是串行的
});
 // 这里执行的命令是串行的
});
// 这里执行的命令是并行的

并行执行模式

Database#parallelize([callback])

如果提供回调,它将立即被调用,即此方法的回调不是异步回调。在该回调中调度的所有数据库语句将并行运行。函数返回后,数据库将再次设置为其原始模式。

db.serialize(function() {
 // 这里执行的命令是串行的
 db.parallelize(function() {
 // 这里执行的命令是并行的
});
 // 这里执行的命令是串行的
});

预编译SQL相关api 

在java的jdbc中,有个PreparedStatement相关的api,可以预编译sql语句,执行的时候再链接具体参数。这样的好处是可以减少sql语句被编译的次数。在sqlite3中,也存在实现这样功能的api。

Database#prepare(sql, [param, ...], [callback])

Database#prepare执行后,会返回一个命令对象,这个命令对象可以反复执行。下面看看这个命令对象(statement )的api:

Statement#run([param, ...], [callback])
Statement#get([param, ...], [callback])
Statement#all([param, ...], [callback])
Statement#each([param, ...], [callback])

以上api方法与Database的同名方法调用方式相同。不同点是这里的Statement对象是可以复用的,避免了重复编译sql语句,因此项目中更推荐使用上述方法。

! 注意,这些方法的param参数都会对Statement对象绑定参数,在下一次执行的时候,如果没有重新绑定参数,是会使用上一次参数的。

绑定参数 

Statement#bind([param, ...], [callback])

Database#prepare执行的时候,是可以绑定参数的。不过使用此方法可以全重置语句对象和行游标,并删除所有先前绑定的参数,实现重新绑定的功能。

重置语句的行游标

Statement#reset([callback])

重置语句的行游标,并保留参数绑定。使用此功能可以使用相同的绑定重新执行相同的查询。

数据库事务

事务是关系型数据库中的一个重要部分,sqlite自然也是支持事务的,但是sqlite3并没有提供特殊API去实现的事务相关的操作,只能靠SQL语句去控制事务。这里举一个事务相关的例子。

var db = new sqlite3.Database(db_path);
db.run("CREATE TABLE foo (id INT, txt TEXT)");
db.run("BEGIN TRANSACTION");
var stmt = db.prepare("INSERT INTO foo VALUES(?, ?)");
for (var i = 0; i < count; i++) {
 stmt.run(i, randomString());
}
db.run("COMMIT TRANSACTION");

对SQLCipher的支持

SQLCipher是一个在SQLite基础之上进行扩展的开源数据库,他和SQLite不同就是提供了对数据的加密,可提供数据库文件的透明256位AES加密。

sqlite3的官网特意提及他对SQLCipher的集成,如果要集成sqlcipher需要在编译时候通过构建选项告诉sqlite3要集成的是SQLCipher:

npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/
node -e 'require("sqlite3")'

不过笔者并没尝试对SQLCipher的集成,具体集成方法请自行查阅官网对这部分的详细介绍。

基于promise对sqlite3API的封装 

sqlite3的API是node早期的API风格,对异步的书写风格并不友好,很容易出现“金字塔回调”式的代码。为了让对API的调用更加优雅,我们往往会把回调封装成Promise。事实上这个工作并不需要我们自己做,sqlite3生态下已经有其他库可以实现这样的功能。sqlite就是一个这样的库。他基于sqlite3,只手用Promise重新封装了一下sqlite3的API,使其代码风格更加优雅,也更容易使用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

展开阅读全文

更多Javascript文章

疯狂Jquery第一天(Jquery学习笔记)
May 11 17
解决jquery中美元符号命名冲突问题
Jan 08 35
如何解决IONIC页面底部被遮住无法向上滚动问题
Sep 06 26
js 判断登录界面的账号密码是否为空
Feb 08 26
详解JavaScript中的数组合并方法和对象合并方法
May 11 19
Vue CLI3 开启gzip压缩文件的方式
Sep 30 27
用webAPI实现图片放大镜效果
Nov 23 31
手机访问当前页面