node 下 sqlite3 的使用

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#execDatabase#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])

相關(guān)鏈接:npm github API

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逝嚎,隨后出現(xiàn)的幾起案子扁瓢,更是在濱河造成了極大的恐慌,老刑警劉巖懈糯,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涤妒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡赚哗,警方通過查閱死者的電腦和手機(jī)她紫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屿储,“玉大人贿讹,你說我怎么就攤上這事」宦樱” “怎么了民褂?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我赊堪,道長(zhǎng)面殖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任哭廉,我火速辦了婚禮脊僚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遵绰。我一直安慰自己辽幌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布椿访。 她就那樣靜靜地躺著乌企,像睡著了一般。 火紅的嫁衣襯著肌膚如雪成玫。 梳的紋絲不亂的頭發(fā)上加酵,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音梁剔,去河邊找鬼虽画。 笑死,一個(gè)胖子當(dāng)著我的面吹牛荣病,可吹牛的內(nèi)容都是我干的码撰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼个盆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脖岛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起颊亮,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤柴梆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后终惑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绍在,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年雹有,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了偿渡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡霸奕,死狀恐怖溜宽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情质帅,我是刑警寧澤适揉,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布留攒,位于F島的核電站,受9級(jí)特大地震影響嫉嘀,放射性物質(zhì)發(fā)生泄漏炼邀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一吃沪、第九天 我趴在偏房一處隱蔽的房頂上張望汤善。 院中可真熱鬧,春花似錦票彪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至摇零,卻和暖如春推掸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驻仅。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工谅畅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人噪服。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓毡泻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親粘优。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仇味,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容