Android 学习笔记 D26|升级后收藏还在吗:数据库迁移与交付准备
新版本增加一个"已读"字段,重新安装后运行正常,是否说明升级完成?还没有。重新安装往往走新建数据库路径,而原用户带着旧数据升级时,走的是另一条路径。数据迁移必须从旧状态出发验证。
本文对应 D26,重点回查《第一行代码》13.5.2 的 Room 数据库升级,以及第 15 章的项目交付。本篇把新增阅读状态作为具体需求,避免把工程整理写成只改目录名称。
先定义旧数据如何解释
假设收藏表已经保存文章 ID、标题、作者、链接和收藏时间。新增字段表示用户是否阅读过文章,需要先确定三条规则:旧记录默认未读;新收藏默认未读;切换已读不改变收藏时间。字段值与业务含义写清楚后,才能选择数据库默认值。
以下 SQL 表达新增列的局部迁移,沿用 D12 的 bookmark 表,SQLite 用整数存储此布尔含义:
sql
ALTER TABLE bookmark
ADD COLUMN is_read INTEGER NOT NULL DEFAULT 0;
SQL 不是完整 Room 迁移实现。还需要更新 Entity、提升数据库版本、注册对应迁移,并使列名、可空性和数据库默认值一致。Kotlin 构造参数的默认值与数据库 schema 的默认值不是同一个东西,不能只给属性写 = false 就认为数据库结构声明完整。Room 迁移
本系列前面的 Room 示例采用当前文档对应代际。修改已有旧工程时要以实际锁定版本为准,迁移类、数据库连接类型和测试辅助 API 必须配套,不能混贴不同年代的导包。
迁移测试为什么要插入旧记录
结构验证可以检查表和列是否符合预期,但它不能替你确认所有业务数据仍然正确。创建旧 schema 后,插入两条 ID、标题和收藏时间都不同的记录,再执行升级。查询时至少验证旧字段没有改变、新字段取默认值、后续更新读写有效。
可以用下面的 SQL 辅助检查,实际测试应由夹具执行并断言结果,而不是只肉眼浏览一次:
sql
SELECT articleId, title, createdAt, is_read
FROM bookmark
ORDER BY articleId;
再单独验证全新安装直接创建的新库。升级后的库和新建库应满足同样结构与默认值约定,否则可能出现新用户正常、旧用户出错的分叉。schema 导出文件应随代码保存,支持后续迁移测试和结构审查。
如果未来需要把一个字符串拆成多列,单纯增加默认字段不再足够,还必须设计旧数据转换与异常值处理。迁移里遇到重复或非法值,是保留、纠正还是拒绝升级,都需要明确规则,不应默默丢弃用户记录。
为什么不能用删库掩盖错误
练习目标是保留已经收藏的数据。删除数据库、清除应用数据或允许破坏性重建,都可能让应用再次启动,但它们没有证明升级路径正确。只有在产品明确允许丢弃某类数据时,才能讨论对应策略,收藏与可重建缓存也不能一概而论。
故障实验可以先漏掉迁移注册,观察旧库启动时的错误;恢复迁移后,再故意让 Entity 默认值与 SQL 不一致,检查 schema 验证。操作在测试数据库或练习副本进行,不拿重要数据做破坏性试验。修复后重复验证旧记录,而不是重新安装后宣布通过。
工程整理要回答对象由谁创建
数据库升级之外,这一天整理实际依赖方向。UI 发送操作并消费状态,ViewModel 组织屏幕逻辑,Repository 管理数据来源,DAO 执行数据库操作。手动注入时,应能指出数据库与 Repository 在哪里创建,ViewModel 的作用域在哪里,哪些对象随页面结束。
不要为了"像大项目"给每个函数包一层只转发调用的类。只有业务复杂或复用需要出现时,再增加领域层和用例抽象。官方架构建议
整理过程中优先删掉无用途代码、统一可预期错误、明确 Fake 与真实接口切换方式。大规模升级依赖和重写结构会同时引入多种变化,不适合在交付前和迁移任务捆绑进行。
让别人按说明找到同一个结果
README 至少说明项目功能范围、实际依赖环境、构建入口、数据来源、测试方式和已知限制。Gradle Wrapper 应与工程一起保留,避免"只能在我电脑上的某个全局 Gradle 跑"。命令应在正确项目目录执行,根据实际模块与构建类型调整。命令行构建
对包含 app 模块的常见工程,可以记录已经验证过的 gradlew.bat :app:assembleDebug 作为 Windows 构建入口;这只是命令示例,不表示本次写稿运行了它。安装包的构建类型、签名方式和是否压缩必须如实记录,不能把 Debug 包标为正式线上发布版本。
三道原创面试自测
Kotlin 默认值和数据库默认值有何不同? 前者用于对象构造,后者参与数据库插入与 schema 语义。追问"为什么升级校验失败",应比较实际表结构、Entity 声明和迁移 SQL,而不是只看对象能否创建。
为什么迁移测试还要做全新安装? 两条路径可能得到不同结构或默认值。追问"只检查打开成功够吗",不够,还要验证数据和后续业务读写。
手动注入是不是没有依赖注入? 不是,构造器传入依赖已经体现依赖注入。追问"框架增加什么",框架可以管理创建与作用域,但对象寿命和职责仍需要开发者设计。
当天验收
用旧数据升级成功并保留证据,再完成新安装对照;随后从 README 入口构建与安装对应版本。若迁移还失败,先解决数据正确性,不以多建几层目录替代交付。