
Room是一个库,是Android 体系结构组件的一部分。 它有助于在应用程序中使用SQLiteDatabase
对象,从而减少标准代码量并在编译时检查SQL查询。
已经有一个使用SQLite存储数据的Android项目? 如果是这样,则可以将其迁移到Room。 让我们看一下如何通过7个简单步骤将现有项目重构并重构为使用Room。
TL; DR:更新gradle依赖关系,创建您的实体,DAO和数据库,用DAO方法调用替换SQLiteDatabase
调用,测试您创建或修改的所有内容,并删除未使用的类。 仅此而已!
在用于迁移的示例应用程序中,我们使用User
类型的对象。 我们使用产品口味来演示各种数据级别的实现:
SQLiteOpenHelper
使用SQLiteOpenHelper
和传统的SQLite接口。- room-用Room代替实施并提供迁移。
每个选项使用相同的用户界面层,这归功于MVP模式,可与UserRepository
类一起使用。
在sqlite变体中,您将看到很多重复的代码,它们在UsersDbHelper
和LocalUserDataSource
使用数据库。 使用ContentValues
构建查询,并逐列读取Cursor
对象返回的数据。 所有这些代码都会导致隐式错误的出现。 例如,您可以跳过向查询中添加列的操作,也可以不正确地从数据库中组装对象。
让我们看看Room如何改进我们的代码。 最初,我们只是从sqlite变体中复制类,然后逐步对其进行更改。
步骤1.更新gradle依赖项
可通过新的Google Maven存储库获得Room的依赖关系。 只需将其添加到主build.gradle
文件中的存储库列表中build.gradle
:
allprojects { repositories { google() jcenter() } }
在同一文件中定义Room库的版本。 虽然它是Alpha版本,但请继续关注开发人员页面上的版本更新:
ext { roomVersion = '2.1.0-alpha03' }
在您的app/build.gradle
添加Room的依赖项:
dependencies { implementation “android.arch.persistence.room:runtime:$rootProject.roomVersion” annotationProcessor “android.arch.persistence.room:compiler:$rootProject.roomVersion” androidTestImplementation “android.arch.persistence.room:testing:$rootProject.roomVersion” }
为了迁移到Room,我们需要增加数据库的版本,并保存用户数据,我们需要实现Migration类。 为了测试迁移 ,我们需要导出模式。 为此,将以下代码添加到app/build.gradle
:
android { defaultConfig { ... // used by Room, to test migrations javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] } } } // used by Room, to test migrations sourceSets { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } ...
步骤2.将模型类更新为实体
Room为每个标记为@Entity的类创建一个表。 类中的字段对应于表中的列。 因此,实体类通常是不包含任何逻辑的模型的小类。 我们的User
类代表数据库中数据的模型。 因此,让我们对其进行更新以告诉Room应该基于此类创建表:
- 用
@Entity
注释类,并使用tableName
属性设置表名称。 - 通过将
@PrimaryKey
批注添加到正确的字段来设置主键-在本例中,这是用户ID。 - 使用
@ColumnInfo(name = "column_name")
批注指定类字段的列名@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; } ... }
注意:为了顺利迁移,请密切注意原始实现中的表和列的名称,并确保在@Entity
和@ColumnInfo
正确设置它们。
步骤3.创建数据访问对象(DAO)
DAO负责定义数据库访问方法。 在我们的SQLite项目的最初实现中,所有数据库查询都在我们使用Cursor
对象的LocalUserDataSource
类中执行。 在Room中,我们不需要与游标相关的所有代码,我们可以使用UserDao
类中的注释来简单地定义我们的请求。
例如,当从数据库查询所有用户时,Room会完成所有“艰苦的工作”,我们只需要编写:
@Query(“SELECT * FROM Users”) List<User> getUsers();
步骤4.创建数据库
我们已经定义了Users
表及其对应的查询,但是还没有创建将Room的所有这些组件组合在一起的数据库。 为此,我们需要定义一个扩展RoomDatabase
的抽象类。 此类标记为@Database
,列出了数据库中包含的对象以及访问它们的DAO。 与原始值相比,数据库版本应增加1,因此在本例中为2。
@Database(entities = {User.class}, version = 2) @TypeConverters(DateConverter.class) public abstract class UsersDatabase extends RoomDatabase { private static UsersDatabase INSTANCE; public abstract UserDao userDao();
由于我们要保存用户数据,因此我们需要实现Migration
类,以告知Room从版本1迁移到版本2时应执行的操作。在本例中,由于数据库架构没有更改,因此我们只提供一个空的实现:
static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) {
通过定义数据库名称和迁移,在UsersDatabase
类中创建数据库对象:
database = Room.databaseBuilder(context.getApplicationContext(), UsersDatabase.class, "Sample.db") .addMigrations(MIGRATION_1_2) .build();
要了解有关如何实施数据库迁移以及它们如何在后台工作的更多信息,请参阅本文 。
步骤5.更新存储库以使用Room
我们创建了数据库,用户表和查询,因此现在该使用它们了。 此时,我们将更新LocalUserDataSource
类以使用UserDao
方法。 为此,我们首先更新构造函数:删除Context
并添加UserDao
。 当然,任何创建LocalUserDataSource
实例的类也必须更新。
接下来,我们将通过调用UserDao
方法来更新进行查询的LocalUserDataSource
方法。 例如,现在一个请求所有用户的方法如下所示:
public List<User> getUsers() { return mUserDao.getUsers(); }
现在是启动我们所做的工作的时候了。
Room的最佳功能之一是,如果您在主线程上执行数据库操作,您的应用程序将崩溃并显示以下错误消息:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
从主线程移出I / O操作的一种可靠方法是创建一个新的Runnable
,它将为每个数据库查询创建一个新线程。 由于我们已经在sqlite变体中使用了这种方法,因此无需进行任何更改。
步骤6.在设备上测试
我们创建了新类UserDao
和UsersDatabase
并更改了LocalUserDataSource
以使用Room数据库。 现在我们需要测试它们。
测试UserDao
要测试UserDao
,我们需要创建一个测试类AndroidJUnit4
。 Room的惊人功能是可以在内存中创建数据库。 这样就无需在每次测试后进行清洁。
@Before public void initDb() throws Exception { mDatabase = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabase.class) .build(); }
我们还需要确保在每次测试后关闭数据库连接。
@After public void closeDb() throws Exception { mDatabase.close(); }
例如,要测试用户的登录名,我们添加一个用户,然后检查是否可以从数据库中获取该用户。
@Test public void insertAndGetUser() {
测试LocalUserDataSource中UserDao的使用
确保LocalUserDataSource
仍能正常工作很容易,因为我们已经有描述此类行为的测试。 我们需要做的就是在内存中创建一个数据库,从中获取一个UserDao
对象,并将其用作LocalUserDataSource
构造函数的参数。
@Before public void initDb() throws Exception { mDatabase = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), UsersDatabase.class) .build(); mDataSource = new LocalUserDataSource(mDatabase.userDao()); }
同样,我们需要确保在每次测试后关闭数据库。
数据库迁移测试
在本文中,您可以阅读有关如何实施数据库迁移测试以及MigrationTestHelper
工作方式的更多信息 。
您还可以从更详细的迁移应用程序示例中查看代码。
步骤7.删除所有不必要的
删除现在由Room功能替换的所有未使用的类和代码行。 在我们的项目中,我们只需要删除UsersDbHelper
类,即可扩展SQLiteOpenHelper
类。
如果您有一个更大,更复杂的数据库,并且想要逐步切换到Room,那么我们推荐这篇文章 。
现在,标准的容易出错的代码数量减少了,在编译时检查了请求,并测试了所有内容。 通过7个简单的步骤,我们便能够将现有应用程序迁移到Room。 您可以在此处查看示例应用程序。