35

我正在写一篇关于 Web 应用程序的离线能力的论文。我的任务是通过具有服务器端关系数据库和客户端和服务器之间的 Ajax/JSON 流量的 Web 应用程序来展示离线存储的可能性。我的第一个实现使用了 localStorage 的方法,将每个 Ajax 响应保存为值,并将请求 URL 作为键。该应用程序运行良好。然而,在下一步中,我想(即论文要求)使用客户端数据库实现更高级的版本。由于服务器维护一个关系数据库,Web SQL 数据库将是直观的选择。但是,正如我们所知,该标准已被弃用,我不想使用未来不确定的技术。因此,我想使用 IndexedDB 来实现客户端数据库逻辑。很遗憾,

我的任务似乎相当简单: 使用 IndexedDB 在客户端实现服务器端数据库,以复制曾经从服务器获取的所有数据。使这变得不那么简单的问题是:

  • 服务器端数据库是关系型的,IndexedDB (或多或少)是面向对象的
  • 没有直观的方法来同步客户端和服务器端数据库
  • 没有直观的方法来实现 IndexedDB 中使用服务器上的外键和 JOIN 实现的关系

现在,我有一个概念,我真的很害怕开始实施。我考虑为服务器数据库中的每个表创建一个对象存储,并手动对不同对象存储中的关系对象进行编程。在我的应用程序中,简而言之,它管理一所大学的课程,我有 7 个对象存储。

我想通过一个来自服务器的 JSON 响应示例来展示我的想法(/* 这些是注释 */):

{ "course": { /* course object */
    "id":1, 
    "lecturer": { "id":"1", /* lecturer object with many attributes */ },
    "semester": { "id":"1", /* semester object with many attributes */ }, 
    /* more references and attributes */
}}

使用 IndexedDB 存储数据的算法会将应用于对象存储的每个对象存储在适当的对象存储中,并用对这些对象的引用替换这些对象。例如,上面的课程对象在对象存储“课程”中如下所示:

{ "course": { /* course object */
    "id":1, 
    "lecturer": 
    { "reference": { /* reference to the lecturer in the object store 'lecturer' */
        "objectstore":"lecturer",
        "id":"1" }
    },
    "semester":
    { "reference": { /* reference to the semester in the object store 'semester' */
        "objectstore":"semester",
        "id":"1" }
    }
    /* more references and attributes */
}}

然后使用 IndexedDB 检索数据的算法将执行以下操作(我模糊地想到了一个递归模式):

Retrieve the course object with id=1 from the object store 'course'
For each reference object in the retrieved course object, do
   Retrieve the object with id=reference.id from the object store reference.objectstore
   Replace the reference object with the retrieved object

很明显,这种实现会非常麻烦,尤其是由于 IndexedDB 的异步特性。它还会导致对数据库的许多不同事务只是为了检索课程对象,并且性能会受到很大影响(我真的不知道 IndexedDB 事务的性能如何)。

我怎样才能做得更好,更简单?

我已经看过这些代表类似问题的线程:link1link2。我在这些中看不到任何更简单的解决方案。此外,由于几个原因,我宁愿避免使用 IndexedDB 包装器框架。

我还可以想象,对于我的问题,我完全走错了 IndexedDB 的轨道。

编辑:

我最终采用我的方法将引用存储在 IndexedDB 中的对象本身中。在具有大量引用的大量数据的情况下,这可能会导致一些性能问题。然而,如果巧妙地使用,在大多数情况下可以避免大量的迭代和数据库命中,并且不需要将复杂的数据库模式存储在内存或 IndexedDB 本身中。

一般来说,我必须说,我的印象是我在某种程度上误解了 IndexedDB 作为无模式数据库的动态和直接的想法。但无论如何,我用 JavaScript 实现了所有东西,它工作正常,没有任何不一致的机会。

4

1 回答 1

25

我自己是 IndexedDB 的新手,但我也一直在思考如何将 IndexedDB 用于这样的目的。如果您还没有这样做,我建议的第一件事是查看其他键值/文档数据库(CouchDB、MongoDB 等)如何工作,因为这本质上是 IndexedDB 的数据库类型。

有几种不同的方法来处理文档数据库中的关系......至于与关系服务器端数据库同步,您可能需要创建某种自定义映射,因为某些关系方法对 IndexedDB 有意义不会很干净地映射到关系数据库。不过,我认为设置这样的映射绝对是可行的,更大的问题是如何处理 IndexedDB 中的关系,所以这就是我将重点介绍的内容......

