Room Persistence Library(官网文档翻译)

官方原文地址

Room持久化库

Room为SQLite提供一个抽象层,在充分利用SQLite的同时,允许流畅的数据库访问

注意:引入Room到你的android工程,参看 adding components to your project

应用处理大量的结构化数据能够从本地持久化数据获益很多,最通用的例子是缓存相关的数据碎片。那样,当设备不能访问网络的时候,用户仍然可以浏览内容。任何用户发起的内容改变在设备恢复网络的时候同步到服务器上。

核心框架对raw SQL内容提供嵌入支持。尽管这些APIs是很给力的,但是他们相当低级并且需要大量的时间和精力去使用:

  • raw SQL查询没有编译时验证。当你的数据图改变,你需要手动的更新受影响的SQL查询。这个过程是耗时的和容易出错的。
  • 你需要使用大量的样板代码在数据查询和java数据对象之间转换

Room为你处理这些问题。在Room中有三个主要组件。

  • Database(数据库): 你可以使用这个组件创建一个数据库holder。注解定义了一系列entities并且类的内容提供了一系列DAOs,它也是下层的主要连接 的访问点。
    注解的类应该是一个抽象的继承 RoomDatabase的类。在运行时,你能获得一个实例通过调用Room.databaseBuilder()或者 Room.inMemoryDatabaseBuilder()

  • Entity(实体):这个组件代表了一个持有数据行的类。对于每个entity,一个数据库表被创建用于持有items。你必须引用entity类通过Database类中的entities数组。每个entity字段被持久化到数据库中除非你注解它通过@Ignore.

注意:Entities能够有一个空的构造函数(如果dao类能够访问每个持久化的字段)或者一个参数带有匹配entity中的字段的类型和名称的构造函数,例如一个只接收其中一些字段的构造函数。

  • DAO(数据访问对象):这个组件代表了一个类或者接口作为DAO。DAOs 是Room中的主要组件,并且负责定义访问数据库的方法。被注解为@Database的类必须包含一个没有参数的抽象方法并且返回注解为@Dao的类。当在编译时生成代码,Room创建一个这个类的实现。

注意:使用DAO类访问数据库而不是query builders或者直接查询。你可以把数据库分成几个组件。还有,DAOs允许你轻松的模拟数据库访问当你测试你的应用的时候。

这些组件和rest app的关系,如图:

room 架构图

如下代码片段包含一个数据库配置的例子、一个entity,一个DAO:

User.java

@Entity
public class User {
    @PrimaryKey
    private int uid;

    @ColumnInfo(name = "first_name")
    private String firstName;

    @ColumnInfo(name = "last_name")
    private String lastName;

    // Getters and setters are ignored for brevity,
    // but they're required for Room to work.
}

UserDao.java

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
           + "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

AppDatabase.java

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

通过创建以上文件,你可以使用如下代码创建一个数据库实例:

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();

注意:你必须遵守单例模式当初始化一个AppDatabase对象,因为每个RoomDatabase实例是相当昂贵的,并且你几乎不需要访问多个实例。

Entities(实体)

当一个类被注解为@Entity并且引用到带有@Database 注解的entities属性,Room为这个数据库做的entity创建一个数据表。

默认情况下,Room为每个定义在entity中的字段创建一个列。如果一个entity的一些字段你不想持久化,你可以使用@Ignore注解它们,像如下展示的代码片段:

@Entity
class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

为了持久化一个字段,Room必须有它的入口。你可以使字段为public,或者你可以提供一个setter或者getter。如果你使用setter或者getter方法,记住在Room中他们遵守Java Beans的惯例。

Primary Key(主键)

每个entity必须至少定义一个field作为主键(primary key)。即使只有一个field,你也必须用@PrimaryKey注释这个field。如果你想让Room为entity设置自增ID,你可以设置@PrimaryKey的autoGenerate属性。

