IndexedDB数据库使用

最近业务需求中需要使用到数据库,在翻看vscode源码过程中,发现vscode一共用了两种数据库存储数据,SQLiteIndexedDB,本文主要对IndexedDB做下讲解

简介

2008 年左右,网站 、 论坛、社交网络开始高速发展,传统的关系型数据库在存储及处理数据的时候受到了很大的挑战 ,其中主要体现在以下几点:

  • 难以应付每秒上万次的高并发数据写入 。
  • 查询上亿量级数据的速度极其缓慢 。
  • 分库、分表形成的子库到达一定规模后难以进一步扩展 。
  • 分库、分表 的规则可能会因为需求变更而发生变更。
  • 修改表结构困难 。

在很多 互联网应用场景下 , 对数据联表的查询需求不是那么强烈 ,也并不需要在数据写入后立刻读取,但对数据的读取和并发写入速度有非常高的要求 。 在这样的情况下 ,非关系型数据库得到高速的发展 。

关系型数据库:

  • Oracle
  • Mysql

非关系型数据库:

  • MongoDB
  • Redis
  • indexedDB

背景

随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。

  • Cookies存储容量太小,只能存4kb的内容,而且每次与服务端交互,同域下的Cookie还会被携带到服务端,也没有关联查询、条件查询的机制。
  • LocalStorage存储容量也很小,大概不会超过10M,它是以键值对形式保存数据的,同样也没有关联查询、条件查询的机制。
  • SessionStorage最大的问题是,每次关闭应用程序,它里面的内容会被清空,想持久化存储数据,就不用考虑它了。
  • WebSql诸般特性都挺好,无奈这个技术已经被W3C委员会否决了,不知道哪天Electron也不支持了,到时就傻眼了。

所以,需要一种新的解决方案,这就是 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 对象)。

使用

  • 数据库:IDBDatabase 对象
  • 对象仓库:IDBObjectStore 对象
  • 索引: IDBIndex 对象
  • 事务: IDBTransaction 对象
  • 操作请求:IDBRequest 对象
  • 指针: IDBCursor 对象
  • 主键集合:IDBKeyRange 对象

我们以类的形式对它封装:

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());

有问题欢迎留言

参考阮一峰日志

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/woyebuzhidao321/article/details/129623546