2. 手动迁移核心类 Migration 创建

当数据库结构发生破坏性变化(如新增表、修改字段类型、删除列)时,Room 无法自动推断升级路径,必须通过手动迁移确保数据完整。手动迁移的核心是继承 Migration 抽象类,并准确声明版本区间与执行 SQL。

2.1 原理:版本区间标识与迁移调度

Room 的迁移引擎根据 startVersion 和 endVersion 匹配升级路径。当数据库版本号(version)升高时,Room 查找所有满足 startVersion == 当前数据库版本号 且 endVersion == 目标版本号 的 Migration 实例,按顺序执行其 migrate() 方法。多个连续升级可串联成链。

属性 说明 约束
startVersion 迁移起始版本号(当前数据库版本) 必须大于等于 1,且 startVersion < endVersion
endVersion 迁移目标版本号 必须大于 startVersion
migrate() 包含 SQL 语句的执行方法 在事务内自动执行,需保证幂等性
提示:版本号必须是递增整数,且 startVersion 不能等于 endVersion。Room 不支持降级迁移(反向迁移需自行处理)。

2.2 实现流程:创建 Migration 实例

  1. 确定新旧版本号(如 1 → 2)。
  2. 继承 Migration 抽象类,实现构造方法中指定版本区间。
  3. 在 migrate() 方法内编写 ALTER TABLE、CREATE TABLE 等 SQL 语句。
  4. 将创建好的 Migration 对象添加到 Room.databaseBuilder() 的 addMigrations() 中。
  5. 适时更新数据库实体定义和 DAO,确保新旧版本兼容。
关键案例:从版本 1 升级到版本 2,新增“用户年龄”字段(INTEGER 类型,默认值 0):
public class Migration1To2 extends Migration(1, 2) {
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE user ADD COLUMN age INTEGER NOT NULL DEFAULT 0");
    }
}

2.3 代码与配置:完整 Migration 类

下面展示符合 Room 标准的 Migration 类实现,包含常见表结构变更操作:

public class Migration2To3 extends Migration(2, 3) {
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {
        // 1. 新建地址表
        database.execSQL(
            "CREATE TABLE IF NOT EXISTS address (" +
            "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
            "user_id INTEGER NOT NULL, " +
            "city TEXT NOT NULL, " +
            "street TEXT, " +
            "FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE" +
            ")"
        );
        // 2. 删除旧的不再使用的列
        database.execSQL("ALTER TABLE user DROP COLUMN old_legacy_flag");
        // 3. 添加新索引
        database.execSQL("CREATE INDEX idx_address_user ON address(user_id)");
    }
}
  • 必须使用 SupportSQLiteDatabase 执行 SQL,而非 Room 的 DAO 对象。
  • 建议将多次 execSQL 封装在同一个 migrate() 内,Room 会自动开启事务。
  • 若需要条件判断,可在 migrate() 内查询 PRAGMA table_info 等系统表做兼容。

2.4 调试与验证

验证方法 操作 预期结果
单元测试 使用 MigrationTestHelper 创建指定版本的数据库并执行迁移 迁移后表结构与数据正确
真实设备测试 安装旧版 App,再安装新版,检查数据是否保留 无数据丢失,新字段有默认值
日志分析 开启 Room 日志 QueryCallback 查看执行的 SQL 确认迁移 SQL 按预期顺序执行
常见错误:
  • 版本号不连续(如从 1 直接跳到 3 但缺少 2 的 Migration),Room 会抛出 IllegalStateException。
  • SQL 语法错误(如 SQLite 不支持 ALTER TABLE DROP COLUMN 旧版本),迁移会失败并导致 App 崩溃。
  • 未正确处理 NOT NULL 默认值与旧数据冲突。

2.5 性能与稳定性优化

  • 批量 DDL 放在同一个迁移中:Room 的迁移在单个事务内执行,多次 execSQL 不会额外提交,避免磁盘 I/O 碎片。
  • 避免大事务阻塞:若迁移涉及大量数据转换(如复制整张表),考虑分批处理或使用 yieldIfContended() 让出线程(Room 3.0+ 支持 autoMigrations 中的渐进式迁移)。
  • 测试迁移的幂等性:多次执行同一迁移应不产生副作用,使用 CREATE TABLE IF NOT EXISTS 和 ALTER TABLE ADD COLUMN 前先检查列是否存在。
  • 预编译 SQL 语句:对于复杂迁移,使用 database.compileStatement() 替代 execSQL 可提升少量性能,但通常 execSQL 足够。

2.6 流程总览:Migration 创建与生效

Migration 创建与生效流程图 旧数据库版本 N 继承 Migration 类 start=N, end=M migrate() 写 SQL 新数据库版本 M 数据库版本号递增 通过 addMigrations() 注册

上述流程清晰展示了:从旧版本 N 出发,通过继承 Migration 并指定版本区间,在 migrate() 中编写 SQL,最终将数据库升级到新版本 M。每个 Migration 只负责连续版本间的单步迁移,多版本升级可通过串联多个 Migration 对象实现。