loki 數據庫詳解


介紹

LokiJS是一個面向文檔的javascript數據庫,與MongoDB有點相似。

它支持索引,查詢和過濾數據集合。 LokiJS還支持更高級的功能,例如mapReduce,事務,並允許您實現自定義遠程同步以將數據保存到服務器(或移動設備上的本地文件)。
磁盤的持久性已經在諸如nodejs之類的CommonJS環境中實現,
在移動設備上,您只需要請求文件系統並將lokijs的serialize()作為內容傳遞即可。

創建

創建一個 db:

var db = new loki('Example');

除了 Example 這個數據庫的名稱外, 還可以傳遞一個選項參數:

interface LokiConfigOptions {
    adapter: LokiPersistenceAdapter | null;
    autoload: boolean;
    autoloadCallback: (err: any) => void;
    autosave: boolean;
    autosaveCallback: (err?: any) => void;
    autosaveInterval: string | number;
    persistenceMethod: "fs" | "localStorage" | "memory" | null;
    destructureDelimiter: string;
    serializationMethod: "normal" | "pretty" | "destructured" | null;
    throttledSaves: boolean;
}

關於參數: persistenceMethod 需要注意的點:

  • "fs": 只能在 node 環境中使用(包括 electron)
  • "localStorage" : web 中可以使用,並且數據會存在 localStorage 中,進行持久存儲
  • "memory": 只是簡單的存在內容中,如果刷新頁面,數據就不存在了

在不同的環境中, 他創建的持久層是不同的, 比如
在瀏覽器中, 是基於 web 數據庫或者 localStorage(默認) 的
在 node 環境中, 可以基於 fs, 創建文件數據庫
關於他們的變化是需要一個 option.adapter 來進行協助

在 web 中, 如果想要將數據存到 web 數據庫中,
那么就需要 option.adapter 了(當傳遞了 adapter 參數時, 'persistenceMethod' 參數就會被無效)
但是如果使用了 option.adapter,那么只能使用自動加載數據庫和字段保存數據庫的方案

增加數據

針對 loki 里的 Collection 如果了解數據庫,可以將它當成 這種結構

想要添加數據,需要先獲取 collection 對象

可以在添加 collection 的時候獲取:

const users = db.addCollection('users', {indices: ['email']});

可以直接獲取:

const coll = db.getCollection('users')

注意 addCollection 有 2 個參數可以傳遞:

  • 第一個參數是 name 是 collection 的名稱

  • 第二個是可選項參數,它擁有很多參數:

名稱 類型 屬性 默認值 描述
unique 數組 <可選> [] 屬性名稱數組,用於定義唯一約束
exact 數組 <可選> [] 屬性名稱數組,用於定義確切的約束
indices 數組 <可選> [] 用於定義二進制索引的數組屬性名稱
adaptiveBinaryIndices 布爾值 <可選> true 收集索引將被重新建立而不是懶加載
asyncListeners 布爾值 <可選> false 偵聽器是否異步調用
disableMeta 布爾值 <可選> false 設置為true以禁用文檔的元屬性
disableChangesApi 布爾值 <可選> true 設置為false以啟用Changes API
disableDeltaChangesApi 布爾值 <可選> true 設置為false以啟用Delta更改API(需要更改API,強制克隆)
autoupdate 布爾值 <可選> false 使用Object.observe自動更新對象
clone 布爾值 <可選> false 指定是否向用戶插入或從用戶克隆查詢
serializableIndices 布爾值 <可選> true[] 將二進制索引屬性上的日期值轉換為紀元時間
cloneMethod 字符串 <可選> 'parse-stringify' 'parse-stringify', 'jquery-extend-deep', 'shallow', 'shallow-assign'
ttl int <可選> 文件被認為是陳舊/過時之前的文件時間(以毫秒為單位)。
ttlInterval int <可選> 清除“陳舊”文件的時間間隔;默認情況下未設置。

使用 inert 添加數據

const coll = db.getCollection('users')
var odin = users.insert({name: 'odin', email: 'odin.soap@lokijs.org', age: 38});
var thor = users.insert({name: 'thor', email: 'thor.soap@lokijs.org', age: 25});
var stan = users.insert({name: 'stan', email: 'stan.soap@lokijs.org', age: 29});

// 也可以同時插入多個數據
// users.insert([{ name: 'Thor', age: 35}, { name: 'Loki', age: 30}]);

這里要注意的是: 分清楚數據的存儲狀態, 當我們不使用自動保存和手動保存的時候 insert, 會將數據插入 collection 中, 但是當我們刷新頁面的時候,數據會重置會原來的數據,
如果我們要將數據全部存下來(即使刷新也會存在的話), 就需要保存:

