最近业务需求中需要使用到数据库,在翻看
vscode
源码过程中,发现vscode一共用了两种数据库存储数据,SQLite
和IndexedDB
,本文主要对IndexedDB做下讲解
2008 年左右,网站 、 论坛、社交网络开始高速发展,传统的关系型数据库
在存储及处理数据的时候受到了很大的挑战 ,其中主要体现在以下几点:
在很多 互联网应用场景下 , 对数据联表的查询需求不是那么强烈 ,也并不需要在数据写入后立刻读取,但对数据的读取和并发写入速度有非常高的要求 。 在这样的情况下 ,非关系型数据库
得到高速的发展 。
关系型数据库:
非关系型数据库:
随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。
所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景。
通俗地说,IndexedDB 就是浏览器提供的本地数据库,一个基于 JavaScript 的面向对象数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
IndexedDB 具有以下特点。
(1)键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
(2)异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
(3)支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
(4)同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
(5)储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
(6)支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
我们以类的形式对它封装:
export class IndexedDB {
static async create(name: string, version: number | undefined, stores: string[]): Promise<IndexedDB> {
const database = await IndexedDB.openDatabase(name, version, stores);
return new IndexedDB(database, name);
}
static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise<IDBDatabase> {
mark(`code/willOpenDatabase/${name}`);
try {
return await IndexedDB.doOpenDatabase(name, version, stores);
} catch (err) {
if (err instanceof MissingStoresError) {
console.info(`Attempting to recreate the IndexedDB once.`, name);
try {
// Try to delete the db
await IndexedDB.deleteDatabase(err.db);
} catch (error) {
console.error(`Error while deleting the IndexedDB`, getErrorMessage(error));
throw error;
}
return await IndexedDB.doOpenDatabase(name, version, stores);
}
throw err;
} finally {
mark(`code/didOpenDatabase/${name}`);
}
}
private static doOpenDatabase(name: string, version: number | undefined, stores: string[]): Promise<IDBDatabase> {
return new Promise((c, e) => {
const request = window.indexedDB.open(name, version);
request.onerror = () => e(request.error);
request.onsuccess = () => {
const db = request.result;
for (const store of stores) {
if (!db.objectStoreNames.contains(store)) {
console.error(`Error while opening IndexedDB. Could not find '${store}'' object store`);
e(new MissingStoresError(db));
return;
}
}
c(db);
};
request.onupgradeneeded = () => {
const db = request.result;
for (const store of stores) {
if (!db.objectStoreNames.contains(store)) {
db.createObjectStore(store);
}
}
};
});
}
private static deleteDatabase(indexedDB: IDBDatabase): Promise<void> {
return new Promise((c, e) => {
// Close any opened connections
indexedDB.close();
// Delete the db
const deleteRequest = window.indexedDB.deleteDatabase(indexedDB.name);
deleteRequest.onerror = (err) => e(deleteRequest.error);
deleteRequest.onsuccess = () => c();
});
}
private database: IDBDatabase | null = null;
private readonly pendingTransactions: IDBTransaction[] = [];
constructor(database: IDBDatabase, private readonly name: string) {
this.database = database;
}
hasPendingTransactions(): boolean {
return this.pendingTransactions.length > 0;
}
close(): void {
if (this.pendingTransactions.length) {
this.pendingTransactions.splice(0, this.pendingTransactions.length).forEach(transaction => transaction.abort());
}
if (this.database) {
this.database.close();
}
this.database = null;
}
runInTransaction<T>(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest<T>[]): Promise<T[]>;
runInTransaction<T>(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest<T>): Promise<T>;
async runInTransaction<T>(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest<T> | IDBRequest<T>[]): Promise<T | T[]> {
if (!this.database) {
throw new Error(`IndexedDB database '${this.name}' is not opened.`);
}
const transaction = this.database.transaction(store, transactionMode);
this.pendingTransactions.push(transaction);
return new Promise<T | T[]>((c, e) => {
transaction.oncomplete = () => {
if (isArray(request)) {
c(request.map(r => r.result));
} else {
c(request.result);
}
};
transaction.onerror = () => e(transaction.error);
const request = dbRequestFn(transaction.objectStore(store));
}).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1));
}
async getKeyValues<V>(store: string, isValid: (value: unknown) => value is V): Promise<Map<string, V>> {
if (!this.database) {
throw new Error(`IndexedDB database '${this.name}' is not opened.`);
}
const transaction = this.database.transaction(store, 'readonly');
this.pendingTransactions.push(transaction);
return new Promise<Map<string, V>>(resolve => {
const items = new Map<string, V>();
const objectStore = transaction.objectStore(store);
// Open a IndexedDB Cursor to iterate over key/values
const cursor = objectStore.openCursor();
if (!cursor) {
return resolve(items); // this means the `ItemTable` was empty
}
// Iterate over rows of `ItemTable` until the end
cursor.onsuccess = () => {
if (cursor.result) {
// Keep cursor key/value in our map
if (isValid(cursor.result.value)) {
items.set(cursor.result.key.toString(), cursor.result.value);
}
// Advance cursor to next row
cursor.result.continue();
} else {
resolve(items); // reached end of table
}
};
// Error handlers
const onError = (error: Error | null) => {
console.error(`IndexedDB getKeyValues(): ${toErrorMessage(error, true)}`);
resolve(items);
};
cursor.onerror = () => onError(cursor.error);
transaction.onerror = () => onError(transaction.error);
}).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1));
}
}
在服务里面使用
import { IndexedDB } from 'vs/base/browser/indexedDB'; //引入
private readonly whenConnected!: Promise<IndexedDB>; //定义私有变量存储数据库
初始化 constructor
this.whenConnected = this.connect(); //链接数据库
private async connect(): Promise<IndexedDB> {
try {
return await IndexedDB.create('indexedDB-test', undefined, ['test-store1']);
} catch (error) {
throw error;
}
}
在方法里面使用
let indexedDB = await this.whenConnected;
// 增
try {
await indexedDB.runInTransaction('test-store1', 'readwrite', store => store.add({ 'test': '222' }, 'key3'));
} catch (e) {
console.log('存储数据出错')
}
// 删
try {
await indexedDB.runInTransaction('test-store1', 'readwrite', store => store.delete('key3'));
} catch (e) {
console.log('删除数据出错');
}
// 改
try {
await indexedDB.runInTransaction('test-store1', 'readwrite', store => store.put({ 'lichangwei': '123' }, 'key3'));
} catch (e) {
console.log('删除数据出错');
}
// 查
const value = await indexedDB.runInTransaction('test-store1', 'readwrite', store => store.getAll());
有问题欢迎留言
参考阮一峰日志