discuz完整迁移
In this article let’s examine a few situations you might face while you are developing your app using ROOM database. Sometimes there is a need to change our database schema by adding fields or entities.
在本文中,我们将研究在使用ROOM数据库开发应用程序时可能遇到的几种情况。 有时需要通过添加字段或实体来更改我们的数据库架构。
First of all, let’s assume that we have created an AppDatabase
with a data access object class UserDao
and a User
entity.
首先,假设我们已经创建了一个AppDatabase
其中包含一个数据访问对象类UserDao
和一个User
实体。
@Database(entities = arrayOf(User::class), version = 0)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}@Entity
data class User(@PrimaryKey val id: Int)
Database version unchanged
数据库版本不变
Let’s say that we want to add a field name
to the User
entity.
假设我们要向User
实体添加一个字段name
。
@Entity
data class User(@PrimaryKey val id: Int, val name: String)
If you leave unchanged the schema version but you modify the User
entity, when you will try to access your database, Room will throw an exception.
如果您保留架构版本不变但修改了User
实体,则当您尝试访问数据库时,Room将引发异常。
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
Database version increased but no migration code provided
数据库版本增加,但未提供迁移代码
Now let’s increase the database version to 1 as suggested by the exception message.
现在,根据异常消息的建议,将数据库版本增加到1。
@Database(entities = arrayOf(User::class), version = 1)
In that case Room doesn’t know how to migrate database from version 0 to version 1 and throws below exception when you try to open it:
在这种情况下,Room不知道如何将数据库从版本0迁移到版本1,并且在尝试打开数据库时抛出以下异常:
java.lang.IllegalStateException: A migration from 0 to 1 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration …) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
In the exception message Room suggests us two solutions:
在例外消息中,Room为我们提供了两种解决方案:
- provide some migration code from version 0 to 1 提供一些从版本0到版本1的迁移代码
- allow database to be deleted and re-created 允许删除并重新创建数据库
Database version increased, fallback to destructive migration
数据库版本增加,回退到破坏性迁移
The second option is the easiest one, since we don’t need to provide any migration code, we only need to add fallbackToDestructiveMigration
in Room builder.
第二个选项是最简单的一种,因为我们不需要提供任何迁移代码,因此只需要在“房间”构建器中添加fallbackToDestructiveMigration
。
val appContext = context.applicationContextval db = Room.databaseBuilder(appContext, AppDatabase::class.java) .fallbackToDestructiveMigration()
.build()
In that case the doc states it allows Room to destructively recreate database tables. In other words database will be deleted and re-created and all the data stored in it will be lost.
在那种情况下,文档指出允许Room破坏性地重新创建数据库表。 换句话说,数据库将被删除并重新创建,并且其中存储的所有数据都将丢失。
Database version increased, migration code provided
数据库版本增加,提供了迁移代码
If you don’t want to erase the database and loose your data, which is generally the goal you try to achieve, you need to provide a migration code.
如果您不想删除数据库并丢失数据(通常这是您试图实现的目标),则需要提供迁移代码。
Room library supports incremental migrations with Migration
classes. Each Migration
class defines a migration path between a startVersion
and endVersion
by overriding migrate()
method. At run-time, when an app update requires a database version upgrade, Room runs the migrate()
method from one or more Migration
class to migrate the database to the latest version.
Room库通过Migration
类支持增量迁移。 每个Migration
类endVersion
通过覆盖endVersion
migrate()
方法来定义startVersion
和endVersion
之间的迁移路径。 在运行时,当应用程序更新需要数据库版本升级时,Room从一个或多个Migration
类运行migrate()
方法,以将数据库迁移到最新版本。
Even if you are not familiar with SQL commands it’s quite straightforward to understand below migration code.
即使您不熟悉SQL命令,也很容易理解下面的迁移代码。
val MIGRATION_0_1 = object : Migration(0, 1) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE User ADD COLUMN name TEXT NOT NULL DEFAULT '' ")
}
}
The last step is to add this Migration
class to Room builder.
最后一步是将此“ Migration
类添加到“房间”构建器中。
val applicationContext = context.applicationContextval db = Room.databaseBuilder(appContext, AppDatabase::class.java) .addMigrations(MIGRATION_0_1)
.build()
Migration code provided: add an Entity
提供的迁移代码:添加实体
Now we want to save more information on our users, we want to save their address, for that purpose we create an Address
entity.
现在,我们想保存有关用户的更多信息,我们想保存他们的地址,为此,我们创建一个Address
实体。
@Entity
data class Address(@PrimaryKey val id: Int, addressLine: String)
We increase the database version to 2.
我们将数据库版本增加到2。
@Database(
entities = arrayOf(User::class, Address::class), version = 2)
We provide some migration code.
我们提供了一些迁移代码。
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE Address
(id INTEGER NOT NULL, addressLine TEXT NOT NULL, PRIMARY KEY(id))")
}
}
Finally we add that migration code to Room builder
最后,我们将该迁移代码添加到“房间”构建器中
val applicationContext = context.applicationContextval db = Room.databaseBuilder(appContext, AppDatabase::class.java) .addMigrations(MIGRATION_0_1, MIGRATION_1_2)
.build()
Special tip
特别提示
Now if you want to add a new entity with a lot of fields and with some nested objects. It would be quite difficult to write by yourself the migration code. Fortunately there is a way to make things much easier. By setting the annotation processor exportSchema
to true, Room will export at compile time the database schema as JSON format.
现在,如果要添加具有许多字段和一些嵌套对象的新实体。 自己编写迁移代码将非常困难。 幸运的是,有一种方法可以使事情变得容易得多。 通过将注释处理器exportSchema
设置为true,Room将在编译时将数据库模式导出为JSON格式。
@Database(
entities = arrayOf(User::class, Address::class), version = 2, exportSchema = true)
In your gradle
file you can specify the location where the schema will be exported.
在gradle
文件中,您可以指定导出架构的位置。
android {defaultConfig {kapt {arguments {
arg("room.schemaLocation", "$projectDir/roomSchemas".toString())
}}
}
Now let’s have a look at the generated JSON file but only the part corresponding to the Address
entity.
现在,让我们看一下生成的JSON文件,但仅查看与Address
实体相对应的部分。
{
"tableName": "Address",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `addressLine` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "addressLine",
"columnName": "addressLine",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
You can see in the line starting by createSql
the SQL command responsible for creating the Address
table in your database. You can copy this line, replace ${TABLE_NAME}
by the entity name Address
and put it to the migration code.
您可以在createSql
开头的行中看到负责在数据库中创建Address
表SQL命令。 您可以复制此行,替换${TABLE_NAME}
按实体名称Address
并将其放入迁移代码。
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `Address`
(`id` INTEGER NOT NULL, `addressLine` TEXT NOT NULL,
PRIMARY KEY(`id`))")
}
}
Migration test
迁移测试
As a good software developer we follow the TDD (Test Driven Development) principle, so we want to test our migrations.
作为一个好的软件开发人员,我们遵循TDD(测试驱动开发)原则,因此我们想测试我们的迁移。
The exported JSON files, that we saw earlier, represent your database’s schema history. You should store these files in your version control system, as it allows Room library to create older versions of the database for testing purposes.
我们之前看到的导出的JSON文件代表了数据库的架构历史。 您应该将这些文件存储在版本控制系统中,因为它允许Room库创建较旧版本的数据库以进行测试。
Before you can test your migrations, you must add the location of the exported schema as an asset folder.
在测试迁移之前,必须将导出的架构的位置添加为资产文件夹。
android {sourceSets {androidTest.assets.srcDirs +=
files("$projectDir/roomSchemas".toString())
}
}
and add the room testing library to the list of dependencies.
并将房间测试库添加到依赖项列表中。
dependencies {
androidTestImplementation "androidx.room:room-testing:2.2.5"
}
Let’s test the migration from version 0 to 1.
让我们测试一下从版本0到版本1的迁移。
The testing package provides a MigrationTestHelper
class, which can read exported schema files and then create the database in an older schema. We create database with version 0.
该测试包提供了一个MigrationTestHelper
类,该类可以读取导出的架构文件,然后在较旧的架构中创建数据库。 我们创建版本为0的数据库。
@RunWith(AndroidJUnit4::class)
class MigrationTest { companion object {
private const val TEST_DB = "migration-test"
} @get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
NdDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()) @Test
fun migrationFrom0To1() {
//Create database with version 0
val db = helper.createDatabase(TEST_DB, 0)
Then we need to insert the User
entity into the database, but we cannot use the DAO
class because it expects the latest schema.
然后,我们需要将User
实体插入数据库中,但是我们不能使用DAO
类,因为它需要最新的架构。
val values = ContentValues().apply {
put("id", 5)
}
db.insert("User", SQLiteDatabase.CONFLICT_REPLACE, values)
Then we need to run and validate the migration code.
然后,我们需要运行并验证迁移代码。
helper.runMigrationsAndValidate(TEST_DB, 1, true, MIGRATION_0_1)
However to have access, in instrumentation test, to MIGRATION_0_1
class we need to add @VisibleForTesting
annotation.
但是,要在工具测试中访问MIGRATION_0_1
类,我们需要添加@VisibleForTesting
批注。
@VisibleForTesting
val MIGRATION_0_1 = object : Migration(0, 1) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE User ADD COLUMN name TEXT NOT NULL DEFAULT '' ")
}
}
Now we need to verify that the data we inserted earlier has been well saved and well preserved during the migration operation. For that we open the database with the latest version.
现在,我们需要验证我们先前插入的数据在迁移操作期间是否已妥善保存和保留。 为此,我们使用最新版本打开数据库。
database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
NdDatabase::class.java, TEST_DB)
.addMigrations(MIGRATION_0_1, MIGRATION_1_2)
.build()
and then we retrieve the user from the database and check if it has been well saved.
然后我们从数据库中检索用户,并检查是否保存正确。
val dbUser = database.userDao().getUser()
assertEquals(5, dbUser.id)
assertEquals("", dbUser.id)
The complete code…
完整的代码…
@RunWith(AndroidJUnit4::class)
class MigrationTest {
companion object {
private const val TEST_DB = "migration-test"
}
@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
NdDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory())
@Test
fun migrationFrom0To1() {
val values = ContentValues().apply {
put("id", 5)
}
//Create database with version 0
helper.createDatabase(TEST_DB, 0).apply {
insert("User", SQLiteDatabase.CONFLICT_REPLACE, values)
close()
}
helper.runMigrationsAndValidate(TEST_DB, 1, true, MIGRATION_0_1)
val database = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
NdDatabase::class.java, TEST_DB)
.addMigrations(MIGRATION_0_1, MIGRATION_1_2)
.build()
val dbUser = database.userDao().getUser()
assertEquals(5, dbUser.id)
assertEquals("", dbUser.name)
}
}
翻译自: https://medium.com/swlh/the-complete-room-migration-guide-d88b46c51206
discuz完整迁移