前些日子尝试使用
HTML5
的indexedDB
,发现标准提出来的非关系型数据库真的很难用,根据自己理解基于indexedDB
封装了一个轻量级的数据库管理类,能够快速的建表、存数据。
1.需求分析
介于 indexedDB
不人性化的事务操作,以及神奇的建表逻辑(只能通过升级数据库,在升级事件内建表!),考虑自己实际需求做了以下的取舍:
- 因为轻量级原因,我只需要建多张表而不需要建数据库,因此全局只要维护一个数据库实例即可;
- 因其离谱的建表逻辑(也有可能我没把握住),通过多次尝试,最终选择在需要建表的时候主动升级数据库;
indexedDB
按索引查询方法过于繁琐(需要根据数据设置索引),因此通过cursor
遍历、人为找到要查询的数据,这样的好处是对SDK
调用友好。
2.数据库管理对象接口封装
创建原型对象,并初始化相关变量:
// 可以参见之前的博客
import promise from 'promise.js'
/*
* 数据半持久化存储
* 注: 利用 IndexDB 做数据缓存
*/
let MircoDB = function () {
// 初始化数据库名
this._db_name = '_MICRO_DB'
// 定义数据库
this._micro_db = undefined
// 数据库版本
this.version = 0
// 获取 indexedDB 管理对象
this._indexedDB = undefined
}
初始化数据库,并打开:
/*
* 数据库初始化接口
* 返回 Promise 对象
*/
MircoDB.prototype.init = function () {
let deffer = _promise.deffer()
// 调用初始化函数
this._init(deffer)
return deffer.promise
}
/*
* [内部函数
* 初始化
*/
MircoDB.prototype._init = function (deffer) {
let request
// 初始化
this._indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB
// 打开数据库
request = this._indexedDB.open(this._db_name)
// 错误监听
request.onerror = (event) => {
// 抛出异常
deffer.reject(new Error(error.DB_OPEN_ERROR))
}
// 正确打开监听
request.onsuccess = (event) => {
// 绑定数据库
this._micro_db = event.target.result
// 记录版本号
this.version = this._micro_db.version
// 返回结果
deffer.resolve(true)
}
}
建表,这里也有比较坑的地方,建表后需要重新赋值数据对象,不然数据库对象会丢失:
/*
* 创建表
* 注: 如果存在,那么打开表;反之,创建表
* 创建执行事件,如果数据库没打开,就执行。那么直接存储到对应的 TODO 数组内,在打开后执行相应的执行函数。
* 参数 tableName: 表名
* 参数 keys: 搜索索引值(数组),如['name', 'id']
*/
MircoDB.prototype.createTable = function (tableName) {
let deffer = _promise.deffer()
let request
// 如果需要新建数据库,那么放到队列里
if (!this._micro_db.objectStoreNames.contains(tableName)) {
// 打开数据库
this.version++
this._micro_db.close()
request = this._indexedDB.open(this._db_name, this.version)
// 错误监听
request.onerror = (event) => {
deffer.reject(new Error(error.DB_OPEN_ERROR))
}
request.onupgradeneeded = function (event) {
this._micro_db = event.target.result
this._micro_db.createObjectStore(tableName, { keyPath: '_uuid' })
}
request.onsuccess = (event) => {
this._micro_db = event.target.result
this.version = this._micro_db.version
deffer.resolve(true)
}
} else {
deffer.resolve(true)
}
return deffer.promise
}
增加一条数据(数据对象类型不限):
/*
* 增加数据
*/
MircoDB.prototype.add = function (tableName, object) {
let deffer = this._gm._internal._promise.deffer()
let uuid = utils.createUUID(8) // 创建唯一标识,可见仓库代码,也可以自行实现
let request
// 添加主键
object._uuid = uuid
// 发起请求
request = this._micro_db.transaction([tableName], 'readwrite')
.objectStore(tableName)
.add(object)
// 成功监听
request.onsuccess = function (event) {
// 返回唯一标识
deffer.resolve(uuid)
}
// 错误监听
request.onerror = function (event) {
deffer.reject(new Error(error.TABLE_HANDLE_ERROR))
}
return deffer.promise
}
删除一条数据,根据 uuid 删除(目前通过增加语句和查询语句返回 uuid,降低复杂度):
/*
* 删除数据
*/
MircoDB.prototype.delete = function (tableName, uuid) {
let deffer = _promise.deffer()
let request = this._micro_db.transaction([tableName], 'readwrite')
.objectStore(tableName)
.delete(uuid)
// 成功监听
request.onsuccess = function (event) {
deffer.resolve(true)
}
// 错误监听
request.onerror = function (event) {
deffer.reject(new Error(error.TABLE_HANDLE_ERROR))
}
return deffer.promise
}
修改数据:
/*
* 修改数据
* 数据必须包含主键
*/
MircoDB.prototype.update = function (tableName, data) {
let deffer = _promise.deffer()
let request = this._micro_db.transaction([tableName], 'readwrite')
.objectStore(tableName)
.put(data)
// 成功监听
request.onsuccess = function (event) {
deffer.resolve(true)
}
// 错误监听
request.onerror = function (event) {
deffer.reject(new Error(error.TABLE_HANDLE_ERROR))
}
return deffer.promise
}
查询数据,因为用 cursor(数据库查询指针)的原因,因此查询效率上可能不如索引快,优点是对用户友好:
/*
* 查询数据
*/
MircoDB.prototype.query = function (tableName, queryObj) {
let deffer = _promise.deffer()
let request = this._micro_db.transaction([tableName], 'readonly')
.objectStore(tableName)
.openCursor()
let result = []
// 错误监听
request.onerror = function (event) {
deffer.reject(new Error(error.TABLE_HANDLE_ERROR))
}
// 成功监听
request.onsuccess = function (event) {
let cursor = event.target.result
if (cursor) {
let tag = false
// 循环找到匹配的数据
for (let key in queryObj) {
if (cursor.value[key] === queryObj[key]) {
continue
} else {
tag = true
break
}
}
// 判断是否键值全部匹配
if (!tag) {
result.push(cursor.value)
}
cursor.continue()
} else {
deffer.resolve(result)
}
}
return deffer.promise
}
读取所有数据:
/*
* 读取所有数据
*/
MircoDB.prototype.queryAll = function (tableName) {
let deffer = _promise.deffer()
let request = this._micro_db.transaction(tableName, 'readonly')
.objectStore(tableName)
.openCursor()
let result = []
// 成功监听
request.onsuccess = function (event) {
let cursor = event.target.result
if (cursor) {
result.push(cursor.value)
cursor.continue()
} else {
deffer.resolve(result)
}
}
// 错误监听
request.onerror = function (event) {
deffer.reject(new Error(error.TABLE_HANDLE_ERROR))
}
return deffer.promise
}
清空表数据:
/*
* 清空表数据
*/
MircoDB.prototype.clear = function (tableName) {
let deffer = _promise.deffer()
let request = this._micro_db.transaction([tableName], 'readwrite')
.objectStore(tableName)
.clear()
// 错误监听
request.onerror = function (event) {
deffer.reject(new Error(error.TABLE_HANDLE_ERROR))
}
// 成功监听
request.onsuccess = function (event) {
deffer.resolve(true)
}
return deffer.promise
}
删除表:
/*
* 删除表
*/
MircoDB.prototype.removeTable = function (tableName) {
let deffer = _promise.deffer()
let request
// 如果需要新建数据库,那么放到队列里
if (this._micro_db.objectStoreNames.contains(tableName)) {
// 打开数据库
this.version++
this._micro_db.close()
request = this._indexedDB.open(this._db_name, this.version)
// 错误监听
request.onerror = (event) => {
deffer.reject(new Error(error.TABLE_HANDLE_ERROR))
}
request.onupgradeneeded = function (event) {
this._micro_db = event.target.result
// 删除表名
this._micro_db.deleteObjectStore(tableName)
deffer.resolve(true)
}
request.onsuccess = (event) => {
this._micro_db = event.target.result
this.version = this._micro_db.version
}
}
return deffer.promise
}
3.具体使用
// 创建实例
let _mircodb = new MicroDB()
// 初始化数据库
_microdb.init()
.then(() => {
// 创建表
return _microdb.createTable('my_table') // true
})
.then(() => {
// 插入数据
return _microdb.add('my_table', {
val1: 123,
val2: '123',
str: 'hello, world'
})
})
.then((uuid) => {
console.log(uuid) // fde34sd4 唯一标识
// 插入数据
return _microdb.update('my_table', {
_uuid: 'fde34sd4',
val1: 234,
val2: '345',
str: 'hello, world'
})
})
.then(() => {
// 查询结果
return _microdb.query('my_table', { val1: 234 })
})
.then((result) => {
console.log(result) // [{ _uuid: 'fde34sd4', val1: 234, val2: '345', str: 'hello, world' }]
})
.catch((err) => {
console.log(err.message)
})
以上是我在 indexedDB
疯狂踩坑后选用的折中解决方案,大家如果有更好的方案,可以讨论。
注:示例代码可参见 个人前端知识库。
created by @SpiderWang
转载请注明作者及链接