前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Room 持久化库

Android Room 持久化库

作者头像
佛系编码
发布2018-05-22 11:21:41
3.9K0
发布2018-05-22 11:21:41
举报

这是一篇自己从Android开发文档中翻译来的关于Room 的文档。哪里不对,欢迎纠错

Room 持久化库

Room持久性库提供了SQLite的抽象层,以便在充分利用SQLite的同时允许流畅的数据库访问。 该库可帮助你在设备上创建应用程序的缓存数据,这样不管设备是否联网都能看到数据。

摘自 Room Persistence Library

使用 Room 在本地保存数据

原文地址 https://developer.android.com/training/data-storage/room/index.html

对于不重要的数据可以存储在本地,最常见的就是缓存相关的数据。这样,在设备没有网络的时候就可以浏览离线数据。当设备联网后,将用户改动的数据同步至服务端。

Room 有三个重要组件

  • Database
  • Entity
  • DAO

Database

包含数据库持有者,并作为与应用持久关联数据的底层连接的主要接入点。

使用@Database注解,并满足以下条件

  • 是抽象类,并且继承自RoomDatabase
  • 在注解中包含与数据库关联的实体列表。
  • 包含一个具有0个参数的抽象方法,并返回用@Dao注解的类。 在运行时,可以通过调用Room.databaseBuilder()或Room.inMemoryDatabaseBuilder()来获取数据库实例。

Entity

表示数据库中的表格

DAO

包含用户访问数据库的方法

这些组件以及组件与APP其他部分的关系 如图所示

Room结构图
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();

数据库实例最好是单例以节省内存开销

使用 Room 实体定义数据

原文地址 https://developer.android.com/training/data-storage/room/defining-data.html

我们定义的每一个实体,Room 都会对应的在数据库中创建一个表。 默认 Room 会为 每个字段在表中创建对应的字段;如果其中一些属性不想被创建在表中怎么办,那就是使用 @Ignore 注解此属性。完成实体的创建之后必须在 Database 引用。

@Entity
class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

类中的每个字段都必须让Room能够访问到。否则Room无法管理。 [^] 注意 :要遵循 JavaBean 规约;否则 管杀不管埋;[^]

定义主键

每个实体必须定义最少一个主键,就算类中只有一个字段,也要保证使用 @PrimaryKey; 如果想让Room自动分配ID,可以设置 autoGenerate 为true; 如果是联合主键,可以在@Entity中设置 primaryKeys 属性。

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

    @Ignore
    Bitmap picture;
}

默认Room会使用类名当作数据库表名,如果你想设置其他名字,可以设置 tableName 属性

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

[^]Sqlite中表名不区分大小写[^]

就像表名一样,字段的名字默认的也是类中属性的名字如果想设置其他名字,可使用 @ColumnInfoname属性

@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;
}

注解索引和唯一约束

使用 @Entityindices 来创建索引,并列出索引或者组合索引包含的列;

@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 属性设置 唯一约束。 下面代码 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;
}

定义对象间的关联关系

由于Sqlite 是关系型数据库,我们可以指定对象间的关系。大部分的ORM框架也都支持对象间相互引用。但是 Room 明确禁止这样做。至于为什么明确禁止,文章最后会说。原文链接:https://developer.android.com/training/data-storage/room/referencing-data.html#understand-no-object-references

虽然不能直接定义对象间引用,但是可以使用外键建立关系。

例如:有一个 Book 实体,可以使用 @ForeignKey 关联到 User 实体。下面代码演示使用

@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;
}

@ForeignKey 是非常强大的,我们可以定义对象间的级联操作。例如可以在注解中设置 onDelete = CASCADE,当删除用户的的时候就会把用户所关联的书都删掉了。

[^]SQLite将@Insert(onConflict = REPLACE)作为一组REMOVE和REPLACE操作处理,而不是单个UPDATE操作。这种替换冲突值的方法可能会影响外键约束。有关更多详细信息,请参阅ON_CONFLICT子句的SQLite文档。[^]

