Room 是一个对象映射库,可利用最少的样板代码实现本地数据持久性。在编译时,它会根据数据架构验证每个查询,这样损坏的 SQL 查询会导致编译时错误而不是运行时失败。Room 可以抽象化处理原始 SQL 表格和查询的一些底层实现细节。它还允许您观察对数据库数据(包括集合和连接查询)的更改,并使用 LiveData 对象公开这类更改。它甚至明确定义了解决一些常见线程问题(如访问主线程上的存储空间)的执行约束。
描述 | 链接 |
---|---|
Room 简单介绍 | Room Persistence Library |
Room 使用指南 | Save data in a local database using Room |
Room 使用实例 | Android Room with a View - Java |
注意:如果你的应用已使用 SQLite 对象关系映射 (ORM) 等其他持久性解决方案,那么你无需将现有解决方案替换为 Room。不过,如果你正在编写新应用或重构现有应用,那么我们建议您使用 Room 保留应用数据。这样一来,你便可以利用该库的抽象和查询验证功能。
1. 概览
Room 提供了一个基于 SQLite 的抽象层,可以在充分利用 SQLite 的同时,流畅地访问数据库。有关更多信息,请参阅引用文档。
1.1 添加依赖
在添加 Room 依赖之前,你必须确保 Google Maven 仓库已正确添加到项目中。阅读 Adding Components to your Project 以获取更多信息。
Room 依赖包含 testing Room migrations 和 Room RxJava ,你可以在 App 或 Module 的 build.gradle
文件中根据需要添加依赖。
1 | dependencies { |
注意:对于基于 Kotlin 的应用程序,请确保使用 kapt 而不是 annotationProcessor。 你还应该添加 kotlin-kapt 插件。 有关使用 Kotlin 扩展的信息,请参阅 KTX 文档。
获取 Room 最新依赖版本号,请参阅 Room Releases,有关依赖项的更多信息,请参阅 Add Build Dependencies。
1.2 Room 的组件构成
Room 中有 3 个主要组件:
- Database:包含数据库持有者,并充当 APP 持久关系数据的基础连接的主要访问点。使用 @Database 注释的类应满足以下条件:
- 是一个继承自 RoomDatabase 的抽象类。
- 在注释中包括与数据库关联的实体列表。
- 包含一个具有 0 个参数的抽象方法,并返回用 @Dao 注释的类。
在运行时,你可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 来获取 Database 实例。
- Entity:表示数据库中的表。
- DAO:包含用于访问数据库的方法。
1.3 Room 的组件关系
应用程序通过 Room 数据库获取与该数据库关联的数据访问对象或 DAO。然后,应用程序使用 DAO 从数据库中获取 Entity,并将 Entity 的更改保存回数据库。最后,应用程序使用 Entity 来获取和设置与数据库中的表列对应的值。Room 组件间的关系图如下所示:
1.4 Room 数据库配置
以下代码片段是一个简单的数据库配置示例,该配置包含一个 Entity 和 一个 DAO:
1 |
|
1 |
|
1 |
|
创建上述文件后,你将使用以下代码获取已创建数据库的实例:
1 | AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build(); |
注意:如果您的应用程序在单进程中运行,则在实例化 AppDatabase 对象时应遵循单例设计模式。因为每个 RoomDatabase 实例都会消耗一定的性能,并且你也很少需要在单进程中访问多个实例。
如果您的应用程序在多进程中运行,请在数据库构建器调用中包含enableMultiInstanceInvalidation()
。这样,当您在每个进程中都有一个 AppDatabase 实例时,您可以在一个进程中禁止数据库文件的共享,并且会将共享失效自动传播到其他进程中的 AppDatabase 实例。
要获得 Room 的实际操作体验,请访问 Android Room with a View 和 Android Persistence。要浏览 Room 代码示例,请参阅 Android Architecture Components。
2. 使用实体定义数据
使用 Room 持久性库时,可以将相关字段集定义为 Entity。对于每个 Entity,都会在与其关联的 Database 对象中创建一个表来保存条目。你必须通过 Database 类中的 entities 数组引用这个 Entity 类。
下面的代码片段展示了如何定义 Entity:
1 |
|
要持久化一个字段(Field),Room 必须有权访问它。可以将字段设置为 public,也可以为其提供 getter 和 setter 方法。在提供 getter 和 setter 方法时,需要遵守 Room 中的 JavaBeans 协议。
注意:Entity 可以有一个空构造函数(如果相应的 DAO 类可以访问每个持久化字段),或一个参数类型和名称与 Entity 中字段匹配的构造函数。Room 也可以使用完整或部分构造函数,例如只接收某些字段的构造函数。
2.1 设置表名
默认情况下,Room 使用类名作为数据库表名。如果希望表(Table)具有不同的名称,可以通过注解 @Entity 的 tableName 属性来设置表名,警告:SQLite 中的表名称不区分大小写。如以下代码段所示:
1 |
|
2.2 设置列名
与 tableName 属性类似,Room 使用字段名称作为数据库中的列名称。如果希望列(Column)具有不同的名称,请将注解 @ColumnInfo 添加到字段中,如以下代码段所示:
1 |
|
2.3 设置主键
每个 Entity 必须至少定义一个字段作为主键。即使只有一个字段,你仍然需要使用注解 @PrimaryKey 来注释该字段。此外,如果你希望 Room 为实体自动分配 ID,你可以设置 @PrimaryKey 的 autoGenerate 属性。如果实体具有复合主键,则可以使用注解 @Entity 的 primaryKeys 属性,如以下代码段所示:
1 |
|
2.4 忽略字段
默认情况下,Room 会为 Entity 中定义的每个字段创建一列。如果 Entity 具有你不想保留的字段,则可以使用 @Ignore 对其进行注释,如以下代码段所示:
1 |
|
如果 Entity 从父 Entity 继承字段,则通常更容易使用注解 @Entity 的 ignoredColumns 属性:
1 |
|
2.5 全文搜索
Room 支持多种类型的注解,使你在数据库表中搜索详细信息时更容易。同时,请在应用程序的 minSdkVersion 大于等于 16 时使用全文搜索。
如果你的应用程序需要通过全文搜索(FTS)快速访问数据库信息,那么请让使用了 FTS3 或 FTS4 SQLite 扩展模块的虚拟表来支持你的实体。要使用此功能,请将 Room 依赖升级到 2.1.0 或更高版本,并向给定的实体添加 @Fts3 或 @Fts4 注解,如以下代码段所示:
1 | // Use `@Fts3` only if your app has strict disk space requirements or if you |
注意:启用 FTS 的表始终使用一个类型为 INTEGER 且列名为“rowid”的主键。如果由 FTS 表支持的实体定义主键,则必须使用该类型和列名。
如果表支持多种语言的内容,则使用 languageId
选项指定存储每行语言信息的列:
1 |
|
Room 还提供了其他的选项,这些选项用于定义 FTS 支持的实体,包括结果排序,标记器类型和作为外部内容管理的表。有关这些选项的更多详细信息,请参阅 FtsOptions 参考。
2.6 设置索引
如果你的应用程序使用的 SDK 版本不允许使用 FTS3 或 FTS4 表支持的实体,你还可以索引数据库中的某些列以加快查询速度。要向实体添加索引,需要在注解 @Entity 中设置 indices 属性,并列出要包含在索引或复合索引中的列的名称。设置过程参考以下代码片段:
1 |
|
有时,数据库中的某些字段或字段组必须是唯一的。你可以通过设置注解 @Index 的 unique 属性为 true 来保证唯一性。以下代码示例会禁止一张表的两行在列 firstName 和 lastName 上包含相同的一组值:
1 |
|
2.7 包括基于 AutoValue 的对象
注意:此功能仅适用于基于 Java 的实体。要在基于 Kotlin 的实体中实现相同的功能,最好使用数据类。
在 Room 2.1.0 或更高的版本中,你可以使用基于 Java 的不可变值类(使用 @AutoValue
进行注释)作为应用程序数据库中的实体。如果实体的两个实例的列包含相同的值,则此支持特别有用。
使用带 @AutoValue
注释的类作为实体时,可以使用 @PrimaryKey
,@ColumnInfo
,@Embedded
和 @Relation
注释类的抽象方法。但是,在使用这些注释时,必须每次都包含 @CopyAnnotations
注释,以便 Room 可以正确解释方法的自动生成实现。
以下代码段显示了一个使用 @AutoValue
注释的类,Room 将其识别为实体:
1 |
|
2.8 外键约束
由于 SQLite 是关系型数据库,因此你可以指定对象之间的关系。尽管大多数对象关系映射库允许实体对象相互引用,但 Room 明确禁止这样做。要了解禁止的原因,请参阅 Understand why Room doesn’t allow object references。
即使你不能使用直接关系,Room 仍允许你在实体之间定义外键约束。
例如,如果有另一个名为 Book 的实体,你可以使用注解 @ForeignKey 定义其与 User 实体的关系,如以下代码段所示:
1 |
|
外键非常强大,因为它们允许你指定更新引用实体时所触发的操作。例如,如果通过在 @ForeignKey 批注中包含 onDelete = CASCADE 来删除相应的 User 实例,则可以告诉 SQLite 删除用户的所有书籍。
注意:SQLite 将 @Insert(onConflict = REPLACE) 作为一组 REMOVE 和 REPLACE 操作处理,而不是单个 UPDATE 操作。这种替换冲突值的方法可能会影响你的外键约束。有关更多详细信息,请参阅 ON_CONFLICT 子句的 SQLite 文档。
2.9 创建嵌套对象
有时,你希望将实体或简单的 Java 对象(POJO)表达为数据库逻辑中的一个整体,即使该对象包含多个字段。在这些情况下,你可以使用 @Embedded 注释来表示要分解到表中子字段的对象。然后,你可以像查询其他单个列一样查询嵌入字段。
例如,我们的 User
类可以包含 Address
类型的字段,它表示名为 street
,city
,state
和 postCode
的字段的组合。要将组合列分别存储在表中,请在 User
类中包含使用 @Embedded 注释的 Address
字段,如以下代码段所示:
1 | public class Address { |
然后,与 User
对象对应的表包含以下名称的列:id
,firstName
,street
,state
,city
和 post_code
。
注意:嵌入字段还可以包含其他嵌入字段。
如果实体具有多个相同类型的嵌入字段,则可以通过设置 prefix 属性使每个列保持唯一。然后,Room 会将提供的值添加到嵌入对象中每个列名称的开头。
3. 在数据库中创建视图
Room 持久性库在 2.1.0 及更高的版本中,提供对 SQLite 数据库视图的支持,允许你将查询封装到类中。 Room 将这些查询支持的类称为视图,并且在 DAO 中使用时它们的行为与简单数据对象相同。
注意:与实体一样,你可以针对视图运行 SELECT 语句。但是,你无法对视图运行 INSERT,UPDATE 或 DELETE 语句。
3.1 创建视图
要创建视图,需要将注解 @DatabaseView 添加到类上。将注释的值设置为类应表示的查询。
以下代码段提供了一个视图示例:
1 |
|
3.2 将视图与数据库关联
要将此视图作为应用程序数据库的一部分,请在应用程序的 @Database
注释中包含 views 属性:
1 |
|
4. 使用 DAO 访问数据
要使用 Room 持久性库访问 APP 的数据,需要使用数据访问对象(data access objects,简称 DAOs)。这组 Dao 对象构成了 Room 的主要组件,因为每个 DAO 都包含提供对应用程序数据库的抽象访问的方法。
通过使用 DAO 类而不是查询构造器或直接查询来访问数据库,你可以分离数据库体系结构的不同组件。此外,DAO 允许你在测试应用程序时轻松模拟数据库访问。
注意:在将 DAO 类添加到应用程序之前,请将 Architecture Components 工件添加到应用程序的 build.gradle 文件中。
DAO 可以是接口,也可以是抽象类。如果它是一个抽象类,它可以选择有一个构造函数,它将 RoomDatabase 作为唯一参数。Room 会在编译时创建每个 DAO 实现。
注意:默认情况下,Room 不支持主线程上的数据库访问,除非你在构造器上调用了 allowMainThreadQueries(),因为它可能会长时间锁定 UI。异步查询(返回 LiveData 或 Flowable 实例的查询)则不受此规则的约束,因为它们在需要时会在后台线程上异步运行查询。
4.1 插入(Insert)
当你创建 DAO 方法并使用 @Insert 注释它时,Room 会生成一个实现,该实现会在一个事务中将所有参数插入到数据库中。示例代码如下:
1 |
|
当使用 @Insert 注解的方法只有一个参数时,可以返回一个 long
类型的值,表示插入项的 rowId
。如果参数是一个数组或集合,则返回 long[]
或 List<Long>
类型的值。
有关更多详细信息,请参阅 @Insert 的相关文档,以及 SQLite documentation for rowid tables。
4.2 更新(Update)
Update 方法修改数据库中的一组实体(以参数的形式传入)。使用每个实体的主键进行匹配查询。示例代码如下:
1 |
|
虽然通常没有必要,但你可以让此方法返回一个 int
值,表示数据库中更新的行数。
4.3 删除(Delete)
Delete 方法从数据库中删除一组实体(以参数的形式传入)。它使用主键来查找要删除的实体。示例代码如下:
1 |
|
虽然通常没有必要,但你可以让此方法返回一个 int
值,表示从数据库中删除的行数。
4.4 查询(Query)
@Query 是 DAO 类中使用的主要注解。它允许你对数据库执行读/写操作。每个 @Query 方法都会在编译时进行验证,因此如果查询出现问题,则会发生编译错误而不是运行时失败。
Room 还验证查询的返回值,如果返回的对象中的字段名称与查询响应中的相应列名称不匹配,Room 将通过以下两种方式之一提醒你:
- 如果只有一些字段名称匹配,它会发出警告。
- 如果没有字段名称匹配,则会出错。
4.4.1 简单查询
1 |
|
这是一个加载所有用户的简单查询。在编译时,Room 知道它正在查询用户表中的所有列。如果查询包含语法错误,或者数据库中不存在用户表,Room 会在应用程序编译时显示相应的错误信息。
4.4.2 带参数的查询
大多数情况下,你需要将参数传递给查询以执行过滤操作,例如仅显示超过特定年龄的用户。要完成此任务,需要在 Room 注释中使用方法参数,如以下代码段所示:
1 |
|
在编译阶段,Room 会将绑定参数 :minAge
与方法参数 minAge
进行匹配。Room 使用参数名称执行匹配。如果存在不匹配,则应用编译时会发生错误。
你还可以在查询中传递多个参数或多次引用它们,如以下代码段所示:
1 |
|
4.4.3 返回列的子集
大多数情况下,你只需要获得实体的几个字段。例如,你的 UI 可能只显示用户的姓氏和名字,而不是每个用户的详细信息。只提取应用程序 UI 中显示的列,不仅可以节省宝贵的资源,还能更快的完成查询。
只要可以将结果列的集合映射到返回的对象,Room 允许你从查询中返回任何基于 Java 的对象。例如,你可以创建以下的普通 Java 对象(POJO)来获取用户的姓氏和名字:
1 | public class NameTuple { |
现在,你可以在查询方法中使用此 POJO:
1 |
|
Room 知道查询应该返回 first_name
和 last_name
列的值,并且这些值可以映射到 NameTuple
类的字段中。因此,Room 可以生成正确的代码。如果查询返回的列太多,或者返回 NameTuple
类中不存在的列,则 Room 会发出警告。
注意:这些 POJO 也可以使用 @Embedded 批注。
4.4.4 传递一组参数
有些查询可能需要传入可变数量的参数,直到运行时才知道参数的确切数量。例如,你可能希望从区域子集中检索有关所有用户的信息。Room 知道参数在什么时候表示集合,并根据提供的参数数量在运行时自动扩展它。
1 |
|
4.4.5 可观察的查询
执行查询时,你通常希望应用程序的 UI 在数据更改时自动更新。为此,你需要在查询方法描述中使用 LiveData 类型的返回值。Room 会生成所有必要的代码,以便在更新数据库时更新 LiveData。
1 |
|
注意:从 1.0 版开始,Room 使用查询中访问的表的列表来决定是否更新 LiveData 的实例。
4.4.6 RxJava 的响应式查询
Room 为 RxJava2 类型的返回值提供以下支持:
@Query
方法:Room 支持Publisher
,Flowable
和Observable
类型的返回值。@Insert
,@Update
,和@Delete
方法:Room 2.1.0 及更高的版本支持Completable
,Single<T>
和Maybe<T>
类型的返回值。
要使用此功能,需要在应用程序的 build.gradle
文件中包含最新版本的 rxjava2 引用:
1 | dependencies { |
下面的代码片段展示了如何使用这些返回类型的几个示例:
1 |
|
有关更多详细信息,请参阅 Google Developers 的 Room 和 RxJava 文章。
4.4.7 直接 Cursor 访问
如果你的应用程序逻辑需要直接访问返回行,则可以从查询中返回 Cursor 对象,如以下代码段所示:
1 |
|
警告:使用 Cursor API 是非常不鼓励的,因为它不能保证行是否存在或行包含什么值。只有当你已经拥有需要光标的代码且无法轻松重构时才使用此功能。
4.4.8 多表查询
有些查询可能需要访问多个表来计算结果。Room 允许你编写任何查询,因此你也可以连接表。此外,如果响应是可观察的数据类型,例如 Flowable 或 LiveData,则 Room 将监视查询中引用的所有表,以确定是否无效。
下面的代码片段展示了如何执行表连接,以合并包含借书用户的表和包含当前借阅书籍数据的表之间的信息:
1 |
|
你还可以从这些查询中返回 POJO。例如,你可以编写一个加载用户及其宠物名称的查询,如下所示:
1 |
|
5. 迁移您的数据库
在应用程序中添加和更改功能时,需要修改实体类来反映这些更改。当用户更新到最新版本的应用程序时,你不希望他们丢失所有现有数据,尤其是在你无法从远程服务器恢复数据的情况下。
Room persistence library 允许你通过编写 Migration 类的方式保留用户数据。每个 Migration 类都会指定一个 startVersion
和 endVersion
。在运行时,Room 会运行每个 Migration 类的 migrate()
方法,并使用正确的顺序将数据库迁移到更高版本。
1 | static final Migration MIGRATION_1_2 = new Migration(1, 2) { |
警告:要保证迁移逻辑按预期运行,请使用完整的查询,而不是引用表示查询的常量。
迁移过程完成后,Room 会验证 schema 以确保正确进行迁移。如果 Room 发现问题,则会抛出包含不匹配信息的异常。
5.1 测试迁移
迁移并不容易编写,如果不能正确地编写迁移逻辑,可能会在应用程序中导致崩溃循环。为了保持应用程序的稳定性,应该事先测试迁移。Room 提供了一个协助测试的 Maven 组件。然而,要使此组件生效,你需要导出数据库的 schema。
5.2 导出 schema
编译后,Room 会将数据库的 schema 信息导出到 JSON 文件中。要导出 schema,请在 build.gradle
文件中设置 room.schemaLocation
注释处理器属性,如以下代码段所示:
1 | android { |
你应该在版本控制系统中保存导出的 JSON 文件(表示数据库的 schema 历史记录),因为它允许 Room 创建数据库的旧版本以进行测试。
要测试这些迁移,需要将 Maven 组件 android.arch.persistence.room:testing 添加到测试依赖项中,并将 schema 位置添加为 asset 文件夹,如以下代码段所示:
1 | android { |
测试包提供了 MigrationTestHelper 类,可以读取这些 schema 文件。它还实现了 JUnit4 TestRule 接口,因此它可以管理创建的数据库。以下代码片段展示了迁移测试:
1 |
|
5.3 优雅地处理丢失的迁移路径
更新数据库的 schema 后,某些设备上的数据库可能仍然使用较旧的 schema 版本。如果 Room 找不到该设备的数据库从旧版本升级到当前版本的迁移规则,则会抛出 IllegalStateException 异常。
为了防止程序在遇到这种情况时奔溃,请在创建数据库时调用 fallbackToDestructiveMigration()
方法:
1 | Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name") |
通过在应用程序的数据库构建逻辑中包含此方法,你可以告诉 Room 在缺少 schema 版本之间的迁移路径的情况下破坏性地重新创建应用程序的数据库表。
警告:通过在应用程序的数据库构造器中配置此选项,Room 会在缺少迁移路径时永久删除数据库表中的所有数据。
破坏性的重建回退逻辑包括几个附加选项:
- 如果在 schema 历史的特定版本中发生错误,而你又无法使用迁移路径解决这些错误,那么请使用
fallbackToDestructiveMigrationFrom()
。此方法表示只有在数据库尝试从其中一个有问题的版本迁移的情况下,你才希望 Room 使用回退逻辑。 - 要仅在尝试 schema 降级时执行破坏性重建,请改用
fallbackToDestructiveMigrationOnDowngrade()
。
6. 测试您的数据库
在使用 Room 持久性库创建数据库时,验证应用的数据库和用户数据的稳定性非常重要。
有两种方法可以测试你的数据库:
- 在 Android 设备上。
- 在你的开发机器上(不推荐)。
更多关于数据库迁移测试的信息,请参阅测试迁移。
注意:在为你的应用程序运行测试时,Room 允许你创建 DAO 类的模拟实例。这样,如果不测试数据库本身,就不需要创建完整的数据库。这个功能是可行的,因为 DAO 不会泄漏数据库的任何细节。
6.1 在Android设备上测试
测试数据库实现的推荐方法是编写一个在 Android 设备上运行的 JUnit 测试。因为这些测试不需要创建 Activity,所以它们的执行速度应该比 UI 测试快。
在设置测试时,你应该创建数据库的内存版本,以使进行密集测试,如以下示例所示:
1 |
|
6.2 在主机上测试
Room 使用 SQLite 支持库,它提供的接口与 Android Framework 类中的接口相匹配。此支持允许你传递支持库的自定义实现以测试数据库查询。
注意:尽管这种设置允许你非常快速地运行测试,但不建议这样做,因为在你的设备和用户设备上运行的 SQLite 版本可能与主机上的版本不匹配。
7. 引用复杂数据
Room 提供了原始类型和包装类型之间进行转换的功能,但不允许实体之间的对象引用。本节介绍了如何使用类型转换器以及 Room 不支持对象引用的原因。
7.1 使用类型转换器
有时,你希望将自定义数据类型的值存储在数据库的单个列中。为了支持自定义类型,需要提供一个 TypeConverter,它可以将自定义类型转换为 Room 能够持久化的已知类型。
例如,如果我们想要保存 Date 的实例,我们可以编写以下 TypeConverter 来在数据库中存储等效的 Unix 时间戳:
1 | public class Converters { |
前面的示例定义了 2 个函数,一个将 Date 对象转换为 Long 对象,另一个将 Long 对象转换为 Date 对象。由于 Room 已经知道如何持久化 Long 对象,因此它可以使用此转换器来保存 Date 类型的值。
接下来,将 @TypeConverters 注释添加到 AppDatabase
类,这样 Room 就可以使用你为该 AppDatabase
中的每个 Entity 和 DAO 定义的转换器:
1 |
|
使用这些转换器后,在查询中可以像使用基本类型一样使用自定义类型。如以下代码段所示:
1 |
|
1 |
|
你还可以将 @TypeConverters 的作用范围限制在单个实体、DAO 和 DAO 方法内。有关详细信息,请参阅 @TypeConverters 的参考文档。
7.2 了解 Room 为何不允许对象引用
关键点:Room 不允许 Entity 类之间的对象引用。相反,你必须显式地请求 APP 所需的数据。
将数据库与相应对象模型建立映射关系是一种常见的做法,并且在服务端非常有效。即使程序在访问字段时去加载字段,服务器也仍然可以正常运行。
但是,在客户端,这种类型的延迟加载是不可行的,因为它通常发生在 UI 线程上,并且在 UI 线程中查询磁盘上的信息会产生严重的性能问题。UI 线程通常有大约 16ms 的时间来计算和绘制 Activity 的更新布局,即使一个查询只需要 5ms,APP 可能仍然没有足够的时间来绘制帧,从而导致明显的视觉延迟。如果有一个单独的事务并行运行,或者设备正在运行其他磁盘密集型任务,那么查询操作可能花费更多时间。但是,如果不使用延迟加载,则 APP 会获取超出其需要的数据,从而产生内存消耗问题。
对象关系映射通常由开发人员决定,因为他们知道如何决定才会对 APP 开发最有利。开发人员通常选择在 APP 和 UI 之间共享模型。然而,这种方案的扩展性很差,因为当 UI 发生变化时,这种共享模型将出现难以预料和调试的问题。
例如,考虑一个加载 Book
对象列表的 UI,每个 Book
都持有一个 Author
对象。你最初可能会使用延迟加载进行查询,以便让 Book
实例检索 Author
。这是 Author
字段的第一次检索查询数据库。一段时间后,你意识到你还需要在 APP 的 UI 中显示作者姓名。你可以轻松地访问 Author
的 name
属性,如以下代码段所示:
1 | authorNameTextView.setText(book.getAuthor().getName()); |
然而,这种看似无害的更改会导致在主线程上查询 Author
表。
如果提前查询作者信息,则在不再需要该数据时,很难更改数据的加载方式。例如,如果 APP 的 UI 不再需要显示 Author
信息,但 APP 仍然会有效地加载不显示的数据,从而浪费宝贵的内存空间。如果 Author
类引用其他表(如 Books),则 APP 的效率会进一步降低。
要使 Room 同时引用多个 Entity ,你需要创建一个包含每个 Entity 的 POJO,然后编写一个连接相应表的查询。这种结构良好的模型与 Room 强大的查询验证功能相结合,可让你的应用在加载数据时消耗更少的资源,从而提高应用的性能和用户体验。