// var db = new loki('Example'); 這是 db 的由來
db.saveDatabase(error => {
  console.log('保存數據')
  error && console.log(error)
})

獲取數據:

獲取數據是比較靈活的,我這里說兩種方法:

方法一:


const dv = coll.addDynamicView('test');

const results = dv.data();

console.log(results)
// 這是results打印結果
// 0: {name: "odin", email: "odin.soap@lokijs.org", age: 38, meta: {…}, $loki: 1}
// 1: {name: "thor", email: "thor.soap@lokijs.org", age: 25, meta: {…}, $loki: 2}
// 2: {name: "stan", email: "stan.soap@lokijs.org", age: 29, meta: {…}, $loki: 3}
// 3: {name: "oliver", email: "oliver.soap@lokijs.org", age: 31, meta: {…}, $loki: 4}
// 4: {name: "hector", email: "hector.soap@lokijs.org", age: 15, meta: {…}, $loki: 5}
// 5: {name: "achilles", email: "achilles.soap@lokijs.org", age: 31, meta: {…}, $loki: 6}

方法二:

const resultsLine = coll.chain().data();

console.log(resultsLine)
// 結果與方法一相同

獲取數據時篩選想要的數據:

方法 1 的篩選:

find
// 通過 coll 直接獲取
const results4 = coll.find({'age': {'$aeq': 15}});
console.log('獲取數據 4',results4)

// 可使用不同的指令:
// 指令名 作用
// $eq  ===
// $ne  !==
// $aeq  ==
// $dteq 時間上的相等
// $gt  >
// $gte >=
// $lt  <
// $lte  <=
// $between   介於 2 個數之間

// 如果不希望使用二進制索引,並且希望簡單的javascript比較是可以接受的,我們提供以下操作,由於它們的簡化比較,它們可以提供更好的執行速度。

// $gt -> $jgt
// $gte -> $jgte
// $lt -> $jlt
// $lte -> $jlte
// $between -> $jbetween
// $regex 使用正則

applyFind
const dv = coll.addDynamicView('test');

dv.applyFind({ 'name' : 'odin' });
const results = dv.data();
applyWhere

dv2.applyWhere(function(obj) { return obj.name === 'oliver'; });
// 作用與上述方法相同
applySimpleSort
// 根據年齡進行排序
const dv3 = coll.addDynamicView('test3');

dv3.applySimpleSort("age");

const results3 = dv3.data();

console.log(results3)
findOne
const results5 = coll.findOne({'age': {'$aeq': 31}});
// 獲取到的是對象  而不是一個數組
console.log('獲取數據 5',results5)
findObject
const results6 = coll.findObject({'age': 38});
// 使用的結果和 findOne 類似
console.log('獲取數據 6',results6)
findObjects
const results7 = coll.findObjects({'age': 31});
// 返回的是一個數組
console.log('獲取數據 7',results7)

比較推薦的是使用 addDynamicView 的方式來篩選,而不是通過 collection 直接獲取

需要注意的是 DynamicView 是一個數據格式,他可以 add 可以 get 也可以 remove

方法 2 的篩選:

// 簡單的篩選
const resultsLine2 = coll.chain().find({ 'name' : 'odin' }).data();

// 排序:
const resultsLine3 = coll.chain().simplesort('age').data();

當然 chain 里還有其他操作,如: limit, map, findAnd, eqJoin, count等等,
我是更推薦使用第一種方法,這里的幾種使用方案我就不詳細舉例了

還有不建議使用 chain 的 update,remove 等操作,因為監聽器里面會監聽不到事件,
這個問題不知道是故意這么做 還是 bug

修改數據:

update

與 insert 同理:

// 要修改 就需要先獲取要修改的東西是什么 
const item = coll.findOne({'age': {'$aeq': 31}});

item.age = 18
coll.update(item);

console.log(coll.chain().data())
// 打印發現名字為 odin 的年齡已經改成了 18

// 當然想要持久化就得保存數據庫:
db.saveDatabase(error => {
  console.log('保存數據')
  error && console.log(error)
})
findAndUpdate
coll.findAndUpdate({'age': {'$aeq': 25}}, data => {
    // 原名"thor"
    data.name = 'grewer'
    return data
})

// 獲取並且修改 集中在同一個方法里面
console.log('修改結果 2', coll.chain().data())
updateWhere

coll.updateWhere(data => {
    return data.name === 'grewer';
}, data => {
    data.age = '999'
    return data
})