创建嵌套对象

Room 支持在数据实体中嵌套其他对象来组合相关字段。例如 User 中嵌套一个 Address 这个地址对象中有三个字段:街道,城市,邮编。在数据表中这个三个字段是在用户表中的,就像其他字段一样。 通过在 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;
}

表示User对象的表格包含具有以下名称的列:id,firstName,street,state,city和post_code。

[^] 嵌套字段可以嵌套其他字段[^] 如果数据实体中有多个 嵌套字段,可以通过设置属性 prefix 加前缀的方式保证字段名不重复。 如果在 User 中使用下面的代码,那么嵌套字段就会是 address_street,address_state,address_cityaddress_post_code

@Embedded(prefix = "address_")
public Address address;

使用 Room DAO 访问数据

原文地址:https://developer.android.com/training/data-storage/room/accessing-data.html

Room 使用数据对象和 DAO 访问数据库。 DAO 是 Room 的重要组件,他包含了操作数据的抽象方法; DAO可以是一个接口或者抽象类,如果是抽象类的话,它可以有一个构造函数,它将RoomDatabase作为其唯一参数。Room会在编译时创建实现。 DAO不能在主线程的时候操作数据,可能会阻塞UI,除非在构建的时候调用 allowMainThreadQueries()。如果是返回 LiveData或者 Flowable 的异步查询例外。

定义操作方法

这里只列出几个常用方法

Insert

当创建一个DAO方法并使用它的时候,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 只接受到一个参数,他会返回一个新插入行的 long类型的 rowid。如果参数是 一个数组和集合就会返回一个long类型的数组或集合。 关于 @Insert 的详细介绍查看文档 https://developer.android.com/reference/android/arch/persistence/room/Insert.html

Update

Room 会通过每个实体的主键进行查询,然后再进行修改。 返回值可以是一个 int 型的值,返回更新的行数。

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

Room 会数据实体的主键删除相应的数据。 返回值可以是一个 int 型的值,用来表示删除的行数。

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

查询信息

@Query 是 DAO 中主要使用的注解。它可以执行对数据库的读写操作。每一个 @Query 方法都会在编译时验证,如果出现问题也是在编译时出现问题不会在运行时出现问题。

Room 也会验证方法的返回值,如果返回对象中的字段名称和查询响应中的字段名字不匹配, Room 会通过以下方式给出提示

  • 如果只有一些字段名称不匹配,会发出警告
  • 如果没有字段名称匹配,会发出错误。
简单查询
@Dao
public interface MyDao {
    @Query("SELECT * FROM user")
    public User[] loadAllUsers();
}

这是一个非常简单的查询所有用户的查询。在编译时,Room会知道是查询用户表的所有列。如果查询包含语法错误或者数据库中不存在这个表。Room会在编译时报错并给出错误信息。

将参数传递给查询

大部分时候查询都是需要过滤参数的。比如要查询一些年龄比较大的用户。

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

在编译时,Room会将 :minAge 与方法参数匹配绑定。 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);
}
返回列的子集

很多时候只需要数据实体的中几个列。例如你可能只想显示用户的姓和名而不是全部的用户信息。只查询需要的列可以节省资源并且查询的更快。

Room 允许返回任何的Java对象。只要查询的结果列能够和Java对象映射上即可。所以我们可以创建一个只包含需要的列的类。

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 知道查询的值并知道怎么映射到对应的NameTuple字段中。所以 Room 会生成正确的代码。如果查询返回的列多了或者少了,Room会给出警告

这里也可以使用@Embedded注解

传递参数集合

有时候查询的参数数量是动态的,只有运行的时候才知道。例如只查询某些地区的用户。 当参数是一个集合的时候,Room 会在运行的时候自动扩展它。

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

