当你在应用中添加和更改功能时,需要修改实体类以反映这些更改。当用户更新到您应用的最新版本时,你不想让他们丢失所有现有数据,尤其是在你无法从远程服务器恢复数据时。
借助 Room 持久性库,您可以编写 Migration 类,以这种方式保留用户数据。每个 Migration 类均指定一个 startVersion 和 endVersion。在运行时,Room 会运行每个 Migration 类的 migrate() 方法,以按照正确的顺序将数据库迁移到更高版本。
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.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
注意:要使迁移逻辑正常工作,请使用完整查询(而不是引用表示查询的常量)。
迁移过程完成后,Room 会验证架构以确保迁移正确进行。如果 Room 发现问题,则会抛出包含不匹配信息的异常。
编写迁移非常重要,如果未正确编写,可能会导致应用陷入崩溃循环。为了保持应用的稳定性,您应事先测试迁移。Room 提供了一个测试 Maven 工件来协助完成此测试过程。不过,要使此工件正常工作,您需要导出数据库的架构。
在编译时,Room 会将数据库的架构信息导出为 JSON 文件。要导出架构,请在 build.gradle 文件中设置 room.schemaLocation 注释处理器属性,如以下代码段所示:
build.gradle
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}
您应将导出的 JSON 文件(代表数据库的架构历史记录)存储在版本控制系统中,因为此系统可让 Room 创建您数据库的较低版本以进行测试。
要测试这些迁移,请将 Room 中的 Room.man.room:room:testing Maven 工件添加至测试依赖项中,并将该架构位置添加为资源文件夹,如以下代码段所示:
build.gradle
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.
}
}
上述示例展示了如何测试从一个版本到另一个版本的增量迁移。不过,建议您进行一次贯穿所有迁移的测试。这种类型的测试有助于捕获经过迁移路径的数据库与最近创建的数据库之间存在的所有差异。
以下代码段展示了所有迁移测试的示例:
@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(),
AppDatabase.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}
@Test
public void migrateAll() throws IOException {
// Create earliest version of the database.
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
db.close();
// Open latest version of the database. Room will validate the schema
// once all migrations execute.
AppDatabase appDb = Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().getTargetContext(),
AppDatabase.class,
TEST_DB)
.addMigrations(ALL_MIGRATIONS).build()
appDb.getOpenHelper().getWritableDatabase();
appDb.close();
}
// Array of all migrations
private static final Migration[] ALL_MIGRATIONS = new Migration[]{
MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4};
}
更新数据库的架构后,部分设备上的数据库可能仍会使用较低版本的架构。如果 Room 无法找到将该设备的数据库从旧版本升级到当前版本的迁移规则,则会发生 IllegalStateException。
要防止应用在发生这种情况时崩溃,请在创建数据库时调用 fallbackToDestructiveMigration() 构建器方法:
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
.fallbackToDestructiveMigration()
.build();
通过在应用的数据库构建逻辑中添加此子句,您指示 Room 在架构版本之间缺少迁移路径时破坏性地重建应用的数据库表。
警告:通过在应用的数据库构建器中配置此选项,Room 会在缺少迁移路径时从数据库表中永久删除所有数据。
破坏性重建回退逻辑包含几个附加选项: