如何为带@Fts4注解的Room实体添加字段并完成数据库迁移?
Room 2.6.1中FTS表的迁移方案
Room的自动迁移无法处理FTS虚拟表的结构变更(因为SQLite的FTS表不支持ALTER语句),必须通过手动迁移解决,以下是标准流程和针对你问题的解决方案:
核心迁移步骤(针对添加FTS索引字段)
1. 先获取Room自动生成的新SQL语句
修改实体类添加notes字段并纳入FTS索引后,编译项目,找到Room自动生成的AppDatabase_Impl.java(路径一般为build/generated/source/kapt/debug/[你的包名]/),复制其中:
- 新的FTS表创建语句
- 三个同步触发器(
after_insert/after_update/after_delete)的创建语句
2. 完整手动迁移代码示例
static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { // 备份原实体表数据(保留rowid,FTS依赖它关联原表) database.execSQL("CREATE TABLE IF NOT EXISTS recipes_backup AS SELECT * FROM recipes;"); // 删除Room自动生成的旧FTS同步触发器 database.execSQL("DROP TRIGGER IF EXISTS recipes_fts_after_insert_rowid;"); database.execSQL("DROP TRIGGER IF EXISTS recipes_fts_after_update_rowid;"); database.execSQL("DROP TRIGGER IF EXISTS recipes_fts_after_delete_rowid;"); // 删除旧FTS表及其关联影子表(这些是FTS4内部辅助表,必须一起删除) database.execSQL("DROP TABLE IF EXISTS recipes_fts;"); database.execSQL("DROP TABLE IF EXISTS recipes_fts_content;"); database.execSQL("DROP TABLE IF EXISTS recipes_fts_docsize;"); database.execSQL("DROP TABLE IF EXISTS recipes_fts_config;"); // 给原实体表添加新字段 database.execSQL("ALTER TABLE recipes ADD COLUMN notes TEXT;"); // 创建新的FTS表(用从Impl类复制的语句,确保包含新字段notes) database.execSQL("CREATE VIRTUAL TABLE IF NOT EXISTS recipes_fts USING FTS4(title, description, source, ingredients, notes, tokenize=unicode61 `tokenchars=#`, content=`recipes`);"); // 重建Room自动生成的同步触发器(复制Impl类中的新语句) database.execSQL("CREATE TRIGGER IF NOT EXISTS recipes_fts_after_insert_rowid AFTER INSERT ON recipes BEGIN INSERT INTO recipes_fts(rowid, title, description, source, ingredients, notes) VALUES (new.rowid, new.title, new.description, new.source, new.ingredients, new.notes); END;"); database.execSQL("CREATE TRIGGER IF NOT EXISTS recipes_fts_after_update_rowid AFTER UPDATE ON recipes BEGIN INSERT INTO recipes_fts(recipes_fts, rowid, title, description, source, ingredients, notes) VALUES('delete', old.rowid, old.title, old.description, old.source, old.ingredients, old.notes); INSERT INTO recipes_fts(rowid, title, description, source, ingredients, notes) VALUES (new.rowid, new.title, new.description, new.source, new.ingredients, new.notes); END;"); database.execSQL("CREATE TRIGGER IF NOT EXISTS recipes_fts_after_delete_rowid AFTER DELETE ON recipes BEGIN INSERT INTO recipes_fts(recipes_fts, rowid, title, description, source, ingredients, notes) VALUES('delete', old.rowid, old.title, old.description, old.source, old.ingredients, old.notes); END;"); // 将备份数据导回原表(触发器会自动同步到新FTS表) database.execSQL("INSERT INTO recipes(rowid, title, description, source, ingredients) SELECT rowid, title, description, source, ingredients FROM recipes_backup;"); // 删除临时备份表 database.execSQL("DROP TABLE IF EXISTS recipes_backup;"); } };
针对你遇到的问题的解答
1. 触发器重建的繁琐问题
不用手动编写触发器逻辑,直接从Room编译生成的AppDatabase_Impl.java中复制新的触发器语句即可——Room会根据你更新后的实体类自动生成正确的同步逻辑,复用这些代码就能避免手动编写的错误和繁琐。
2. 手动迁移的常见问题及解决
- 数据丢失:必须备份原表数据并导回,且备份时要保留
rowid(FTS表通过rowid与原表关联); - FTS同步失效:确保删除旧触发器并重建新的,否则原表数据变更不会同步到新FTS表;
- SQL语法错误:严格复制Room生成的FTS创建语句和触发器语句,尤其是
tokenize参数、触发器内的'delete'标记等细节。
3. FTS影子表的处理
那些以recipes_fts_开头的表(_content/_docsize/_config)是SQLite FTS4的内部辅助表,与主FTS表绑定,旧的影子表在删除主FTS表后不会自动清理,必须手动删除,否则会残留无效表。重建FTS表时,SQLite会自动创建新的影子表,所以旧的影子表可以安全删除。
注意事项
- 迁移操作要在后台线程执行,避免主线程阻塞导致ANR;
- 测试时用真实数据验证,确保FTS搜索功能正常、数据无丢失;
- 每次FTS结构变更后,都需要重复上述流程:备份→删旧触发器/表→改原表→建新表/触发器→恢复数据。
内容的提问来源于stack exchange,提问作者flauschtrud
相关产品推荐
相关产品推荐