// 與上面的類似,但是更加自由,而且還可以是用 `{'age': {'$aeq': 15}}` 這種方法來獲取

刪除數據:

remove

刪除數據也是非常簡單的(與更新類似):

const item2 = coll.findOne({'age': {'$aeq': 31}});

coll.remove(item2);

console.log(coll.chain().data())
findAndRemove
coll.findAndRemove({'age': {'$aeq': 15}})
// 同 findAndUpdate, 集中了 find 和 remove
console.log('修改結果 2', coll.chain().data())
removeWhere
// 同 updateWhere
coll.removeWhere((value,index)=>{
    return index === 1
})

console.log('刪除結果 3', coll.chain().data())

添加操作的監聽:

Loki 的 DB 支持自定義事件,使用如下:

// 添加自定義 grewer 事件
db.addListener('grewer',(data) => {
    console.log('grewer事件', data)
})

// 觸發事件
db.emit('grewer','qwerty')

Loki 支持對 collection 添加操作的監聽, 監聽的事件支持以下事件

close
delete
error
flushbuffer
insert
pre-insert
pre-update
update
warning

使用:

coll.on('update', (event) => {
    console.log('coll change 事件', event)
})
// inset, delete 等其他事件同理

在我們使用 update/findAndUpdate/updateWhere 的時候就會自動觸發此回調了

關於 Collection transforms

他的官方介紹是這樣的:

轉換背后的基本思想是允許將結果集“鏈”過程轉換為該過程的對象定義。然后可以選擇命名該數據定義,並將其與集合一起保存在數據庫中。

一個簡單的使用:

var tx = [
    {
        type: 'find',
        value: {
            'name': 'oliver'
        }
    }
];
console.log(coll.chain(tx).data())

// 打印結果:
[{
    $loki: 4
    age: 18
    email: "oliver.soap@lokijs.org"
    meta: {...}
    name: "oliver"
}]

關於他的使用,感覺像是其他數據庫里面的schema, 我這里也沒碰到過具體的情況,所以了解不夠深刻

其他數據庫功能:

連表查詢:

public eqJoin(
    joinData: Collection<any> | Resultset<any> | any[],
    leftJoinProp: string | ((obj: any) => string),
    rightJoinProp: string | ((obj: any) => string),
    mapFun?: (left: any, right: any) => any,
    dataOptions?: Partial<GetDataOptions>
): Resultset<any>;

一個簡單的使用例子:

// 創建另一個 collection(表)


var collection = db.addCollection("test", {
    unique: ["name"]
});

collection.insert({owner: 0, name: 'Betsy'});
collection.insert({owner: 1, name: 'Bingo'});
collection.insert({owner: 2, name: 'Fifi'});
collection.insert({owner: 3, name: 'Fuzzy'});
collection.insert({owner: 4, name: 'Gizmo'});

// 這是另一個表

// 進行查詢:

const resultSet = coll.eqJoin(collection.chain(), 'id', 'owner')
// 當 id 和 owner 相等時 數據會被連接
console.log('連表', resultSet.data())
// 打印一下 console
[{
$loki: 1
left: {name: "odin", id: 0, email: "odin.soap@lokijs.org", age: 38, meta: {…}, …}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {owner: 0, name: "Betsy", meta: {…}, $loki: 1}
},
{
$loki: 2
left: {name: "grewer", id: 1, email: "thor.soap@lokijs.org", age: "999", meta: {…}, …}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {owner: 1, name: "Bingo", meta: {…}, $loki: 2}
},
{
$loki: 3
left: {name: "stan", email: "stan.soap@lokijs.org", age: 29, meta: {…}, $loki: 3}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {}
},
{
$loki: 4
left: {name: "oliver", email: "oliver.soap@lokijs.org", age: 18, meta: {…}, $loki: 4}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {}
},
{
$loki: 5
left: {name: "hector", email: "hector.soap@lokijs.org", age: 15, meta: {…}, $loki: 5}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {}
},
{
$loki: 6
left: {name: "achilles", email: "achilles.soap@lokijs.org", age: 31}
meta: {revision: 0, created: 1597421406034, version: 0}
right: {}
}]

這就是最簡單的連表使用

還有一些沒說到的,但是也就是邊邊角角的東西了,基本就是這些方法的使用

寫在最后

Loki 擁有 adapter 使得他的適用性特別高,但是相對詳細的使用卻比較少,所以我寫了這篇相對詳細一點的文章來記錄此數據庫的相關操作

關於官方文檔地址:

http://techfort.github.io/LokiJS/

還有這篇文章里面的所有例子的地址:

https://github.com/Grewer/JsDemo/blob/master/lokijs/index.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM