Parse.com 中的关系使用 REST API





5.00/5 (2投票s)
使用 Angular `$http` 和 Parse REST API 处理关系
引言
Parse 是一个云服务平台,提供后端存储服务。它提供了多种应用程序编程接口(API),例如 .NET、JavaScript 和 PHP。
在我们的 Web 应用程序中,我们将使用它的 REST API。这使我们能够从任何能够发送 HTTP 请求的编程语言编写的应用程序与 Parse 上的数据进行交互。请求头应包含 `X-Parse-Application-Id` 和 `X-Parse-REST-API-Key`,但为了简洁起见,以下代码将省略这些消息。此外,我们经常使用的 `objectId` 是 Parse 内置的标准 ID,用于标识单个对象记录。
内容列表:
背景
对象之间的关系
基于经典的数据库关系模型,对象之间存在三种(或四种)关系:
- 一对一关系:一个对象与另一个对象关联;
- 一对多(多对一)关系:一个对象拥有多个相关对象;
- 多对多关系:它表示多个对象之间复杂的关联。
Parse 中的关系
为了应对这些关系,Parse 提供了四种方式:
- Pointer:适用于一对一和一对多关系;
- Array:适用于一对多和多对多关系;
- Parse Relation:适用于多对多关系;
- Join Table:适用于多对多关系。
深入了解之前
接下来的内容将基于“公共图书馆”项目,讨论在 Parse 中构建关系的使用。在我们的 Web 应用程序中,我们构建了一个具有以下两个双向关联的“出版商-图书-作者”设计模型。
在我们的例子中,不存在“一对一”关系,这种情况通常也很少见,因此本文将不作讨论。
此外,Parse 官方在线文档关于 关系 的内容仅展示了在开发 Android 或 iOS 应用程序时如何解决 Parse 中的关系。其中存在许多不完全适用于 REST API 的系统级函数,但它们确实提供了逻辑解决方案。
一对多关系
根据“出版商-图书-作者”设计模型,“出版商”和“图书”之间出现一对多关系。一个出版商可能没有或出版了许多图书,一本图书可能没有或只有一个出版商。
根据 Parse 中关系的介绍,我们可以使用 **Pointer** 或 **Array** 来建立它们之间的关系。
使用 Pointer 实现一对多关系
使用 Pointer 在出版商和图书对象之间建立关系,其起点是一个不包含关联的设计模型,如下所示:
- `publisher` 是 `Book` 的一个属性,它将没有值或只有一个值;
- `publishedBook` 是 `Publisher` 的一个反向引用属性,它将不存储值或存储多个值。
创建/更新 Pointer
假设我们有一个 `Publisher` 对象和两个 `Book` 对象,我们想将所有 `Book` 对象与此出版商关联。
twoBooks.forEach( function( book) {
$http( {
method: 'PUT',
url: 'https://api.parse.com/1/classes/Book/' + book.objectId,
data: {
publisher: {
__type: 'Pointer',
className: 'Publisher',
objectId: publisher.objectId
}
}
});
});
`objectId` 将作为属性保存在每个图书对象中。在 Parse Core 中,一本图书的记录将是:
此出版商的记录将保留:
检索对象
如果我们想获取一本已知图书的出版商信息:
$http( {
method: 'GET',
url: 'https://api.parse.com/1/classes/Publisher/' + book.publisher.objectId
});
当属性类型为 Pointer 时,Parse 提供了一个 `include` 操作,用于在一次查询中返回多种类型的相关对象。在我们的例子中,`Book` 的 `publisher` 属性是 Pointer,即使在获取所有图书的同时,我们也可以获取所有相关的出版商对象,而无需再次发送 GET 请求来获取所有对应的出版商。
// Get all books
$http( {
method: 'GET',
url: 'http://api.parse.com/1/classes/Book',
params: { include: 'publisher'}
});
如果我们想获取此出版商出版的所有图书:
$http( {
method: 'GET',
url: 'https://api.parse.com/1/classes/Book',
params: {
where: {
publisher: {
__type: 'Pointer',
className: 'Publisher',
objectId: publisher.objectId
}
}
}
});
删除 Pointer
假设我们要删除所有关系:
twoBooks.forEach( function ( book) {
$http( {
method: 'PUT',
url: 'https://api.parse.com/1/classes/Book/' + book.objectId,
data: { publisher: { __op: 'Delete'}}
});
});
此 HTTP 请求会将 `ooTkye1JWh` 的 `publisher Pointer<publisher>` 字段值设置为 `undefined`,从而使该图书在记录中没有出版商。
此外,如果我们没有删除关联而是删除了出版商,这两本书的记录不会改变,这意味着出版商的 `objectId` 仍然存在于 `publisher` 字段中,但此 Pointer 将指向空。出于安全原因,最好在删除出版商后删除 Pointer。
使用 Array 实现一对多关系
使用 Array 在出版商和图书对象之间建立关系,其起点是一个不包含关联的设计模型,如下所示:
- `publisher` 是 `Book` 的一个反向引用属性,它将没有值或只有一个值;
- `publishedBook` 是 `Publisher` 的一个属性,它将以对象数组的形式存储零个或多个值。
与 Pointer 相比,我们的重点已从图书对象转移到出版商对象。
Parse 提供了 Add、AddUnique 和 Remove 操作来处理 Array 以及一个 对 Array 值进行查询 的方法。经过一些测试,我发现这些操作仅适用于字符串数组,而不适用于对象数组,因为这些查询无法深入到每个单独的对象。
此外,如果我们在数组中只存储 `objectId` 而不是整个对象,那么这个数组将构成一个 Pointer 列表,这意味着使用 Array 的方式将获得与使用 Pointer 相同的所有优点。
创建/更新 Array
假设我们知道一个出版商出版了两本书。如果我们想建立这种关系,应该将这两本书添加到此出版商的 `publishedBooks` 中。
对象数组
$http( {
method: 'PUT',
url: 'https://api.parse.com/1/classes/Publisher/' + publisher.objectId,
data: { publishedBooks: [ bookObject1, bookObject2]}
});
此出版商的记录将更改为:
对象 ID 数组
$http( {
method: 'PUT',
url: 'https://api.parse.com/1/classes/Publisher/' + publisher.objectId,
data: {
publishedBooks: {
__op: 'AddUnique', // can be also 'Add' or 'Remove'
objects: [ bookObject1.objectId, bookObject2.objectId]
}
}
});
检索对象
对象数组
如果我们有一个出版商并想获取关于该出版商的所有信息,包括其出版的书籍,那么将无需执行任何操作,因为该出版商的记录已经包含了其出版书籍的相关信息。
如果我们想获取一本已知图书的出版商信息,我们必须先加载所有出版商记录,然后手动从 `publishedBooks` 数组中查找对应的图书。
$http( {
method: 'GET',
url: 'http://api.parse.com/1/classes/Publisher'
})
.success( function ( data) {
var publishers = data.results;
publishers.forEach( function ( p) {
if ( p.publishedBooks.length > 0) {
p.publishedBooks.forEach( function ( pb) {
if ( pb.objectId == book.objectId) {
console.log("Found the publisher of this book finally. Could you try to use Pointer?");
}
});
}
});
});
对象 ID 数组
如果我们有一个出版商并想获取关于该出版商的所有信息,包括其出版的书籍:
publisher.publishedBooks.forEach( function ( pb) {
$http( {
method: 'GET',
url: 'https://api.parse.com/1/classes/Book/' + pb.objectId
});
});
如果我们想获取一本已知图书的出版商信息,我们可以简单地发送一个包含 `where` 的请求,让 Parse 为我们处理(在我看来,服务器总是比我的个人电脑更强大)。
$http( {
method: 'GET',
url: 'https://api.parse.com/1/classes/Publisher',
params: { where: { publishedBooks: book.objectId}}
});
删除 Array
假设我们要删除所有关系:
$http( {
method: 'PUT',
url: 'https://api.parse.com/1/classes/Publisher/' + publisher.objectId,
data: { publishedBooks: { __op: 'Delete'}}
});
多对多关系
根据“出版商-图书-作者”设计模型,“图书”和“作者”之间出现多对多关系。一本书可能没有或有多位作者,一位作者可能写过零本或多本书。
根据 Parse 中关系的介绍,我们可以使用 **Parse Relation**、**Join Table** 或 **Array** 来建立它们之间的关系。
使用 Parse Relation 实现多对多关系
使用 Parse Relation 在图书和作者对象之间建立关系,其起点是一个不包含关联的设计模型,如下所示:
- `authors`(类型为 Parse Relation)是 `Book` 的一个属性,它将保存所有相关的作者;
- `authoredBooks`(类型为 Parse Relation)是 `Author` 的一个属性,它将存储所有相关的图书。
在使用 Parse Relation 方法之前,我们先简单讨论一下 Parse Relation 到底是什么。
如果我们创建一些图书和作者作为 Parse 中的测试数据,带有 Parse Relations 的图书记录将是:
带有 Parse Relations 的作者记录将是:
在图书记录中点击“View Relations”后,左侧会显示一个列表,其中包含所有相关作者的信息。
因此,Parse Relations 非常类似于 Pointer 数组与对应对象的组合。
Parse 提供了 AddRelation 和 RemoveRelation 操作来处理 Parse Relation,用于更新对象。还有一些查询操作,Parse 将它们命名为关系查询,但这些只适用于一对多关系。
创建/更新 Parse Relation
假设我们有一本书,想将其关联到两位作者。
// add relation from book to authors
$http( {
method: 'PUT',
url: 'https://api.parse.com/1/classes/Book/' + book.objectId,
data: {
authors: {
__op: 'AddRelation', // or 'RemoveRelation'
objects: [{
__type: 'Pointer',
className: 'Author',
objectId: author1.objectId
},
{
__type: 'Pointer',
className: 'Author',
objectId: author2.objectId
}]
}
}
})
如果我们有一个作者,想将其关联到两本书。
// add relation from author to books
$http( {
method: 'PUT',
url: 'https://api.parse.com/1/classes/Author/' + author.objectId,
data: {
authoredBooks: {
__op: 'AddRelation', // or 'RemoveRelation'
objects: [{
__type: 'Pointer',
className: 'Book',
objectId: book1.objectId
},
{
__type: 'Pointer',
className: 'Book',
objectId: Book2.objectId
}]
}
}
})
请注意,如果我们更新/删除作者(姓名、出生日期、去世日期)或图书(标题、年份、出版商)记录的基本信息,则无需更改 Parse Relations 部分。
检索对象
假设我们有一本书,想查找这本书的所有作者。有两种方法可以实现:
-
检查作者对象的 `authoredBooks` 字段。
$http({ method: 'GET', url: 'https://api.parse.com/1/classes/Author', params: { where: { authoredBooks: { __type: 'Pointer', className: 'Book', objectId: book.objectId } } } })
-
使用 Parse 内置的 `$relatedTo` 操作检查 Parse Relation。
$http({ method: 'GET', url: 'https://api.parse.com/1/classes/Author', params: { where: { $relatedTo: { object: { __type: 'Pointer', className: 'Book', objectId: book.objectId }, key: 'authors' } } } })
如果我们有一个作者,想查找他/她的书籍,也有相同的两种方法可以实现:
-
检查图书对象的 `authors` 字段。
$http({ method: 'GET', url: 'https://api.parse.com/1/classes/Book', params: { where: { authors: { __type: 'Pointer', className: 'Author', objectId: author.objectId } } } })
-
使用 Parse 内置的 `$relatedTo` 操作检查 Parse Relation。
$http({ method: 'GET', url: 'https://api.parse.com/1/classes/Book', params: { where: { $relatedTo: { object: { __type: 'Pointer', className: 'Author', objectId: author.objectId }, key: 'authoredBooks' } } } })
删除 Parse Relation
Parse 不允许用户直接删除 `Relation` 类型的字段,我们只能使用其 `RemoveRelation` 操作和请求循环来删除所有关联的记录。
使用 Array 实现多对多关系
与使用 Array 实现一对多关系相比,此方法的基本思想更像是构建两个一对多关系。
- 当我们使用对象数组时,应该构建一个作者对象数组,然后将其保存到图书记录的 `authors` 中;构建一个图书对象数组,然后将其保存到作者记录的 `authoredBooks` 中。
- 当我们使用对象 ID 数组时,应该构建一个作者 ID 数组,然后将其保存到图书记录的 `authors` 中;构建一个图书 ID 数组,然后将其保存到作者记录的 `authoredBooks` 中。
使用 Join Table 实现多对多关系
Join Table 是源自经典数据库的思想。当存在多对多关系时,我们将两边的 `objectId` 组合起来,构建一个新的单独的表来跟踪关系。
`(bookObjectId, authorObjectId)` 将组合成一个键,用于单个记录。
创建 Join Table
假设我们有两个图书和两个作者,每个作者写了一本书。
// author1 had authored book1
$http( {
method: 'POST',
url: 'https://api.parse.com/1/classes/joinBookAuthor',
data: {
bookObjectId: book1.objectId,
authorObjectId: author1.objectId
}
});
// author2 had authored book2
$http( {
method: 'POST',
url: 'https://api.parse.com/1/classes/joinBookAuthor',
data: {
bookObjectId: book2.objectId,
authorObjectId: author2.objectId
}
});
更新 Join Table
假设 author1 还写了 book2。
$http( {
method: 'POST',
url: 'https://api.parse.com/1/classes/joinBookAuthor',
data: {
bookObjectId: book2.objectId,
authorObjectId: author1.objectId
}
});
然后,假设我们想改变 author1 不再写任何书。
$http( {
method: 'GET',
url: 'https://api.parse.com/1/classes/joinBookAuthor',
params: { where: { authorObjectId: author1.objectId}}
})
.success( function( data) {
var joinBookAuthorRecords = data.results;
joinBookAuthorRecords.forEach( function ( jba) {
$http( {
method: 'DELETE',
url: 'https://api.parse.com/1/classes/joinBookAuthor/' + jba.objectId
});
});
});
检索对象
假设我们得到了 book1,想查找它所有的作者。
$http( {
method: 'GET',
url: 'https://api.parse.com/1/classes/joinBookAuthor',
params: { where: { bookObjectId: book1.objectId}}
})
.success( function( data) {
var joinBookAuthorRecords = data.results;
joinBookAuthorRecords.forEach( function ( jba) {
$http( {
method: 'GET',
url: 'https://api.parse.com/1/classes/Author/' + jba.authorObjectId
});
});
});
删除 Join Table
Parse 中的Join Table 被保存为一个类,Parse 不提供删除类的函数。
结束之前
基于使用 REST API 的 Parse 平台,**Pointer** 用于一对多关系,**Parse Relation** 用于多对多关系,是较好的选择。
我们已经讨论了在 Parse 中使用 Array 来设置 Publisher-Book 和 Book-Author 关系的方法。Array 可以灵活使用,但这些关系仅在我们的应用程序中逻辑存在,而不在数据库中。
Join Table 是多对多关系的直接方式,但在我们的例子中,它需要比其他方法发送更多的请求。