@Entity(tableName = "user")
public class User {
    @PrimaryKey(autoGenerate = true)
     private  Integer id;
     ...
}

如果你的entity有一个组合主键,你可以使用@Entity注解的primaryKeys属性,具体用法如下:

@Entity(primaryKeys = {"firstName", "lastName"})
class User {
    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

Room默认把类名作为数据库的表名。如果你想用其它的名称,使用@Entity注解的tableName属性,如下:

@Entity(tableName = "users")
class User {
    ...
}

注意:SQLite中的表名是大小写敏感的。

与tablename属性相似的是,Room使用字段名称作为列名称。如果你希望一个列有不同的名称,为字段增加@ColumnInfo注解,如下所示:

@Entity(tableName = "users")
class User {
    @PrimaryKey
    public int id;

    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;

    @Ignore
    Bitmap picture;
}

Indices and uniqueness(索引和唯一性)

为了提高查询的效率,你可能想为特定的字段建立索引。要为一个entity添加索引,在@Entity注解中添加indices属性,列出你想放在索引或者组合索引中的字段。下面的代码片段演示了这个注解的过程:

@Entity(indices = {@Index("name"),
        @Index(value = {"last_name", "address"})})
class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String address;

    @ColumnInfo(name = "last_name")
    public String lastName;

    @Ignore
    Bitmap picture;
}

有时候,某个字段或者几个字段必须是唯一的。你可以通过把@Index注解的unique属性设置为true来实现唯一性。下面的代码防止了一个表中的两行数据出现firstName和lastName字段的值相同的情况:

@Entity(indices = {@Index(value = {"first_name", "last_name"},
        unique = true)})
class User {
    @PrimaryKey
    public int id;

    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;

    @Ignore
    Bitmap picture;
}

Relationships(关系)

因为SQLite是关系数据库,你可以指定对象之间的关联。虽然大多数ORM库允许entity对象相互引用,但是Room明确禁止了这种行为。更多细节请参考 Addendum: No object references between entities.

虽然不可以使用直接的关联,Room仍然允许你定义entity之间的外键(Foreign Key)约束。

比如,假设有另外一个entity叫做Book,你可以使用@ForeignKey注解定义它和User entity之间的关联,如下:

@Entity(foreignKeys = @ForeignKey(entity = User.class,
                                  parentColumns = "id",
                                  childColumns = "user_id"))
class Book {
    @PrimaryKey
    public int bookId;

    public String title;

    @ColumnInfo(name = "user_id")
    public int userId;
}

外键非常强大,因为它允许你指定当被关联的entity更新时做什么操作。例如,通过在@ForeignKey注解中包含Delete = CASCADE, 你可以告诉SQLite,如果相应的User实例被删除,那么删除这个User下的所有book。

注意:SQLite处理@Insert(OnConflict=REPLACE) 作为一个REMOVEREPLACE操作而不是单独的UPDATE操作。这个替换冲突值的方法能够影响你的外键约束。更多细节,参看 SQLite documentation

Nested objects(内嵌对象)

有时,你希望entity或者POJOs作为一个整体在你数据库的逻辑当中,即使对象包含几个字段。在这种情况下,你可以使用@Embedded注解去代表一个你希望分解成一个表中的次级字段的对象。接着你就可以查询嵌入字段就像其他单独的字段那样。

例如,我们的user类能够包含一个代表了street,city,state,postCode的组合字段Address。为了分别的保存组合列,包括被@Embedded注解的user类中的Address字段,如下所示:

class Address {
    public String street;
    public String state;
    public String city;

    @ColumnInfo(name = "post_code")
    public int postCode;
}

@Entity
class User {
    @PrimaryKey
    public int id;

    public String firstName;

    @Embedded
    public Address address;
}

Table表示了一个包含如下名称列的User对象:id,firstName,street,state,city和post_code。

注意:嵌入字段也包括其他嵌入字段