在执行查询时,我们经常想让UI在数据更改时自动更新。要实现这一点,可以在查询方法使用 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使用查询中访问的表的列表来决定是否更新LiveData的实例。

使用 RxJava 进行响应查询

Room还可以从定义的查询中返回 RxJava2 的 Publisher 和 Flowable 对象。要使用此功能,需要将 Room 组中的 android.arch.persistence.room:rxjava2 组件添加到构建Gradle依赖项中,添加组件之后就可以返回 Rxjava2 中的对象

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

更多 Room 和 Rxjava2 的使用 看另一篇文章 https://medium.com/google-developers/room-rxjava-acb0cd4f3757

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

非常不推荐使用Cursor API,因为它不能保证行是否存在或行包含的值。只有当已经拥有需要游标并且无法轻松重构的代码时才使用此功能。

查询多个表

有些时候可能需要查询多个表中的数据来计算结果。Room运行我们写任何查询,当然也允许连接其他表。如果响应式可观察数据类型,例如 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);
}

也可以从这些查询中返回POJO。例如,可以编写一个查询来加载用户及其宠物的名称,如下所示:

@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;
   }
}

迁移 Room 数据库

原文 https://developer.android.com/training/data-storage/room/migrating-db-versions.html

在APP升级时可能需要更改数据库来策应新的功能。这个时候当然不希望数据库中的数据丢失。

Room 允许我们编写 Migration ,以此来迁移数据。每个迁移类制定一个开始版本和结束版本。

在运行时,Room会运行每个Migration类的migrate()方法,并使用正确的顺序将数据库迁移到更高版本。

如果不提供必要的Migration , 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 验证模式会确认迁移正确进行,如果 Room 发现错误,会抛出一个包含不匹配的异常。

测试迁移

数据迁移是很重要的,一旦迁移失败可能会发生Crash。为了保证程序的稳定性,一定要确认是否否迁移成功。Room 提供了一个测试工件来帮助我们测试,为保证测试工件的正确运行,必须开启导出模式。

导出模式

编译后,Room将数据库的模式信息导出到JSON文件中。要导出模式,在build.gradle文件中设置room.schemaLocation注解处理器属性,如下面的代码片段所示:

build.gradle

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

我们应该把导出的 json 文件加入到版本控制中,它记录了数据库的模式历史,它能让Room在测试时创建老版本的数据库。

为了测试迁移,增加 Room 的测试工件依赖,并设置数据库模式文件地址,如下所示:

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

测试包提供了一个MigrationTestHelper类,它可以读取这些模式文件。它实现了 JUnit4 的 TestRule 接口,它能够管理已经创建的数据库。

下面是一个简单的测试

@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.
    }
}

测试数据库

使用 Room 创建数据库时,验证数据库和用户数据的稳定性非常重要。

测试数据库有两种方法

  • 在Android 设备上
  • 在开发主机上(不推荐)

关于测试指定数据库升级的信息 上面已经说过了。

注意:在测试时,Room允许创建Dao的模拟实例。这样的话,如果不是测试数据库本身就不需要创建完整的数据库,这个功能是很好的,Dao不会泄露数据库的任何信息

在设备上测试

测试数据库实现的推荐方法是编写在Android设备上运行的JUnit测试,由于这些测试不需要创建活动,它们应该比UI测试更快执行。

在设置测试时,应该创建数据库的内存中版本,以使测试更加密封,如以下示例所示

@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));
    }
}

在电脑上测试

Room使用SQLite Support Library,它提供了与Android Framework类中的接口相匹配的接口。此支持允许您传递支持库的自定义实现以测试数据库查询。

注意:即使此设置允许您的测试运行速度非常快,也不建议这样做,因为设备上运行的SQLite版本以及用户的设备可能与主机上的版本不匹配

使用Room引用复杂数据

Room提供了原始和包装类型转换的功能,但是不允许实体间对象引用。这里会解释为什么不支持对象引用和怎么使用类型转换器。

使用类型转换器

