indexedDB是什么
IndexedDB是一种在用户浏览器中持久存储数据的方法,属于key-value键值对事务模式的数据库。它允许您不考虑网络可用性,创建具有丰富查询能力的可离线 Web 应用程序。
只要不主动清除浏览器存储数据indexedDB就不会清除;
存储数据可达200M以上,相比session,cookie,localstorage可存数据量大很多;
应用范围
- IndexedDB 对于存储大量数据的应用程序(例如借阅库中的 DVD 目录)和不需要持久 Internet 连接的应用程序(例如邮件客户端、待办事项列表或记事本)很有用;
- 也可以用于区块链技术需要大量下载加解密数据、electron开发桌面应用程序、人工智能等需要大量存储数据等特殊场景;
特性
- IndexedDB 数据库使用 key-value 键值对储存数据.
- IndexedDB 是事务模式的数据库.
- The IndexedDB API 基本上是异步的.
- IndexedDB数据库“请求”无处不在 我们上边提到,数据库“请求”负责接受成功或失败的DOM事件
- IndexedDB在结果准备好之后通过DOM事件通知用户 DOM事件总是有一个类型(type)属性(在IndexedDB中,该属性通常设置为success或error)
- IndexedDB是面向对象的。indexedDB不是用二维表来表示集合的关系型数据库
- indexedDB不使用结构化查询语言(SQL)
- IndexedDB遵循同源(same-origin)策略 “源”指脚本所在文档URL的域名、应用层协议和端口。每一个“源”都有与其相关联的数据库。在同一个“源”内的所有数据库都有唯一、可区别的名称
问题
如果要实现如下类似关系型数据库的多条件查询
select * from image_colors where h>=230 and h<=234 and s>=0.222 and s<=0.225 and v>=0.666 and v<=0.668;
那么可以怎么实现呢?下文将会告诉您答案。
使用样例
直接上代码,代码有完整等注释说明:
新建数据库
let db;
exports.newDB = (upgrade) => {
return new Promise((resolve, reject) => {
let request = window.indexedDB.open('image_tool', 6);//第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本
request.onerror = (e) => {
console.log('数据库打开报错');
reject(e);
};
//第一次打开数据库时,会先触发upgradeneeded事件,然后触发success事件。
request.onsuccess = (e) => {
db = request.result;
console.log('onsuccess','数据库打开成功');
// addSourceLibRecord(); // 操作数据在这开始
resolve(db);
};
// 如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded
// 时通过事件对象的target.result属性,拿到数据库实例
request.onupgradeneeded = (e) => {
console.log('onupgradeneeded');
db = e.target.result;
!!upgrade&&upgrade();
// creatSourceLibStore(); // 创建表必须在这开始
};
});
};
这里特别需要注意:创建表必须在
onupgradeneeded里执行;第一次打开数据库时,会先触发upgradeneeded事件,然后触发success事件。
新建store
//新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建。不同之处在于,后续的操作主要在upgradeneeded事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。
// 创建资源库表格
exports.creatSourceLibStore = () => {
let objectStore;
if (!db.objectStoreNames.contains('source_lib')) {
// objectStore = db.createObjectStore('source_lib', { keyPath: 'name' }); // 主键是name属性
objectStore = db.createObjectStore('source_lib', { autoIncrement: true });
// { autoIncrement: true } 自动生成主键
objectStore.createIndex('path', 'path', { unique: true });// 创建索引
objectStore.createIndex('name', 'name', { unique: true });
//三个参数分别为索引名称、索引所在的属性、配置对象
}
};
下面我们创建一个图片表库,下文中的操作都会用到这个库来讲解:
function createImageColors() {
let objectStore;
if (!db.objectStoreNames.contains('image_colors')) {
objectStore = db.createObjectStore('image_colors', { autoIncrement: true });
objectStore.createIndex('name', 'name');
objectStore.createIndex('path', 'path');
objectStore.createIndex('h', 'h');
objectStore.createIndex('s', 's');
objectStore.createIndex('v', 'v');
objectStore.createIndex('h,s,v',['h','s','v']);// 声明个组合索引,用于条件组合查询
}
}
exports.createImageColors = createImageColors;
请留意组合索引的定义
添加数据
先封装一个函数:
需要开启一个事务才能对你的创建的数据库进行操作。事务来自于数据库对象,而且你必须指定你想让这个事务跨越哪些对象仓库,事务提供了三种模式:readonly、readwrite 和 versionchange。
想要修改数据库模式或结构——包括新建或删除对象仓库或索引,只能在 versionchange 事务中才能实现,使用 readonly 或 readwrite 模式都可以从已存在的对象仓库里读取记录。但只有在 readwrite 事务中才能修改对象仓库。
exports.addRecord = (table, record) => {
return new Promise((resolve, reject) => {
let request = db.transaction([table], 'readwrite')
.objectStore(table)
.add(record);
request.onsuccess = function (event) {
console.log('数据写入成功');
resolve();
};
request.onerror = function (event) {
console.log('数据写入失败');
reject();
};
});
};
再调用函数插入一些测试数据
await addRecord('image_colors', {name:'a.jpg',path:'1/a.jpg',h:230,s:0.222,v:0.666});
await addRecord('image_colors', {name:'b.jpg',path:'1/b.jpg',h:232,s:0.224,v:0.668});
await addRecord('image_colors', {name:'c.jpg',path:'1/c.jpg',h:234,s:0.226,v:0.668});
索引查询
封一个函数先:
//增删查改都要通过事务来完成
//读取一个数据,通过索引来获取,查询结果满足多条数据时只拿第一个数据
exports.readOneRecordByIndex = (table, indexName, value) => {
return new Promise((resolve, reject) => {
let transaction = db.transaction([table]);
let objectStore = transaction.objectStore(table);
let index = objectStore.index(indexName); //索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录
let request = index.get(value);
request.onerror = function(event) {
console.log('readSourceLibRecord 事务失败');
reject(event);
};
request.onsuccess = function(e) {
if (e.target.result) {
// console.log('name: ' + e.target.result.name);
resolve(e.target.result);
} else {
console.log('readSourceLibRecord 未获得数据记录');
resolve(null);
}
};
});
};
调用查询:
// 多个记录的话,只取第一个记录
let imagesR = await readOneRecordByIndex('image_colors','v',0.668);
console.log('imageR',imagesR);
游标查询
游标有两种 openCursor和openKeyCursor,游标有两个参数
- 第一个参数,遍历范围,指定游标的访问范围。该范围通过一个IDBKeyRange参数的方法来获取
- 第二个参数,遍历顺序(IDBCursor的常量字符串)如下:
- "next"光标显示所有记录,包括重复记录。它从键范围的下限开始向上移动(按键的顺序单调递增)
- "nextunique"光标显示所有记录,不包括重复记录。如果存在具有相同键的多个记录,则仅检索迭代的第一个记录。它从键范围的下限开始向上移动
- "prev"光标显示所有记录,包括重复记录。它从键范围的上限开始向下移动(按键的顺序单调递减)
- "prevunique"光标显示所有记录,不包括重复记录。如果存在具有相同键的多个记录,则仅检索迭代的第一个记录。它从键范围的上限开始向下移动
- 你想要在游标在索引迭代过程中过滤出重复的,你可以传递 nextunique (或 prevunique 如果你正在向后寻找)作为方向参数。 当 nextunique 或是 prevunique 被使用时,被返回的那个总是键最小的记录;
- 当游标便利整个存储空间但是并未找到给定条件的值时,仍然会触发onsuccess函数;
用游标遍历全表数据,先封一个函数来:
//通过游标遍历数据 全表
// table 表名
// eachFn每次循环时的执行钩子函数
exports.readAllRecord = (table, eachFn=(v)=>{}) => {
return new Promise((resolve, reject) => {
let transaction = db.transaction([table]);
let objectStore = transaction.objectStore(table);
let request = objectStore.openCursor();
let datas = [];
request.onsuccess = function (event) {
let cursor = event.target.result;
if (cursor) {
//console.log('cursor',Object.assign(cursor.value));
datas.push(cursor.value);
eachFn(cursor.value);
cursor.continue();
} else {
resolve(datas);
}
};
request.onerror = function (event) {
// 错误处理!
reject(event);
};
});
};
遍历范围定义
游标的第一个参数是遍历范围
定义和说明见如下代码:
//IDBKeyRange.only(1); //===1
//IDBKeyRange.lowerBound(1); //>=1
//IDBKeyRange.lowerBound(1, true); //>1
//IDBKeyRange.upperBound(2, true); //<2
//IDBKeyRange.bound(1, 2, false, true); // >= 1 && < 2
// 索引范围
let hr = IDBKeyRange.bound(230,234,false,false);//h>=230&&h<=234
let sr = IDBKeyRange.bound(0.222,0.225,false,false);//s>=0.222&&s<=0.225
let vr = IDBKeyRange.bound(0.666,0.668,false,false);//v>=0.666&&v<=0.668
let kr = IDBKeyRange.bound([230,0.222,0.666],[234,0.225,0.668],false,false);//组合索引,这里相当于上面3条语句的与值,只能统一设置hsv的边界true or false
条件组合查询
这里我们使用上面代码定义的组合索引查询范围kr
我们先封装一个完整调索引游标入参遍历数据的函数:
//通过索引游标遍历数据
//table表名
//indexName索引名
//keyRange查询范围
//eachFn每次循环时的执行钩子函数
exports.readRecordsByIndexCursor = (table,indexName,keyRange,eachFn=(v)=>{}) => {
return new Promise((resolve, reject) => {
let transaction = db.transaction([table]);
let objectStore = transaction.objectStore(table);
let index = objectStore.index(indexName);
let request = index.openCursor(keyRange);
let datas = [];
request.onsuccess = function (event) {
let cursor = event.target.result;
if (cursor) {
//console.log('cursor',Object.assign(cursor.value));
datas.push(cursor.value);
eachFn(cursor.value);
cursor.continue();
} else {
resolve(datas);
}
};
request.onerror = function (event) {
// 错误处理!
reject(event);
};
});
};
接着调用:
let records2 = await readRecordsByIndexCursor('image_colors','h,s,v',kr);
console.log('record2',records2);
同理使用索引方式调用试试看:
// 多个记录的话,只取第一个记录
let imagesI = await readOneRecordByIndex('image_colors','h,s,v',kr);
console.log('组合索引条件查询imagesI',imagesI);
更新数据
更新数据是以主键为准来操作,看代码:
- 先创建表
if (!db.objectStoreNames.contains('source_lib')) {
objectStore = db.createObjectStore('source_lib', { keyPath: 'path' }); // 主键是path属性
//objectStore = db.createObjectStore('source_lib', { autoIncrement: true });
// { autoIncrement: true } 自动生成主键
objectStore.createIndex('path', 'path', { unique: true });// 创建索引
objectStore.createIndex('name', 'name', { unique: false });
//三个参数分别为索引名称、索引所在的属性、配置对象
}
- 添加数据
await addRecord('source_lib',{
name: '我的资源库', path: __dirname
});
await addRecord('source_lib',{
name: '我的资源库1', path: __filename
});
- 更新数据
使用.put,先封装一个函数:
// 更新数据,建议用查询到的数据对象
exports.updateRecord = (table, record) => {
return new Promise((resolve, reject) => {
let request = db.transaction([table], 'readwrite')
.objectStore(table)
.put(record);
request.onsuccess = function (event) {
console.log('数据更新成功');
resolve();
};
request.onerror = function (event) {
console.log('数据更新失败');
reject();
};
});
};
执行更新:
// 先查找数据
oneRecord = await readOneRecordByIndex('source_lib','path',__filename);
console.log('record', oneRecord);
// 更改name属性值
oneRecord.name = 'xx/oo/aa/b.fuck'+(new Date().getTime());
await updateRecord('source_lib',oneRecord);
console.log('更新后',await readOneRecordByIndex('source_lib','path',__filename));
删除数据
使用.delete,与更新数据同理
先封一个函数:
// 删除数据 indexUniqueValue 主键(keyPath)值
exports.deleteRecord = (table, indexUniqueValue) => {
return new Promise((resolve, reject) => {
let request = db.transaction([table], 'readwrite')
.objectStore(table)
.delete(indexUniqueValue);
request.onsuccess = function (event) {
console.log('数据删除成功');
resolve();
};
request.onerror = function (event) {
console.log('数据删除失败');
reject();
};
});
};
执行删除:
console.log('删除前',await readAllRecord('source_lib'));
await deleteRecord('source_lib',__dirname);
console.log('删除后',await readAllRecord('source_lib'));
参考资料
ufotool.com版权所有,转载请注明来源!