如果一个字段有多个同一类型的嵌入字段,你能保持每个列是独一无二的通过设置prefix属性。Room然后将所提供的值添加到嵌入对象中每个列名的开头

Data Access Objects (DAOs)(数据访问对象)

Room中的主要组件是Dao类。DAOs抽象地以一种干净的方式去访问数据库。

Dao可以是接口,也可以是抽象类。如果它是一个抽象类,那么它可以有一个构造函数,它将一个RoomDatabase作为它唯一的参数。

注意:Room不允许在主线程中访问数据库除非你在建造器中调用allowMainThreadQueries(),因为它可能长时间的锁住UI。异步查询(返回LiveData或者RxJava流的查询)是从这个规则中豁免的因为它们异步的在后台线程中进行查询。

Methods for convenience(便捷方法)

这里有很多你可表示的查询惯例使用DAO类。这篇文档包括几个通用的例子:

Insert

当你创建一个DAO方法并且使用@Insert注解它,Room生成一个在单独事务中插入所有参数到数据库中的实现。
如下代码展示了几个查询实例:

@Dao
public interface MyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertUsers(User... users);

    @Insert
    public void insertBothUsers(User user1, User user2);

    @Insert
    public void insertUsersAndFriends(User user, List<User> friends);
}

如果@Insert方法接收只有一个参数,它可以返回一个插入item的新rowId 的long值,如果参数是一个集合的数组,它应该返回long[]或者List<Long>

更多细节,参看文档 @Insert
注解,和 SQLite documentation for rowid tables

Update

Update 是更新一系列entities集合、给定参数的惯例方法。它使用query来匹配每个entity的主键。如下代码说明如何定义这个方法:

@Dao
public interface MyDao {
    @Update
    public void updateUsers(User... users);
}

尽管通常不是必须的,你能够拥有这个方法返回int值指示数据库中更新的数量。

Delete

Delete是一个从数据库中删除一系列给定参数的entities的惯例方法。它使用主键找到要删除的entities。如下所示:

@Dao
public interface MyDao {
    @Delete
    public void deleteUsers(User... users);
}

尽管通常不是必须的,你能够拥有这个方法返回int值指示数据库中删除的数量。

Methods using @Query(使用@Query)

@Query 是用于DAO类的主要注解。它允许你在数据库上执行读写操作。每个@Query方法都会在编译时验证,因此如果查询语句有问题,那么编译时就会报错,而不是在运行时发生。

  • 如果仅仅部分成员名相符,则发出警告
  • 如果没有成员名相符,则发出错误
查询示例:
@Dao
public interface MyDao {
    @Query("SELECT * FROM user")
    public User[] loadAllUsers();
}

这是载入所有用户的非常简单的查询例子。在编译时,Room知道这是查询user表中的所有列。如果查询包含语法错误,或者如果用户表不存在,Room在你app编译时会报出合适的错误消息。

往查询中传入参数:

大多数时间,你需要传入参数到查询中去过滤操作,例如只展示比一个特定年龄大的用户,为了完成这个任务,在你的Room注解中使用方法参数,如下所示:

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge")
    public User[] loadAllUsersOlderThan(int minAge);
}

当编译时处理这个查询时,,Room将:minAgeminAge匹配在一起。Room使用参数名进行匹配,如果匹配不成功,会在编译时报错。

你也可以通过传入多个参数或者多次引用它们在一个查询当中,如下所示:

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
    public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

    @Query("SELECT * FROM user WHERE first_name LIKE :search "
           + "OR last_name LIKE :search")
    public List<User> findUserWithName(String search);
}

Returning subsets of columns(返回列中的子集)

大多数时候,我们只需要一个entity的部分字段。比如,你的界面也许只需显示user的first name 和 last name,而不是用户的每个详细信息。只获取UI需要的字段可以节省可观的资源,查询也更快。

