Room 是一个持久化的库,是 Android Architecture Components
的一部分。使用 Room 可以更容易的处理 app 中的 SQLiteDatabase 对象,减少模板代码的数量以及在编译时验证 SQL 查询语句。
下面是在 app
中使用 Room 的七个基本步骤:
-
更新项目下的
build.gradle
依赖allprojects { repositories { google () jcenter () } } 复制代码
在同文件(也可能是
versions.gradle
)下配置 roomVersionext { … roomVersion = '…' } 复制代码
在
app/build.gradle
中添加 Room 的依赖dependence { … implementation "android.arch.persistence.room:runtime:$rootProject.roomVersion" annotationProcessor "android.arch.persistence.room:compiler:$rootProject.roomVersion" androidTestimplementation "android.arch.persistence.room:testing:$rootProject.roomVersion" } 复制代码
对于从 SQLiteDatabaseHelper 迁移到 Room 的,需要实现一个 Migration 类来保持 user 数据需要升级数据库的版本号。需要调整架构来测试这个迁移。为此需要在
app/build.gradle
添加如下代码:android { defaultConfig { ... //用于 Room 的迁移测试 javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocations": "$projectDir/schemas".toString()] } } } //用于 Room 的迁移测试 sourceSets { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } } 复制代码
-
将 model 类更新为 entity
- 使用
@Entity
标注这个类,并将表名设置给tableName
属性; - 给主键对应的字段添加
@PrimaryKey
注解 - 给字段设置
@ColumnInfo(name = "column_name")
注解 - 如果有多个构造函数可用,添加
@Ignore
注解告诉 Room 哪个要用,哪个不要。
@Entity(tableName = "Users") public class User { @PrimaryKey @ColumnInfo(name = "userid") private String mId; @ColumnInfo(name = "username") private String mUserName; @ColumnInfo(name = "last_update") private Date mDate; @Ignore public User(String userName) { mId = UUID.randomUUID().toString(); mUserName = userName; mDate = new Date(System.currentTimeMillis()); } public User(String id, String userName, Date date) { this.mId = id; this.mUserName = userName; this.mDate = date; } ... } 复制代码
- 使用
-
创建数据访问对象
Data Access Objects
(DAOs)DAOs
负责定义访问数据库的方法。在项目的初始化 SQLite 实现中,所有的数据库查询都在LocalUserDataSource
中使用Cursor
对象完成。使用 Room,不需要关联代码所有的Cursor
并且在UserDao
类中使用注解简单定义了查询操作。例如,需要查询数据库中的所有用户时,我们只需要写如下代码,Room 就会处理所有这些繁重的任务。
@Query("SELECT * FROM Users") List<User> getUsers(); 复制代码
-
创建数据库
到目前为止,已经定义了
Users
表,和它相关的查询操作,但还没有创建将其他 Room 组件组合在一起的数据库。为了实现这一点,我们需要定义一个继承了RoomDatabase
的抽象类。这个类用@Database
注解修饰,列举了包含在数据库中的 entity 以及操作他们的 DAO。@Database(entities = {User.class}, version = 2) @TypeConverters(DataConverter.class) public abstract class UserDatabase extends RoomDatabase { private static UsersDatabase INSTANCE; public abstract UserDao userDao(); 复制代码
如果是从版本
1
迁移到2
,那么需要实现一个Migration
类告诉 Room 迁移时需要做什么。这里得数据库架构没有发生变化,所以只需要一个空实现即可。static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migration(SupportSQLiteDatabase database) { //数据库无修改,空实现 } }; 复制代码
在
UsersDatabase
类中创建数据库对象,定义数据库的名称和迁移database = Room.databaseBuilder(context.getApplicationContext(), UsersDatabse.class, "Sample.db") .addMigrations(MIGRATION_1_2) .build(); } 复制代码
更多关于数据库迁移是如何实现的以及底层是如何运行得,请参见下文的文章:
-
在这一步,将修改
LocalUserDatabaseSource
类来使用UserDao
的方法。为了做到这一点,首先要通过移除Context
,添加UserDao
来修改构造函数。当然,其他实例化LocalUserDatabaseSource
的类也需要修改。然后,调用UserDao
的方法更新LocalDatabaseSource
的查询数据库的方法。public List<User> getUsers() { return mUserDao.getUsers(); } 复制代码
运行代码!Room 的一个非常棒的特性就是,如果你的数据库操作在主线程执行,你的 app 会崩溃,会显示下面的异常信息:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. 复制代码
-
上机实测
现在测试创建的
UserDao
,UsersDatabase
和LocalUserDatabaseSource
测试UserDao
创建一个
AndroidJUnit4
测试类用于测试UserDao
。Room 的一个很棒的特性就是可以在内存中创建数据库,避免需要清除每次的测试用例。@Before public void initDb() throws Exception { mDatabse = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabse.class) .build(); } 复制代码
确保每次测试后关闭了数据库连接
@After public void closeDb() throws Exception { mDatabase.close(); } 复制代码
为了测试插入一个
User
,例如,可以先插入这个对象,再检查能否从数据库中得到这个对象。@Test public void insertAndGetUser() { // When inserting a new user in the data source mDatabase.userDao().insertUser(USER); //The user can be retrieved List<User> users = mDatabase.userDao().getUsers(); assertThat(users.size(), is(1)); User dbUser = users.get(0); assertEquals(dbUser.getId(), USER.getId()); assertEquals(dbUser.getUserName(), USER.getUserName()); } 复制代码
测试
LocalUserDataSource
中的UserDao
使用 我们需要做的是创建一个内存中的数据库,并从中获取一个UserDao
,并将其作为LocalUserDataSource
构造函数的参数。@Before public void initDb() throws Exception { mDatabase = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabase.class) .build(); mDataSource = new LocalUserDataSource(mDatabase.userDao()); } 复制代码
MigrationTsetHelper如何使用,请参见下面的博客文章:
-
删除
UserDbHelper
类