indexdb是什么?
indexedDB 是一个前端数据持久化解决方案(即前端缓存),由浏览器实现
indexdb的使用
- 基于文件存储。意味着其容量可达到硬盘可用空间上限
- 非关系型数据库。意味着扩展或收缩字段一般无须修改数据库和表结构(除非新增字段用做索引)
- 键值对存储。意味着存取无须字符串转换过程
- 存储类型丰富。意味着浏览器缓存中不再是只能存字符串了
- 异步: 意味着所有操作都要在回调中进行
创建数据库:
window.indexedDB.open(dbName, version)
onpen() 方法说明:
- 如果指定的数据库已经存在,直接连接数据库,返回request 实例。【因为indexedDB 是异步的,所以它所有的操作都必须建立一个请求(request),请求的结果将被封装在request实例中返回】
- 如果不存在指定的数据库,则创建数据库,然后连接数据库,返回request 实例。
- 如果传入的数据库版本比浏览器实际最新的版本低,则会抛出一个错误。
创建表:
if (!db.objectStoreNames.contains(tableName)){
db.createObjectStore(tableName, options)
}
数据库版本
- 一个数据库同一时间只能存在一个最新的版本(该版本记录了当前使用的数据库和表结构)
- 只有在修改数据库结构和表结构时,版本才需要升级
- 修改数据库结构和表结构或升级数据库版本对数据库内的数据一般没有影响(除非删除表)
indexdb的封装库,dexiejs
dexie.js是一个对浏览器indexexDB的包装库,使得我们可以更方便地操作indexedDB
获取数据库实例:
var db = new Dexie(dbname);
定义数据库结构:
db.version(lastVersion).stores(
{
localVersions: 'matadataid, content, lastversionid, date, time',
users: "++id, name, &username, *email, address.city",
relations: "++rid, userId1, userId2, [userId1+userId2], relation",
books: 'id, author, name, *categories'
}
);
上例中的localVersions,users,relations 都是数据库要包含的objectStore的名称,而他们的值则是要定义的索引,如果某个字段不需要索引,则不要写入这个索引列表,另外,如果某个字段存储的是图片数据(imageData),视频(arrayBuffer),或者特别大的字符串,不建议加入索引列表。
可以定义四种索引:
-
主键(自增):索引列表的第一个总会被当做主键,如上例中的matadataid,id,rid,如果主键前有++ 符号,说明这个字段是自增的。
-
唯一索引。如果某个索引的字段的值在所有记录中是唯一的,那么可以在它前面加& 号,比如上例中users仓库中的username字段。
-
多值索引。 如果某个字段具有多个值,那么可以在它前面加*号将其设置为多值索引,如上例中的books仓库中的categories字段,用户可以根据它多个值的任何一个值来查询它,如:
db.books.put({
id: 1,
name: 'Under the Dome',
author: 'Stephen King',
categories: ['sci-fi', 'thriller']
});
这里面的categores 是个数组,有多个值,那么我们就可以将其设置为多值索引 然后我们查询时便可以用其中一个值为查询条件去查询:
function getSciFiBooks() {
return db.books
.where('categories').equals('sci-fi')
.toArray ();
}
这里便会查询到所有类型为sci-fi 的书籍,即使这些书还可能同时属于其他分类。
- 复合索引。如果某个索引是基于多个键路径的,就可以将其设置为复合索引,格式为[A+B],如上例中relations中的[userId1+userId2] 索引。下面是一个例子:
// Store relations
await db.relations.put({
rid: 1,
userId1: '1',
userId2: '2'
});
// Query relations:
const fooBar = await db.relations.where({
userId1: '1',
userId2: '2'
}).first();
当你定义了复合索引后,就可以在where查询子句中传入一个复合条件对象,该示例就将查询出userId1为1,userId2为2的记录,但同时,你也可以只以其中一个字段为索引条件进行查询:
db.relations
.where('userId1')
.equals("1")
.toArray();
每次页面刷新都会重新获取一遍实例,重新运行一遍数据库定义逻辑,不会有问题吗?
答案是,不会有问题。如果你传入的lastVersion与数据库当前版本一致,则即使重新运行一遍数据库定义逻辑,也不会覆盖你第一次运行时定义的结构(即使你修改了数据库结构),在这种情况下,你已经存入的数据不会受任何影响。只有当lastVersion版本高于当前数据库版本时,才会去更新数据库结构(即使结构没有任何变化),这时如果定义中的仓库被删除了,那对应的仓库会被删除,如果定义中是索引被删除了,那仓库中对应的索引也会被删除。
【只有执行完version().stores()方法之后再至少进行一次数据库操作(比如open(), get(),put()等),这个才可以生效,因为versions().store()只是定义结构,并不立即生效,而是等到进行数据库操作时才会打开数据库进行更新】
dexie.js的一些概念
在进行增删改查操作介绍前,我们先明确一些dexie.js里面的概念。
-
Dexie :数据库类,返回一个数据库实例, 具体实践中就是 db = new Dexie()后所获得的的db。
-
Table: 表类,返回一个表实例(对应于原生的objectStore),具体实践中对应db.tableName,如db.users,db.books等等。
-
Collection: 数据集合类,返回查询到的数据集合,它的构造方法是私有的,所以用户无法通过new Collection()来创建它的实例,而是只能通过where子句和Table实例的一些方法获得。
-
whereClause:where子句,标识索引或主键上的筛选器。
常用方法:
- 增
await db.friends.add({name: "Josephine", age: 21}); //or await db.friends.bulkAdd([ {name: "Foo", age: 31}, {name: "Bar", age: 32} ]);
- 删
const oneWeekAgo = new Date(Date.now() - 60*60*1000*24*7);
await db.logEntries .where('timestamp').below(oneWeekAgo) .delete();
- 改
await db.friends.put({id: 4, name: "Foo", age: 33});
//or
await db.friends.bulkPut([
{id: 4, name: "Foo2", age: 34},
{id: 5, name: "Bar2", age: 44}
]);
await db.friends.update(4, {name: "Bar"});
await db.customers
.where("age")
.inAnyRange([ [0, 18], [65, Infinity] ])
.modify({discount: 0.5});
- 查
const someFriends = await db.friends
.where("age").between(20, 25)
.offset(150).limit(25)
.toArray();
await db.friends
.where("name").equalsIgnoreCase("josephine")
.each(friend => {
console.log("Found Josephine", friend);
});
const abcFriends = await db.friends
.where("name")
.startsWithAnyOfIgnoreCase(["a", "b", "c"])
.toArray();
await db.friends
.where('age')
.inAnyRange([[0,18], [65, Infinity]])
.modify({discount: 0.5});
const angelasSortedByLastName = await db.friends
.where('[firstName+lastName]')
.between([["Angela", ""], ["Angela", "\uffff"])
.toArray()
const best5GameSession = await db.gameSessions.orderBy("score").reverse() .limit(5) .toArray();
参考: