一、Room 数据库简介与迁移必要性
说起 Android 端的本地存储,大家可能用过 SQLite、SharedPreferences、或者更早的 SQLiteOpenHelper。但说实话,真正让本地数据库变得「好用」的,还是 Room。
Room 是 Google 官方推出的 ORM 框架,它帮我们封装了 SQLite 的样板代码,还提供了编译时 SQL 校验、与 LiveData/Flow 的完美配合、以及最关键的——数据库迁移支持。我个人是从 Room 1.0 就开始用了,那时候坑还不少,但到今天,它已经是 Jetpack 家族里最靠谱的成员之一。
1.1 Room 到底解决了什么问题?
你可能觉得 SQLite 够用了,何必再套一层?我一开始也这么想。但在实际项目中,你会发现每次数据库表结构变化,都要写一堆升级代码。而且人的记性是不可靠的……
- 简化 SQLite 操作:不用再写 ContentValues、Cursor 来回转对象,直接操作 DAO 接口就行。
- 编译时校验 SQL:如果你的 SQL 写错了,编译直接报错,而不是等到崩溃才查 Bug。
- 与架构组件无缝集成:配合 ViewModel、LiveData、Flow,数据变化自动通知 UI。
- 内置迁移机制:这是重点——
Migration类让你以安全的方式升级数据库。
一句话总结:Room 把“难用的 SQLite 操作”变成了“优雅的 Kotlin/Java 代码”,而且强制你管理好数据库版本。
1.2 为什么一定要关注迁移?
你在开发阶段可以随意删 App 重装,但用户不会。一旦 App 发布到商店,用户的数据库里存着他们的数据。你要是更新版本时直接把旧表 drop 掉重建,那用户就该骂你了。
迁移(Migration)就是让你在不丢失数据的前提下,把数据库从旧版本升级到新版本。比如我遇到过一个真实案例:用户从 1.0 版本直接跳到 3.0 版本,中间跳过了一个版本,结果数据全丢了,就是因为少写了一组 Migration。
⚠️ 警告:如果版本升级时没有提供对应的 Migration,Room 会抛 IllegalStateException 并直接 crash。这不是 Bug,是 Google 故意设计的——就是要逼你正视数据库变化。
1.3 什么时候需要写迁移?
你想想看,下面这些场景都会触发迁移需求:
- 新增一张表
- 删除一张表
- 修改表名或列名
- 增加/删除一列
- 修改列的类型或约束
- 添加索引或唯一约束
说白了,只要 @Entity 注解里的结构变了,你就要考虑写 migration。哦对,别忘了索引也算。
1.4 迁移的四种策略
| 策略 | 说明 | 风险 |
|---|---|---|
Migration(startVersion, endVersion) |
手写 SQL,完全控制升级步骤 | 低,但需要你保证 SQL 正确 |
fallbackToDestructiveMigration() |
找不到匹配 Migration 时,直接重建所有表 | 高——数据全丢 |
| AutoMigration(Room 2.4+) | 自动生成 migration,但只支持简单的增加列/表 | 中,复杂结构变化会失败 |
MigrationTestHelper 配合测试 |
在测试环境模拟迁移,验证数据完整性 | 低,强烈推荐 |
我个人最推荐第一种 + 第四种组合。手动写 SQL,再用测试验证。别相信自己的手写 SQL 能一次正确,我翻过车,真的。
1.5 为什么需要回归验证?
迁移写好了,但你怎么保证迁移后 DAO 还能正常工作?比如我以前写过一次迁移:把 userName 改成了 nickname,迁移 SQL 也写了 ALTER TABLE ... RENAME COLUMN。但忘了修改对应的 DAO 方法里的查询 SQL,结果接口返回空字段 → 用户看不到昵称 → 被投诉。
回归验证就是要测试:迁移前能查到数据,迁移后相同的数据还能查到,且格式正确。DAO 回归验证通常结合 Room.inMemoryDatabaseBuilder 和 JUnit 测试来做,我们后面章节会详细展开。
1.6 一个典型的版本演进过程
下面这张图展示了 App 从 version 1 到 version 3 的迁移路径,包含了中间版本的数据流动:
1.7 迁移不写测试的后果
我之前有个项目,上线前紧急加了一列,觉得只是 ALTER TABLE ADD COLUMN 很简单。结果上线后用户反馈数据加载不出来。查了半天发现:新列有 NOT NULL 约束,但旧数据该列为 NULL,迁移直接失败。Room 抛了异常,整个数据库不可用。
如果当时我写了一个简单的迁移测试,在新建的数据库里插入一条旧版本数据,然后跑迁移,再查一下……就不会出这事了。
所以,我强烈建议:每个 Migration 都配一个对应的 @Test。后面章节我会手把手演示怎么写。
💡 小技巧:使用 MigrationTestHelper 时,记得传 allowDestructiveMigrationWhenSchemaMismatch = false,这样如果有 schema 不匹配情况,测试会直接报错而不是删除重建——就能暴露问题。
1.8 本章小结
讲了这么多,核心就三件事:
- Room 让数据库操作更安全、更优雅。
- 迁移是保护用户数据的必经之路,不能偷懒。
- 迁移 + DAO 回归验证,才能保证「升级不丢数据,接口还能正常工作」。