有时候你想存储自定义的数据类型在数据库的单个列中。这就需要为自定义类型添加一个类型转换器,这个转换器会将自定类型转换为Room能够认识的原始类型。

例如,我想保存Date类型的实例,我可以编写下面的类型转换器来在数据库中存储等效的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对象持久化。

接下来,在AppDataBase类添加注解 @TypeConverters 这样AppDataBase中的Dao和实体就都能使用这个转换器了。

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);
}

还可以将@TypeConverters限制到不同的作用域,包括个体实体,DAO和DAO方法。关于 @TypeConverters更详细的介绍 请查看文档 https://developer.android.com/reference/android/arch/persistence/room/TypeConverters.html

理解Room不允许使用对象引用的原因

关键问题:Room不允许实体类之间的对象引用。相反,您必须明确您的应用需要的数据。

将数据库中的关系映射到相应的对象模型是常见的做法,并且在服务器端运行良好。即使程序在访问时加载字段,服务器仍然运行良好。

但是,在客户端,这种延迟加载不可行,因为它通常发生在UI线程上,并且在UI线程中查询磁盘上的信息会产生严重的性能问题。UI线程通常具有约16 ms的时间来计算和绘制活动的更新布局,因此即使查询只需要5 ms,仍然可能您的应用程序将耗尽时间来绘制框架,从而导致明显的视觉干扰。如果有单独的事务并行运行,或者设备正在运行其他磁盘密集型任务,则查询可能需要更多时间才能完成。但是,如果不使用延迟加载,则应用会获取比所需更多的数据,从而导致内存消耗问题。

对象关系映射通常将这个决定留给开发人员,以便他们可以为他们的应用程序的用例做最好的事情。开发人员通常决定在应用程序和用户界面之间共享模型。然而,这种解决方案并不能很好地扩展,因为随着UI的变化,共享模型会产生一些难以让开发人员预测和调试的问题。

例如,考虑加载一个Book对象列表的UI,每个书都有一个Author对象。最初可能会将查询设计为使用延迟加载,以便Book的实例使用getAuthor()方法返回作者。过了一段时间,你意识到你也需要在应用程序的用户界面中显示作者姓名。您可以轻松地添加方法调用,如以下代码片段所示:

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

但是,这个看起来无害的更改会导致在主线程上查询Author表。

如果提前查询作者信息,如果不再需要数据,则很难更改数据的加载方式。例如,如果您的应用程序的用户界面不再需要显示作者信息,则您的应用程序会有效地加载不再显示的数据,从而浪费宝贵的内存空间。如果作者类引用另一个表(如Books),则应用程序的效率会进一步降低。

要使用Room同时引用多个实体,需要创建一个包含每个实体的POJO,然后编写一个查询来加入相应的表。这种结构良好的模型与Room强大的查询验证功能相结合,可让您的应用在加载数据时消耗更少的资源,从而改善应用的性能和用户体验。

end

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-04-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Room 持久化库
  • 使用 Room 在本地保存数据
    • Database
      • Entity
        • DAO
        • 使用 Room 实体定义数据
          • 定义主键
            • 注解索引和唯一约束
              • 定义对象间的关联关系
                • 创建嵌套对象
                • 使用 Room DAO 访问数据
                  • 定义操作方法
                    • Insert
                    • Update
                    • Delete
                  • 查询信息
                    • 简单查询
                    • 将参数传递给查询
                    • 返回列的子集
                    • 传递参数集合
                    • 可观察的查询
                    • 使用 RxJava 进行响应查询
                    • 直接访问 Cursor
                    • 查询多个表
                • 迁移 Room 数据库
                  • 测试迁移
                    • 导出模式
                    • 测试数据库
                      • 在设备上测试
                        • 在电脑上测试
                        • 使用Room引用复杂数据
                          • 使用类型转换器
                            • 理解Room不允许使用对象引用的原因
                            相关产品与服务
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档