至于您提出的解决方案,我认为它实际上可以很好地工作,并且您可以编写一个简单的查询库来帮助整合管道代码(更多内容见下文)。键值存储在通过键查找项目时非常有效,因此对每个相关对象执行此操作可能不会像您想象的那样低效……但是,我提出了另一个更好地利用索引的想法。 ..

首先,对于我提出的解决方案,您需要将“objectstore”元数据存储在“reference”对象本身之外的某个地方……它甚至根本不需要存储在 IndexedDB 中;您可以为此使用内存模式:

var schema = {
    Course: {
        fields: [id, title],
        relationships: {
            lecturers: {objectstore: 'lecturer'},
            semester: {objectstore: 'semester'},
        }
    },
    Lecturer: { ... }
    ...
};

(顺便说一句,您的 JSON 示例有一个错误……您不能拥有多个名为“reference”的键 - 它需要是一个“references”数组。)

这使您可以将 ID 值直接存储在关系字段中,以便您可以在它们上创建索引(为了清楚起见,我使用了字母前缀,尽管实际上所有这些可能具有 1 的 ID,因为 ID 值不需要在商店中唯一):

var course1 = {
    id:'C1',
    lecturers:['L1'],
    semester:1
};

var lecturer1 = {
    id:'L1',
    courses:['C1']
}

var semester1 = {
    id:'S1',
    courses:['C1']
}

当然,您必须小心所有存储/检索操作都是通过数据访问函数(例如 insert()、update()、delete())发生的,这些函数足够聪明,可以确保关系始终在两者上正确更新结束...实际上,根据您计划查询数据的方式,您可能不需要它,但这似乎是个好主意,因为您有时可能只想获取相关对象的 ID(稍后查找或不查找) ) 而不是实际检索它们。

假设您在讲师商店的“课程”字段上有一个索引。使用索引,您可以一举查找与特定课程 ID 关联的所有讲师:

lecturerStore.index("courses").get("C1").onsuccess = …

对于那个例子,这并不重要,因为课程通常只有 1-2 位讲师,但考虑如何使用索引来有效地查找特定学期中的所有课程:

coursesStore.index("semester").get("S1").onsuccess = …

请注意,在讲师示例中(多对多关系),索引需要指定为“multientry”,这意味着如果您有一个值为数组的字段,则数组的每个元素都将添加到索引。(请参阅https://developer.mozilla.org/en/IndexedDB/IDBObjectStore#createIndex ...我不确定浏览器对此的支持是什么。)

而且我相信您也可以通过索引来做其他聪明的事情,使用游标和 IDBKeyRange 来帮助执行某种“加入”操作。有关想法,请查看此链接,该链接演示了在 CouchDB 中处理关系的方法:

http://wiki.apache.org/couchdb/EntityRelationship

该链接还提到使用嵌入式文档,这是您绝对应该考虑的事情 - 并非所有对象都必须拥有自己的对象存储,尤其是对于“聚合”关系。

(顺便说一句,我不确定它对您有多大帮助,因为它没有提供太多查询方式,但实际上有人在 IndexedDB 之上实现了一个类似 CouchDB 的数据库:https://github .com/mikeal/pouchdb

除了索引之外,实现缓存机制可能也会有很大帮助。

现在,为了简化查询过程,我知道您提到不想使用包装库……但我有一个可以创建的方便 API 的想法,它可以接受这样的对象:

//select all courses taught by 'Professor Wilkins'
{
from: 'lecturer',  //open cursor on lecturer store 
where: function(lecturer) { return lecturer.name=='Professor Wilkins' }, //evaluate for each item found
select: function(lecturer) { return lecturer.courses }, //what to return from previous step
//this should be inferred in this case, but just to make it clear...
eagerFetch: function(lecturer) { return lecturer.courses }
}

我不确定实施起来会有多困难,但它肯定会让生活变得更轻松。

我已经啰嗦了很长时间,但我想提最后一件事,那就是我也一直在考虑从图形数据库中借鉴一些想法,因为它们在处理关系方面比文档数据库要好得多,而且我确实认为可以在 IndexedDB 之上实现一个图形数据库,我只是还不确定它有多实用。

祝你好运!

于 2012-01-15T00:10:22.863 回答