只要结果的字段可以和返回的对象匹配,Room允许返回任何的Java对象。比如,你可以创建如下的POJO获取user的first name 和 last name:

public class NameTuple {
    @ColumnInfo(name="first_name")
    public String firstName;

    @ColumnInfo(name="last_name")
    public String lastName;
}

现在,你可以使用这个POJO在你的查询方法中:

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user")
    public List<NameTuple> loadFullName();
}

Room理解查询返回first_namelast_name的列值并且这些值被映射到NameTuple类的字段中。因此,Room能够生成合适的代码。如果查询返回太多columns,或者一个列不存在,Room将会报警。

注意:这些POJOs也使用@Embedded注解

Passing a collection of arguments(传递参数集合)

你的部分查询可能需要你传入可变数量的参数,确切数量的参数直到运行时才知道。例如,你可能想提取来自某个地区所有用户的信息。Room理解当一个参数代表一个集合并且自动的在运行时扩展它根据提供的参数数量。

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public List<NameTuple> loadUsersFromRegions(List<String> regions);
}

Observable queries(可观察查询)

你经常希望你的app'sUI自动更新当数据发生改变。为了实现这点,使用返回值类型为liveData在你的查询方法描述中。当数据库被更新,Room生成所有需要的代码去更新LiveData

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}

注意:在1.0版本,Room使用被访问的table列表在查询中决定是否更新数据对象。

RxJava

Room也能返回RxJava2 PublisherFlowable对象从你定义的查询当中。为了使用这个功能,添加android.arch.persistence.room:rxjava2 到你的build Gradle依赖。你能够返回Rxjava2定义的对象,如下所示:

@Dao
public interface MyDao {
    @Query("SELECT * from user where id = :id LIMIT 1")
    public Flowable<User> loadUserById(int id);
}

有关更多细节,请参见谷歌开发者Room and RxJava文章

Direct cursor access(直接游标访问)

如果你的应用逻辑直接访问返回的行,你可以返回一个Cursor对象从你的查询当中,如下所示:

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    public Cursor loadRawUsersOlderThan(int minAge);
}

注意:非常不建议使用Cursor API 因为它不能保证行是否存在或者行包含什么值。使用这个功能仅仅是因为你已经有期望返回一个cursor的代码并且你不能轻易的重构。

Querying multiple tables(查询多张表)

你的一些查询可能访问多个表去计算结果。Room允许你写任何查询,所以你也能连接表格。还有,如果答复是一个observable数据类型,例如Flowable或者LiveData,Room监视所有被查询中被引用的无效的表格。

如下代码段展示如何执行一个表格连接去联合当前正在借出的书和借的有书的人的信息。

@Dao
public interface MyDao {
    @Query("SELECT * FROM book "
           + "INNER JOIN loan ON loan.book_id = book.id "
           + "INNER JOIN user ON user.id = loan.user_id "
           + "WHERE user.name LIKE :userName")
   public List<Book> findBooksBorrowedByNameSync(String userName);
}

你也能返回POJOs从这些查询当中,例如,你可以写一个查询去装载user和他们的宠物名称,如下:

@Dao
public interface MyDao {
   @Query("SELECT user.name AS userName, pet.name AS petName "
          + "FROM user, pet "
          + "WHERE user.id = pet.user_id")
   public LiveData<List<UserPet>> loadUserAndPetNames();

   // You can also define this class in a separate file, as long as you add the
   // "public" access modifier.
   static class UserPet {
       public String userName;
       public String petName;
   }
}

Using type converters (使用类型转换)

Room为原始类型和可选的装箱类型提供嵌入支持。然而,有时你可能使用一个单独存入数据库的自定义数据类型。为了添加这种类型的支持,你可以提供一个把自定义类转化为一个Room能够持久化的已知类型的TypeConverter。

例如:如果我们想持久化日期的实例,我们可以写如下TypeConverter去存储相等的Unix时间戳在数据库中:

public class Converters {
    @TypeConverter
    public static Date fromTimestamp(Long value) {
        return value == null ? null : new Date(value);
    }

    @TypeConverter
    public static Long dateToTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }
}

之前的例子定义了两个函数,一个把Date对象转换为Long对象。另一个逆向转换,从Long到Date。因为Room已经知道了如何持久化Long对象,它能使用转换器持久化Date类型。

接着,你增加@TypeConverters注解到AppDatabase类为了Room能够使用你已经为每个entity定义的转换器和DAO
AppDatabase.java

AppDatabase.java

@Database(entities = {User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

使用这些转换器,你可以使用你自定义类型在其他查询中,就像你使用的原始类型,如下代码片段所示:

User.java

@Entity
public class User {
    ...
    private Date birthday;
}

UserDao.java

@Dao
public interface UserDao {
    ...
    @Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
    List<User> findUsersBornBetweenDates(Date from, Date to);
}

您还可以将@typeconverter限制在不同的范围内,包含单独的entities,DAOs,和DAO methods。更多细节,请参考@typeconverter
文档

Database migration(数据库迁移)

当你添加或改变你app的特性,你需要修改你的entity类去反映这些改变。当一个用户更新你应用到最近的版本,你不希望他们丢失已经存在的数据,特别是你无法从远程服务器恢复数据。

Room允许你使用Migration类保留用户数据以这种方式。每个Migration类在运行时指明一个开始版本和一个结束版本,Room执行每个Migration类的migrate()方法,使用正确的顺序去迁移数据库到一个最近版本。

注意:如果你不提供必需的migrations类,Room重建数据库,也就意味你将丢失数据库中的所有数据。

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
                + "`name` TEXT, PRIMARY KEY(`id`))");
    }
};

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE Book "
                + " ADD COLUMN pub_year INTEGER");
    }
};

注意:为了保持你的迁移逻辑与预期一致,使用完全查询而不是代表查询的引用常量。

当迁移过程结束,Room验证schema去保证迁移成功。如果Room发现问题,它将抛出不匹配异常。

Testing migrations(测试迁移)

迁移并不是一件简单的事情,如果不能正确编写将会造成应用崩溃。为了保证你应用的稳定性,你应该在提交前测试你的迁移类。Room提供一个测试Maven组件去协助测试过程。然而,为了让这个组件工作,你需要到处你的数据库schema。

Exporting schemasI(导出 schemas)

根据编译,Room导出你的数据库Schema到一个JSON文件中。为了导出schema,设置 注释处理器的属性room.schemaLocation在你的build.gradle文件中,如下所示:

build.gradle

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation":
                             "$projectDir/schemas".toString()]
            }
        }
    }
}

你应该存储导出的JSON文件-代表了你数据库schema的历史-在你的版本控制系统中,正如它允许创建老版本的数据库去测试。

为了测试这些migrations,添加 android.arch.persistence.room:testing Maven artifac从Room当中到你的测试依赖当中,并且把schema 位置当做一个asset文件添加,如下所示:

build.gradle

android {
    ...
    sourceSets {
        androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
    }
}

测试package提供一个 可以读取这些schema文件的MigrationTestHelper类。它也是Junit4 TestRule类,所以它能管理创建的数据库。

如下代码展示了一个测试migration的例子:

@RunWith(AndroidJUnit4.class)
public class MigrationTest {
    private static final String TEST_DB = "migration-test";

    @Rule
    public MigrationTestHelper helper;

    public MigrationTest() {
        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
                MigrationDb.class.getCanonicalName(),
                new FrameworkSQLiteOpenHelperFactory());
    }

    @Test
    public void migrate1To2() throws IOException {
        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);

        // db has schema version 1. insert some data using SQL queries.
        // You cannot use DAO classes because they expect the latest schema.
        db.execSQL(...);

        // Prepare for the next version.
        db.close();

        // Re-open the database with version 2 and provide
        // MIGRATION_1_2 as the migration process.
        db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);

        // MigrationTestHelper automatically verifies the schema changes,
        // but you need to validate that the data was migrated properly.
    }
}

