Android 内容提供商 SQLite 连接表

Android Content Provider SQLite Junction Tables

我正在实施一个由相当复杂的 SQLite 数据库模式支持的内容提供程序。数据库有几个连接 table,我不确定它们是否应该对内容提供者的用户可见。

parent table 是通过 Contract 公开的,每个都有自己的内容 URI 等。现在,当通过 ContentResolver#applyBatch() 方法插入数据时,我为每个 table 的内容 URI 创建了 ContentProviderOperation。到目前为止一切都很清楚。但我的问题是,应该如何填充联结 table,因为它们没有自己的内容 URI?

为了说明这一点,这里有一个例子。我有 2 "parent" table、MoviesActors。它们之间的关系是 many-to-many,因此我有一个名为 MoviesActors.

的结点 table

要批量插入,我执行以下操作:

List<ContentProviderOperation> operations = new ArrayList<>;
// movie
operations.add(ContentProviderOperation.newInsert(Contract.Movie.ContentUri).withValue("movie_id", "23asd2kwe0231123sa").build());
// actor
operations.add(ContentProviderOperation.newInsert(Contract.Actor.ContentUri).withValue("actor_id", "89asd02kjlwe081231a").build());
getContentResolver().applyBatch(authority, operations);

结点 table MoviesActors 应插入包含 movie_idactor_id 的行。在这种情况下,我该如何处理连接点 table?

我想到的唯一一件事是扩展合约,使内容 URI 指向联结点 tables 并添加另一个 ContentProviderOperation,否则,您如何沟通 movie_idactor_idContentProvider#applyBatch()?

我宁愿不向 ContentProvider 的用户公开联结 table,但我在这里可能是错误的...也许这就是应该在 Android 上完成的方式?

我已经搜索这个主题好几天了,但没有找到答案。 任何帮助将不胜感激。

加分题:

是否有必要通过合约公开每一个table?例如,当在 one-to-many 关系中有 child table 时。我特别指的是 Insert/Update/Delete 因为我知道使用 Query 我可以简单地进行连接,但也许我在这里也错了。

非常感谢!

注意:我对第 3 方库解决方案不感兴趣。

我认为您从错误的角度解决问题。您正在尝试设计一个接口来匹配您的数据库结构,但接口应该放在第一位。

首先,界面应该满足您的ContentProvider 客户端的所有要求。如果您的 ContentProvider 客户端需要访问联结 table,您必须公开它(以某种方式,见下文),否则您不必公开。一个好的接口隐藏了实际的实现细节,因此 ContentProvider 客户端不需要关心 ContentProvider 是否由 SQLite 数据库、一堆 in-memory 映射甚至 web-service.

此外,您不应将 ContentProvider 仅视为数据库的接口,而将 Contract 视为数据库模式。 ContentProvider 比它更通用、更强大。主要区别在于 ContentProvider 由 URI 寻址,而在 SQL 中您只有 table 名称。与 table 名称相反,URI 具有结构。 URI 具有标识您要操作的对象(或对象目录)的路径。您还可以将查询参数添加到 URI 以修改操作的行为。在这方面,ContentProvider 可以设计得更像 RESTful 服务。

有关简单电影数据库合同的具体(但不完整)示例,请参见下文。这基本上就是人们设计 RESTful web-service 的方式,除了一件事:就像在您的代码中一样,movie-id actor-id由调用者提供。真正的 RESTful 服务会自动创建和分配这些,然后 return 将它们分配给调用者。 ContentProvider 在插入新对象时只能 return long 个 ID。

插入一部新电影

在 /movies/

上插入

值:{"movie_id": <movie-id>, "title": <movie-title>, "year": ...}

插入一个新演员

在 /actors/

上插入

值:{"actor_id": <actor-id>, "name": <actor-name>, "gender": ...}

将现有演员添加到电影

在/movies/movie-id/actors/[=上插入 17=]

值:{"actor_id": <actor-id>}

将现有电影添加到演员:

在/actors/actor-id/movies/[=上插入 17=]

值:{"movie_id": <movie-id>}

可选:将新演员直接添加到电影中:

在/movies/movie-id/actors/[=上插入 17=]

值:{"actor_id": <actor-id>, "name": <actor-name>, "gender": ... }

如果不存在具有给定 id 的演员,此操作将创建新演员并 link 一步到电影中。如果具有此 ID 的参与者已经存在,则会抛出异常。 反过来也可以这样做,为演员添加一部新电影。

从电影中删除演员

在 /movies/movie-id/actors/[= 上删除 87=]actor-id

删除 /actors/actors-id/movies/movie-id

获取所有电影

在 /movies/

上查询

获取特定电影

在 /movies/movie-id

上查询

获取特定电影中的所有演员

在 /movies/movie-id/actors/[= 上查询 17=]

获取特定演员演过的所有电影

在/actors/actor-id/movies/[=上查询 17=]

可选的查询选择语句可用于过滤结果。要获取特定演员过去 10 年出演的电影,您可以将选择项 movies_year>=2005 添加到最后一个查询。

通过使用这样的合同,您不会公开联结 table,而是为您的数据库提供一个 REST-like 接口。

ContentProvider 的工作是将这些操作映射到数据库或任何其他 back-end。