IndexedDB 的包装函数

问题描述 投票:0回答:5

我需要为 iPad/平板电脑设备构建一个离线 HTML5 Web 应用程序,用户可以从服务器下载数据集(数据表)并将其存储在设备上。然后,用户可以断开与服务器的连接并在设备上本地查看/编辑数据。这适用于在没有蜂窝网络覆盖的偏远地区锻炼并需要收集/更新数据的人。当他们回到办公室时,他们可以将数据同步/上传回服务器。它需要是 HTML5 的原因是它与平台无关,即只要它有支持 HTML5 的现代 Web 浏览器就可以在 iOS、Android 等上运行。

现在我已经使用 HTML5 本地存储(用于数据)和 HTML5 离线应用程序缓存(用于页面/css/js/图像)构建了系统,并且它可以很好地处理小型数据集(我可以查看、编辑和离线时保存并在线时加载/同步)。现在我需要扩展到 10,000 行数据。它可以工作,但速度相当慢,在 Intel 四核 8GB 机器上加载时,浏览器会挂起 10 秒。

所以我一直在研究一些比本地存储更好的替代方案:

1)WebSQL:能够使用 SQL 语言查询数据并进行联接等。问题是它现在已被弃用,并且不再受支持,因此我不想投入时间为其构建一些东西。

2) IndexedDB:使用对象存储(从技术上讲,我已经使用本地存储 API 存储对象并使用 JSON 存储)。可能会更快,因为它使用带有 SQL lite 后端的索引。有很多样板代码可以完成简单的任务,例如创建数据库、添加数据库、读取数据库、迭代数据库。我只想做一个像

select(xyc, abc).where(abc = 123).limit(20)
这样的简单查询,但必须编写大量 JavaScript 代码才能完成。如何编写自己的代码来在表之间进行联接,任何地方都有示例?

我找到了一个 jQuery 插件,它可能会让生活变得更简单。是否有其他库或其他库可以减轻使用 IndexedDB 的痛苦?

非常感谢!

javascript jquery local-storage indexeddb web-storage
5个回答
4
投票

我有一个开源 web 数据库包装器,它支持 IndexedDB 和 WebSql。

版本迁移是在sense后面处理的。以下代码迁移(或初始化)到版本 2。

schema_ver2 = {
    version: 2,
    size: 2 * 1024 * 1024, // 2 MB
    stores: [{
        name: 'ydn_obj',
        keyPath: 'id.value',
        indexes: [{
            name: 'age',
            type: 'INTEGER'  // type is require for WebSql
        }]
    }]
}
db = new ydn.db.Storage('db name', schema_ver2)

Query非常灵活且强大。例如:

q = db.query('customer').when('age', '>=', 18 , '<', 25).where('sex', '=', 'FEMALE')
young_girls = q.fetch(10, 2); // limit and offset

如果年龄被索引,再次使用更有效的键范围查询:

q = db.query('customer', 'age').bound(18, 25, true).where('sex', '=', 'FEMALE')

还支持交易

p123 = db.tkey('player', 123);
db.runInTransaction(function() {
   p123.get().success(function(p123_obj) {
        p123_obj.health += 10;
        p123.put(p123_obj);
   });
}, [p123]);

3
投票

尝试linq2indexeddb。它具有您想要的查询接口 + 以及用于 websql 的 indexeddb shim 还支持 WebSQL API。


1
投票

您考虑过[草坪椅][1]吗?它提供了对底层存储的良好抽象,还有用于查询、聚合和分页数据的插件。以查询为例:

  // basic searching
    this.where('record.name === "brian"', 'console.log(records)') 
    this.where('record.name != ?', username, 'console.log(records)')

    // sorting results
    this.where('name === "brian"').asc('active', 'console.log(records)')

我能看到的唯一潜在缺点是它似乎无法处理迁移,并且通用性似乎没有创建索引等的方法。

关于连接,IndexedDB 被设计为面向文档(无 SQL)的存储,而不是关系数据库,但是考虑到这是一种常见的情况,似乎有两种选择:

1)光标迭代数据项 2)如果上面的方法太慢,您还可以创建一个专用的键值对象存储,然后可以使用它在相关存储中进行索引查找。根据您的加入要求的数量,这可能是一件苦差事。