Testing your database(测试你的数据库)

当运行你app的测试时,你不应该创建一个完全的数据库如果你不测试数据库本身。Room允许你轻松的模仿数据访问层在测试当中。这个过程是可能的因为你的DAOs不暴漏任何你数据库的细节。当测试你的应用,你应该创建模仿你的DAO类的假的实例。

这儿有两种方式去测试你的数据库:

  • 在你的开发主机上
  • 在一个Android设备上

Testing on your host machine(在你的主机上测试)

Room使用SQLite支持库,这个支持库提供匹配这些Android Framework类的接口并且允许你通过自定义支持库实现去测试你的数据库查询。

即使这个装置允许你的测试运行很快,它是不建议的因为用户设备的SQLite版本和可能与host主机不匹配。

Testing on an Android device(在Android设备上测试)

测试你的数据库推荐的方法实现是写一个单元测试在Android设备上。因为这些测试不需要创建一个activity,他讲bicentennialUI单元测试快。

当装置你的测试用例时,你应该创建一个数据库的内存版本好让你的测试更密闭,如下所示:

@RunWith(AndroidJUnit4.class)
public class SimpleEntityReadWriteTest {
    private UserDao mUserDao;
    private TestDatabase mDb;

    @Before
    public void createDb() {
        Context context = InstrumentationRegistry.getTargetContext();
        mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
        mUserDao = mDb.getUserDao();
    }

    @After
    public void closeDb() throws IOException {
        mDb.close();
    }

    @Test
    public void writeUserAndReadInList() throws Exception {
        User user = TestUtil.createUser(3);
        user.setName("george");
        mUserDao.insert(user);
        List<User> byName = mUserDao.findUsersByName("george");
        assertThat(byName.get(0), equalTo(user));
    }
}

更多关于测试数据库migrations的信息参看 Migration Testing

附加:没有实体键的对象引用

从数据库到对象间关系的映射是一个很常见的实践,并且在服务端运行良好,在它们被访问的时候进行高性能的惰性加载。

但是在客户端,惰性加载并不可行,这是因为很有可能发生在主线程,在主线程查询磁盘信息会导致很严重的性能问题。主线程有大概16ms来计算并绘制一个Activity的界面更新,因此甚至一个查询仅仅耗费5ms,你的app仍然会耗光绘制画面的时间,导致显著的Jank[1]问题。更糟的是,如果有个并发运行的数据库事务,或者如果设备正忙于处理其他磁盘相关的繁重工作,查询会花费更多的时间完成。如果你不使用惰性加载的方式,app会获取多余其所需要的数据,从而导致内存消耗的问题。

ORM通常将该问题交给开发者决定,使得他们可以根据自己的用例选择最佳的方式。不幸地是,开发者通常终止模型和UI之间的共享。当UI变更超时时,问题随之发生并且很难预感和解决。

举个例子,UI界面读取一组Book列表,每本书拥有一个Author对象。你可能开始会设计你的查询去使用惰性加载,从而Book实例使用getAuthor()方法查询数据库。过了一些时间,你意识到你需要在app的UI界面显示作者名。你可以添加以下方法:

authorNameTextView.setText(user.getAuthor().getName());

但是这种看似没有问题的代码会导致Author表在主线程被查询。

如果你急于查询作者信息,这会变得很难去改变数据是如何加载的,如果你不再需要这个数据的话,例如当你app的UI不再需要显示关于特定作者信息的时候。于是你的app必须继续加载不再显示的信息。这种方式更为糟糕,如果Author类引用了其他表,例如getBooks()方法。

由于这些原因,Room禁止实体间的对象引用。作为替换,你必须显式地请求你所需要的数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容