Asynchronous, non-blocking SQLite3 bindings for Node.js
sqlite3 是一個(gè)專為 nodejs 設(shè)計(jì)的剧辐,node 上面的輕量級(jí)嵌入式數(shù)據(jù)庫(kù)倡鲸,作為嵌入式數(shù)據(jù)庫(kù)的代表喊式,sqlite 無疑是個(gè)理想的選擇方案猫态。
sqlite3 幾乎支持所有版本的 nodejs
望薄,同時(shí)也可以和 nwjs
集成槽驶。
安裝
基于 npm 安裝
npm install sqlite3
這樣除了安裝完 sqlite3 的 npm 包,最主要的是也裝完了 sqlite 數(shù)據(jù)庫(kù)捌肴,因?yàn)?sqlite 是嵌入式數(shù)據(jù)庫(kù)蹬叭,嵌入到客戶端中。sqlite3 使用 node-pre-gyp 為各個(gè)平臺(tái)下載指定的預(yù)編譯的二進(jìn)制文件状知。如果無法下載到預(yù)編譯的二進(jìn)制文件,sqlite3 將使用 node-gyp 和源代碼來構(gòu)建擴(kuò)展孽查。
這個(gè)過程出現(xiàn)兩個(gè)的庫(kù)——node-pre-gyp 和 node-gyp饥悴。他們究竟是什么呢?
node-gyp 是一個(gè)跨平臺(tái)的命令行工具,用于編譯 C++編寫的 nodejs 擴(kuò)展盲再,首先 gyp 是為 Chromium 項(xiàng)目創(chuàng)建的項(xiàng)目生成工具西设,可以從平臺(tái)無關(guān)的配置生成平臺(tái)相關(guān)的 Visual Studio、Xcode答朋、Makefile 的項(xiàng)目文件贷揽,node-gyp 就是將其集成到 nodejs 中。因?yàn)?linux 的二進(jìn)制分發(fā)快平臺(tái)做的并不好梦碗,所有 npm 為了方便干脆就直接源碼分發(fā)禽绪,用戶裝的時(shí)候再現(xiàn)場(chǎng)編譯蓖救。不過對(duì)有些項(xiàng)目二進(jìn)制分發(fā)就比源碼分發(fā)簡(jiǎn)單多了,所以還有個(gè) node-pre-gyp 來直接二進(jìn)制擴(kuò)展的分發(fā)印屁。
兩者區(qū)別在于 node-gyp 是發(fā)布擴(kuò)展的源碼循捺,然后安裝時(shí)候編譯;node-pre-gyp 是直接發(fā)布編譯后的二級(jí)制形式的擴(kuò)展雄人。
和 sqlite3 一樣的需要基于 node-gyp 安裝的 npm 模塊也有很多从橘,比如 node-sass 等,都是發(fā)布源代碼础钠,然后編譯安裝恰力。
基礎(chǔ) api
sqlite3 的 api 都是基于函數(shù)回調(diào)的,因?yàn)?nodejs 中沒有像 java 的 jdbc 那種官方的數(shù)據(jù)庫(kù)客戶端接口旗吁,因此每個(gè)數(shù)據(jù)庫(kù)的 api 都不一樣踩萎,這里簡(jiǎn)單介紹幾個(gè) sqlite3 重要的 api。
新建并打開數(shù)據(jù)庫(kù)
new sqlite3.Database(filename, [mode], [callback]);
該方法返回一個(gè)自動(dòng)打開的數(shù)據(jù)庫(kù)對(duì)象阵漏,參數(shù):
filename:有效值是一個(gè)文件名驻民,如:“mydatebase.db”,數(shù)據(jù)庫(kù)打開之后會(huì)創(chuàng)建一個(gè)“mydatebase.db”的文件用于保存數(shù)據(jù)履怯。如果文件名是“:memory:”回还,表示是一個(gè)內(nèi)存數(shù)據(jù)庫(kù)(類似 h2 那種),數(shù)據(jù)不會(huì)持久化保存叹洲,當(dāng)關(guān)閉數(shù)據(jù)庫(kù)時(shí)柠硕,內(nèi)容將丟失。
mode(可選):數(shù)據(jù)庫(kù)的模式运提,共 3 種值:sqlite3.OPEN_READONLY(只讀)蝗柔,sqlite3.OPEN_READWRITE(可讀寫)和 sqlite3.OPEN_CREATE(可以創(chuàng)建)。 默認(rèn)值為 OPEN_READWRITE |OPEN_CREATE民泵。
callback(可選):則當(dāng)數(shù)據(jù)庫(kù)成功打開或發(fā)生錯(cuò)誤時(shí)癣丧,將調(diào)用此函數(shù)。 第一個(gè)參數(shù)是一個(gè)錯(cuò)誤對(duì)象栈妆,當(dāng)它為空時(shí)胁编,表示打開成功。
打開一個(gè)數(shù)據(jù)庫(kù)
//數(shù)據(jù)庫(kù)的名字是"mydatebase.db"
var database;
database = new sqlite3.Database("mydatebase.db", function(e) {
if (err) throw err;
});
//也可以使用內(nèi)存型鳞尔,數(shù)據(jù)不會(huì)永久保存
database = new sqlite3.Database(":memory:", function(e) {
if (err) throw err;
});
執(zhí)行后會(huì)在項(xiàng)目的根目錄生成一個(gè)“mydatebase.db”文件嬉橙,這就是 sqlite 保存數(shù)據(jù)的文件了。
關(guān)閉數(shù)據(jù)庫(kù)
Database#close([callback])
該方法可以關(guān)閉一個(gè)數(shù)據(jù)庫(kù)連接對(duì)象寥假,參數(shù):
callback(可選):關(guān)閉成功的回調(diào)市框。 第一個(gè)參數(shù)是一個(gè)錯(cuò)誤對(duì)象,當(dāng)它為“null”時(shí)糕韧,表示關(guān)閉成功枫振。
執(zhí)行 DDL 和 DML 語句
Database#run(sql, [param, ...], [callback])
該方法可以執(zhí)行 DDL 和 DML 語句喻圃,如建表、刪除表蒋得、刪除行數(shù)據(jù)级及、插入行數(shù)據(jù)等,參數(shù):
sql
:要運(yùn)行的 SQL 字符串额衙。sql 的類型是 DDL 和 DML饮焦,DQL 不能使用這個(gè)命令。執(zhí)行后返回值不包含任何結(jié)果窍侧,必須通過 callback 回調(diào)函數(shù)獲取執(zhí)行結(jié)果县踢。
param,...
(可選):當(dāng) SQL 語句包含占位符(?)時(shí),這里可以傳對(duì)應(yīng)的參數(shù)伟件。 這里有三種傳值方法硼啤,如:
// 直接通過參數(shù)傳值.
db.run("UPDATE tbl SET name = ? WHERE id = ?", "bar", 2);
// 將值封裝為一個(gè)數(shù)組傳值.
db.run("UPDATE tbl SET name = ? WHERE id = ?", ["bar", 2]);
// 使用一個(gè) json 傳值.參數(shù)的前綴可以是“:name”,“@name”和“$name”斧账。推薦用“$name”形式
db.run("UPDATE tbl SET name = $name WHERE id = $id", {
$id: 2,
$name: "bar"
});
關(guān)于占位符的命名谴返,sqlite3 還支持更復(fù)雜的形式,這里不再擴(kuò)展咧织,有興趣了解的話請(qǐng)查看官方文檔嗓袱。
callback(可選):如果執(zhí)行成功,則第一個(gè)參數(shù)為 null习绢,否則就是出錯(cuò)渠抹。
如果執(zhí)行成功,上下文 this 包含兩個(gè)屬性:lastID 和 changes闪萄。lastID 表示在執(zhí)行 INSERT 命令語句時(shí)梧却,最后一條數(shù)據(jù)的 id;changes 表示 UPADTE 命令和 DELETE 命令時(shí)候败去,影響的數(shù)據(jù)行數(shù)放航。
db.run("UPDATE foo SET id = 1 WHERE id <= 500", function(err) {
if (err) throw err;
//使用 this.changes 獲取改變的行數(shù)
assert.equal(500, this.changes);
done();
});
執(zhí)行多條語句
Database#exec(sql, [callback])
Database#exec
與 Database#run
函數(shù)一樣,都是 DDL 和 DML 語句圆裕,但是 Database#exec
可以執(zhí)行多條語句三椿,并且不支持占位符參數(shù)。
database.run("CREATE TABLE foo (id INT)", function(e) {
if (e !== null) {
throw e;
}
//循環(huán)生成 sql 語句葫辐,批次插入多條數(shù)據(jù)
var sql = "";
for (var i = 0; i < 500; i++) {
sql += "INSERT INTO foo VALUES(" + i + ");";
}
database.exec(sql, done);
});
查詢一條數(shù)據(jù)
Database#get(sql, [param, ...], [callback])
sql
:要運(yùn)行的 SQL 字符串。sql 的類型是 DQL伴郁。這里僅返回第一條查詢到的數(shù)據(jù)耿战。
param,...
(可選):同 Database#run
的 param 參數(shù)
callback
(可選):同樣是返回 null 代表執(zhí)行成功『父担回調(diào)的簽名是 function(err剂陡,row)狈涮。如果查詢結(jié)果集為空,則第二個(gè)參數(shù)為 undefined鸭栖;否則第二個(gè)參數(shù)值是查詢到的第一個(gè)對(duì)象歌馍,他是個(gè) json 對(duì)象,屬性名稱對(duì)應(yīng)于結(jié)果集的列名稱晕鹊,因此查詢的每一列都應(yīng)該給出一個(gè)列表名松却。
查詢所有數(shù)據(jù)
Database#all(sql, [param, ...], [callback])
sql
:要運(yùn)行的 SQL 字符串。sql 的類型是 DQL溅话。和 Database#get 不同晓锻,Database#all 會(huì)返回所有查詢到的語句。
param,...
(可選):同 Database#run
的 param 參數(shù)
callback
(可選):同樣是返回 null 代表執(zhí)行成功飞几⊙舛撸回調(diào)的簽名是 function(err, rows) 。rows 是一個(gè)數(shù)組屑墨,如果查詢結(jié)果集為空數(shù)組躁锁。
! 注意,Database#all
首先檢索所有結(jié)果行并將其存儲(chǔ)在內(nèi)存中卵史。 對(duì)于數(shù)據(jù)量可能很大的查詢命令時(shí)候战转,請(qǐng)使用 Database#each 函數(shù)或 Database#prepare
代替這個(gè)方法。
遍歷數(shù)據(jù)
Database#each(sql, [param, ...], [callback], [complete])
與 Database#run
函數(shù)相同程腹,都是查詢多條數(shù)據(jù)匣吊,但是具有以下區(qū)別:
回調(diào)的簽名是 function(err,row)寸潦。如果結(jié)果集成功但為空色鸳,則不會(huì)調(diào)用回調(diào)。對(duì)于每個(gè)檢索到的行见转,該方法都會(huì)調(diào)用一次回調(diào)命雀。執(zhí)行順序與結(jié)果集中的行順序完全對(duì)應(yīng)。
調(diào)用所有行回調(diào)后斩箫,如果存在 complete 回調(diào)函數(shù)吏砂,將調(diào)用這個(gè)回調(diào)。第一個(gè)參數(shù)是一個(gè)錯(cuò)誤對(duì)象乘客,第二個(gè)參數(shù)是檢索行數(shù)狐血。
預(yù)編譯 SQL 相關(guān) api(Using Prepared Statements)
在 java 的 jdbc 中,有個(gè) PreparedStatement 相關(guān)的 api易核,可以預(yù)編譯 sql 語句匈织,執(zhí)行的時(shí)候再鏈接具體參數(shù)。這樣的好處是可以減少 sql 語句被編譯的次數(shù)。在 sqlite3 中缀匕,也存在實(shí)現(xiàn)這樣功能的 api纳决。
Database#prepare(sql, [param, ...], [callback])
Database#prepare
執(zhí)行后,會(huì)返回一個(gè)命令對(duì)象乡小,這個(gè)命令對(duì)象可以反復(fù)執(zhí)行阔加。下面看看這個(gè)命令對(duì)象(statement )的 api:
Statement#run([param, ...], [callback])
Statement#get([param, ...], [callback])
Statement#all([param, ...], [callback])
Statement#each([param, ...], [callback], [complete])
以上 api 方法與 Database 的同名方法調(diào)用方式相同。不同點(diǎn)是這里的 Statement 對(duì)象是可以復(fù)用的满钟,避免了重復(fù)編譯 sql 語句胜榔,因此項(xiàng)目中更推薦使用上述方法。
! 注意零远,這些方法的 param 參數(shù)都會(huì)對(duì) Statement 對(duì)象綁定參數(shù)苗分,在下一次執(zhí)行的時(shí)候,如果沒有重新綁定參數(shù)牵辣,是會(huì)使用上一次參數(shù)的摔癣。
綁定參數(shù)
Statement#bind([param, ...], [callback])
Database#prepare
執(zhí)行的時(shí)候,是可以綁定參數(shù)的纬向。不過使用此方法可以全重置語句對(duì)象和行游標(biāo)择浊,并刪除所有先前綁定的參數(shù),實(shí)現(xiàn)重新綁定的功能逾条。
重置語句的行游標(biāo)
Statement#reset([callback])
重置語句的行游標(biāo)琢岩,并保留參數(shù)綁定。使用此功能可以使用相同的綁定重新執(zhí)行相同的查詢师脂。
數(shù)據(jù)庫(kù)事務(wù)事務(wù)是關(guān)系型數(shù)據(jù)庫(kù)中的一個(gè)重要部分担孔,sqlite 自然也是支持事務(wù)的,但是 sqlite3 并沒有提供特殊 API 去實(shí)現(xiàn)的事務(wù)相關(guān)的操作吃警,只能靠 SQL 語句去控制事務(wù)糕篇。這里舉一個(gè)事務(wù)相關(guān)的例子。
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");
語句執(zhí)行順序(Control Flow)
sqlite3 的 API 都是異步的酌心,這就會(huì)出現(xiàn)可能有若干個(gè)命令同時(shí)進(jìn)行的情況拌消,因此 sqlite3 提供了兩個(gè)函數(shù)來幫助控制語句的執(zhí)行流程。默認(rèn)是并行模式安券。
序列化執(zhí)行
Database#serialize([callback])
如果提供回調(diào)五芝,它將立即被調(diào)用邮利,即此方法的回調(diào)不是異步回調(diào)镊折。在該回調(diào)中調(diào)度的所有數(shù)據(jù)庫(kù)語句將被序列化運(yùn)行夜畴,即一個(gè)接一個(gè)地執(zhí)行。 函數(shù)返回后址貌,數(shù)據(jù)庫(kù)將再次設(shè)置為其原始模式盛龄。
// 這里執(zhí)行的命令是并行的
db.serialize(function() {
// 這里執(zhí)行的命令是串行的
db.serialize(function() {
// 這里執(zhí)行的命令是串行的
});
// 這里執(zhí)行的命令是串行的
});
// 這里執(zhí)行的命令是并行的并行執(zhí)行模式
Database#parallelize([callback])
如果提供回調(diào),它將立即被調(diào)用,即此方法的回調(diào)不是異步回調(diào)余舶。在該回調(diào)中調(diào)度的所有數(shù)據(jù)庫(kù)語句將并行運(yùn)行。函數(shù)返回后锹淌,數(shù)據(jù)庫(kù)將再次設(shè)置為其原始模式匿值。
db.serialize(function() {
// 這里執(zhí)行的命令是串行的
db.parallelize(function() {
// 這里執(zhí)行的命令是并行的
});
// 這里執(zhí)行的命令是串行的
});
對(duì) SQLCipher 的支持
SQLCipher 是一個(gè)在 SQLite 基礎(chǔ)之上進(jìn)行擴(kuò)展的開源數(shù)據(jù)庫(kù),他和 SQLite 不同就是提供了對(duì)數(shù)據(jù)的加密赂摆,可提供數(shù)據(jù)庫(kù)文件的透明 256 位 AES 加密挟憔。
sqlite3 的官網(wǎng)特意提及他對(duì) SQLCipher 的集成,如果要集成 sqlcipher 需要在編譯時(shí)候通過構(gòu)建選項(xiàng)告訴 sqlite3 要集成的是 SQLCipher:
npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/
node -e 'require("sqlite3")'
不過筆者并沒嘗試對(duì) SQLCipher 的集成烟号,具體集成方法請(qǐng)自行查閱官網(wǎng)對(duì)這部分的詳細(xì)介紹绊谭。
基于 promise 對(duì) sqlite3API 的封裝
sqlite3 的 API 是 node 早期的 API 風(fēng)格,對(duì)異步的書寫風(fēng)格并不友好汪拥,很容易出現(xiàn)“金字塔回調(diào)”式的代碼达传。
為了讓對(duì) API 的調(diào)用更加優(yōu)雅,我們往往會(huì)把回調(diào)封裝成 Promise迫筑。
事實(shí)上這個(gè)工作并不需要我們自己做宪赶,sqlite3 生態(tài)下已經(jīng)有其他庫(kù)可以實(shí)現(xiàn)這樣的功能。
sqlite 就是一個(gè)這樣的庫(kù)脯燃。他基于 sqlite3搂妻,只手用 Promise 重新封裝了一下 sqlite3 的 API,使其代碼風(fēng)格更加優(yōu)雅辕棚,也更容易使用欲主。
API
Main
- new sqlite3.Database(filename, [mode], [callback])
- sqlite3.verbose()
Database
- Database#close([callback])
- Database#configure(option, value)
- Database#run(sql, [param, ...], [callback])
- Database#get(sql, [param, ...], [callback])
- Database#all(sql, [param, ...], [callback])
- Database#each(sql, [param, ...], [callback], [complete])
- Database#exec(sql, [callback])
- Database#prepare(sql, [param, ...], [callback])
Statement
- Statement#bind([param, ...], [callback])
- Statement#reset([callback])
- Statement#finalize([callback])
- Statement#run([param, ...], [callback])
- Statement#get([param, ...], [callback])
- Statement#all([param, ...], [callback])
- Statement#each([param, ...], [callback], [complete])