0
投票

我认为 JsStore 适合你。

假设您的查询在 sql 中看起来像这样 -

select * from table_name where column1='abc' limit 20

JsStore - 将会是

var Connection = new JsStore.Instance("YourDbName");
Connection.select({
    From: "table_name"
    Where: {
        Column1: 'abc',
    },
    Limit:20,
    OnSuccess:function (results){
        console.log(results);
    },
    OnError:function (error) {
        console.log(error);
    }
});

因此您可以使用JsStore编写类似sql的查询。


0
投票

将 IndexedDB 的包装器版本同步到 localStorage API https://gist.github.com/xnohat/b7aa5035278478871697b7ad6255efb2

class IndexedDBStorage {
  constructor(dbName = 'localStorageDB', storeName = 'localStorageStore') {
      this.dbName = dbName;
      this.storeName = storeName;
      this._init();
      this.cache = {};
  }

  _init() {
      const request = window.indexedDB.open(this.dbName, 1);
      request.onerror = (event) => console.error('Error opening indexedDB');
      request.onsuccess = async (event) => {
          this.db = event.target.result;
          await this._populateCache();
          this._syncCache();
      };
      request.onupgradeneeded = (event) => {
          const db = event.target.result;
          db.createObjectStore(this.storeName);
      };
  }

  async _populateCache() {
      const store = this._getStore();
      return new Promise((resolve, reject) => {
          const request = store.openCursor();
          request.onsuccess = (event) => {
              const cursor = event.target.result;
              if (cursor) {
                  this.cache[cursor.key] = cursor.value;
                  cursor.continue();
              } else {
                  resolve(); // Finished populating the cache
              }
          };
          request.onerror = (event) => reject('Error populating cache');
      });
  }

  async _syncCache() {
      for (const key in this.cache) {
          await this._asyncSetItem(key, this.cache[key]);
      }
  }

  async _asyncSetItem(key, value) {
      const store = this._getStore('readwrite');
      return new Promise((resolve, reject) => {
          const request = store.put(value, key);
          request.onsuccess = () => resolve();
          request.onerror = (event) => reject('Error storing value');
      });
  }

  _getStore(mode = 'readonly') {
      const transaction = this.db.transaction([this.storeName], mode);
      return transaction.objectStore(this.storeName);
  }

  setItem(key, value) {
      this.cache[key] = value;
      this._asyncSetItem(key, value).catch(console.error);
  }

  getItem(key) {
      if (this.cache[key]) {
          return this.cache[key];
      }
      // Fetch from indexedDB and store in cache (in the background)
      this._asyncGetItem(key).then(value => {
          this.cache[key] = value;
      });
      return null; // or some default value
  }

  async _asyncGetItem(key) {
      const store = this._getStore();
      return new Promise((resolve, reject) => {
          const request = store.get(key);
          request.onsuccess = (event) => resolve(event.target.result);
          request.onerror = (event) => reject('Error retrieving value');
      });
  }

  removeItem(key) {
      delete this.cache[key];
      this._asyncRemoveItem(key).catch(console.error);
  }

  async _asyncRemoveItem(key) {
      const store = this._getStore('readwrite');
      return new Promise((resolve, reject) => {
          const request = store.delete(key);
          request.onsuccess = () => resolve();
          request.onerror = (event) => reject('Error removing value');
      });
  }

  clear() {
      this.cache = {};
      this._asyncClear().catch(console.error);
  }

  async _asyncClear() {
      const store = this._getStore('readwrite');
      return new Promise((resolve, reject) => {
          const request = store.clear();
          request.onsuccess = () => resolve();
          request.onerror = (event) => reject('Error clearing store');
      });
  }
}

const idbStorage = new Proxy(new IndexedDBStorage(), {
  get: (target, prop) => {
      if (typeof target[prop] === 'function') {
          return target[prop].bind(target);
      }
      return target.getItem(prop);
  },
  set: (target, prop, value) => {
      target.setItem(prop, value);
      return true;
  }
});

window.idbStorage = idbStorage;
© www.soinside.com 2019 - 2024. All rights reserved.