IndexedDB 的打开方式
IndexedDB 是一个浏览器内置的非关系型数据库,用于替代曾经的 webSQL,据说很强大,反正我已经上车了。那么 IndexedDB 是如何初始化和迁移的呢?
打开数据库
indexedDB.open(dbName, version) -> IDBRequest
打开数据库dbName,设置版本号为version。
- 如果版本号增加,则会触发
upgradeneeded
事件,否则跳过这一步。 - 如果成功,则触发
success
事件,否则触发error
事件。
返回值req
是一个IDBRequest,如果连接成功,req.result
就是这个数据库连接,可以保存下来在后面使用。
流程是这样的:
open -> [ upgradeneeded ] -> [ success | error ]
用代码来表示就是这样:
const req = indexedDB.open('my-db', 1);
req.onupgradeneeded = e => {
// upgrade database here
};
req.onsuccess = e => {
const db = req.result;
// `db` can be used later.
};
req.onerror = e => {
console.error(e);
};
更新数据库结构
IndexedDB 中,数据库的结构只能在版本发生变化的时候进行修改,即只能在upgradeneeded
事件中进行。
先从最简单的创建一个 ObjectStore 说起。ObjectStore 是 IndexedDB 里的存储结构,类似于关系型数据库中的一个表。
ObjectStore 里可以有很多对象,但是他们之间必须可以通过key
区分开来,更新数据的时候将通过key
来查找要更新的对象,所以具有相同key
的对象其实是会相互覆盖的。在创建ObjectStore 的时候,我们需要指定keyPath
,然后根据keyPath
就可以在对象中找到它的key
,如keyPath = 'id'
时,对象obj
的key
就是obj.id
。之后查找数据的时候,就可以通过key
来查找了。
除此之外,我们还可以建立额外的索引,通过索引来进行其他维度的数据查找,如我们知道store1
里的对象都有字段weight
,可以为它创建一个索引,这样之后就可以用weight
来查找数据了。与关系型数据库不同的是,没有索引过的字段是无法查询的。
代码如下:
req.onupgradeneeded = e => {
const db = req.result;
const os = db.createObjectStore('store1', { keyPath: 'id' });
const idx = os.createIndex('idxWeight', 'weight');
};
升级数据库
上面提到的更新数据库结构实际上仅仅是第一次使用时的初始化过程,只要数据库版本号不变,它就不会再次运行了。重复运行的话,由于已经存在相同的ObjectStore和Index,所以是会出错的。
那么问题来了,如果数据库在之前的基础上更新的话,应该如何操作呢?比如我们已经有store1
了,现在需要再创建一个store2
,而不影响store1
。
onupgradeneeded
会传入一个IDBVersionChangeEvent,它有oldVersion
和newVersion
属性,可以知道升级前后的版本,只进行必要的迁移操作。
代码如下:
req.onupgradeneeded = e => {
const db = req.result;
if (e.oldVersion < 1) {
// upgrade from 0 to 1
const os = db.createObjectStore('store1', { keyPath: 'id' });
const idx = os.createIndex('idxWeight', 'weight');
}
if (e.oldVersion < 2) {
// upgrade from 1 to 2
const os = db.createObjectStore('store2', { keyPath: 'id' });
}
};
修改已有的ObjectStore
这个问题乍一看并不像是一个问题。可是实践之后就发现在onupgradeneeded
中,我们只能创建一个新的ObjectStore,却无法获取一个现有的。所以这个问题的关键是如何在onupgradeneeded
中获取一个现有的ObjectStore。
首先尝试了一下db.transaction
,果然就失败了,因为这个时候不能创建新的transaction
,否则会引起混乱。
然后查文档发现,IDBRequest有一个属性transaction
,表示当前这个IDBRequest所在的transaction,而onupgradeneeded
过程中就可以获取到。
代码如下:
req.onupgradeneeded = e => {
const db = req.result;
// ...
if (e.oldVersion < 2) {
const { transaction } = req;
const os = transaction.objectStore('store1');
const idx = os.createIndex('antherIndex', 'rank', { unique: true });
}
};