IndexedDB的概念问题(关系等)

38
我正在撰写有关Web应用程序离线能力的论文。我的任务是通过使用具有服务器端关系数据库和客户端与服务器之间的Ajax / JSON交互的Web应用程序来展示离线存储的可能性。我的第一个实现使用了localStorage的方法,将每个Ajax响应作为值保存,并以请求URL作为键。该应用程序工作正常。然而,在下一步中,我想(即需要论文)使用更高级别的客户端数据库进行实现。由于服务器维护关系数据库,Web SQL数据库将是直观的选择。但是,众所周知,该标准已被弃用,我不想使用未来不确定的技术。因此,我希望使用IndexedDB来实现客户端数据库逻辑。不幸的是,在阅读了大量基本都是表面浅显的材料(如待办事项应用等)后,我仍然不知道如何继续。

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

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

现在,我有一个想法,但我真的很害怕开始实施。我考虑为服务器数据库中的每个表创建一个对象存储,并手动编写不同对象存储中的关系对象。在我的应用程序中(简而言之,管理大学课程),我将有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": { /* 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实现了所有功能,它可以正常工作,并且没有任何不一致性的机会。


那么你基本上完全放弃了IndexedDB的关系型思想? - jayarjo
1个回答

26
我对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值,以便您可以在它们上创建索引(我已经使用字母前缀以增加清晰度,尽管实际上所有这些都可能具有ID值1,因为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之上实现图形数据库是可能的,只是我还不确定它是否实用。祝你好运!

我知道这有点晚了,但还是要感谢你详细的回答,真的很有价值。我在下面发布了一个答案,最初也包括了一些感激的话,但不幸的是这些部分被删除了。 - Felix
很高兴你觉得有帮助!感谢你分享论文……我还没来得及看,但肯定会的。 - Matt Browne
我刚刚发现了这个库,它似乎是一种有前途的方法来查询IndexedDB中的数据,以及许多其他数据库,使用相同的API: http://jaydata.org/ - Matt Browne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接