node 下 sqlite3 的使用

原文連接 github npm APIjava

Asynchronous, non-blocking SQLite3 bindings for Node.jsnode

sqlite3 是一個專爲 nodejs 設計的,node 上面的輕量級嵌入式數據庫,做爲嵌入式數據庫的表明,sqlite 無疑是個理想的選擇方案。linux

sqlite3 幾乎支持全部版本的 nodejs,同時也能夠和 nwjs 集成。git

安裝

基於 npm 安裝github

npm install sqlite3
複製代碼

這樣除了安裝完 sqlite3 的 npm 包,最主要的是也裝完了 sqlite 數據庫,由於 sqlite 是嵌入式數據庫,嵌入到客戶端中。sqlite3 使用 node-pre-gyp 爲各個平臺下載指定的預編譯的二進制文件。若是沒法下載到預編譯的二進制文件,sqlite3 將使用 node-gyp 和源代碼來構建擴展。sql

這個過程出現兩個的庫——node-pre-gyp 和 node-gyp。他們到底是什麼呢?數據庫

node-gyp 是一個跨平臺的命令行工具,用於編譯 C++編寫的 nodejs 擴展,首先 gyp 是爲 Chromium 項目建立的項目生成工具,能夠從平臺無關的配置生成平臺相關的 Visual Studio、Xcode、Makefile 的項目文件,node-gyp 就是將其集成到 nodejs 中。由於 linux 的二進制分發快平臺作的並很差,全部 npm 爲了方便乾脆就直接源碼分發,用戶裝的時候再現場編譯。不過對有些項目二進制分發就比源碼分發簡單多了,因此還有個 node-pre-gyp 來直接二進制擴展的分發。npm

二者區別在於 node-gyp 是發佈擴展的源碼,而後安裝時候編譯;node-pre-gyp 是直接發佈編譯後的二級制形式的擴展。json

和 sqlite3 同樣的須要基於 node-gyp 安裝的 npm 模塊也有不少,好比 node-sass 等,都是發佈源代碼,而後編譯安裝。api

基礎 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#execDatabase#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 回調函數,將調用這個回調。第一個參數是一個錯誤對象,第二個參數是檢索行數。

預編譯 SQL 相關 api(Using Prepared Statements)

在 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], [complete])

以上 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");
複製代碼

語句執行順序(Control Flow)

sqlite3 的 API 都是異步的,這就會出現可能有若干個命令同時進行的狀況,所以 sqlite3 提供了兩個函數來幫助控制語句的執行流程。默認是並行模式。

序列化執行

Database#serialize([callback])

若是提供回調,它將當即被調用,即此方法的回調不是異步回調。在該回調中調度的全部數據庫語句將被序列化運行,即一個接一個地執行。 函數返回後,數據庫將再次設置爲其原始模式。

// 這裏執行的命令是並行的
db.serialize(function() {
  // 這裏執行的命令是串行的
  db.serialize(function() {
    // 這裏執行的命令是串行的
  });
  // 這裏執行的命令是串行的
});
// 這裏執行的命令是並行的並行執行模式
複製代碼

Database#parallelize([callback])

若是提供回調,它將當即被調用,即此方法的回調不是異步回調。在該回調中調度的全部數據庫語句將並行運行。函數返回後,數據庫將再次設置爲其原始模式。

db.serialize(function() {
  // 這裏執行的命令是串行的
  db.parallelize(function() {
    // 這裏執行的命令是並行的
  });
  // 這裏執行的命令是串行的
});
複製代碼

對 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,使其代碼風格更加優雅,也更容易使用。

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

相關文章
相關標籤/搜索