Android Room如何顺序插入数据并获取各表rowid完成关联插入?
如何向Room数据库顺序添加数据?
需求场景
需要严格按先后顺序完成多表关联插入:
- 第一步:向
word表插入2条单词记录,拿到两条记录自动生成的rowid - 第二步:用两个word的rowid,向
word pair表插入单词配对记录,拿到这条配对记录的rowid - 第三步:用配对记录的rowid,向
word base and word pairs中间关联表插入关联数据
原有实现代码
Fragment层代码:
viewLifecycleOwner.lifecycleScope.launch { var languageId = vocabularyViewModel.getLanguageId(word.wordLanguage) // yet added to db var languageId2 = vocabularyViewModel.getLanguageId(word.translatedLanguage) languageId.combine(languageId2) { l1, l2 -> val r1 = vocabularyViewModel.addWord_(word.wordToTranslate, l1) val r2 = vocabularyViewModel.addWord_(word.translatedWord, l2) val r3 = vocabularyViewModel.addPairOfWords(r1.toInt(), r2.toInt()) vocabularyViewModel.addWordBasePairOfWords(adapter.wordBaseName, r3.toInt()) }
ViewModel层代码:
fun getLanguageId(language: String) = repo.getLanguageId(language) suspend fun addWord_ (name: String, id: Int) = repo.insertWord(Word(name = name, languageId = id)) suspend fun addPairOfWords(wordId: Int, translateId: Int) = repo.insertPairOfWords( PairOfWords(word_id = wordId , translate_id = translateId) ) fun addWordBasePairOfWords(wordBaseName: String, pairId: Int) = repo.getWordBaseId(wordBaseName).map { repo.insertWordBasePairOfWords(PairOfWordsWordBaseCrossRef(it, pairId)) }
核心问题点
combine是Flow的冷流操作符,没有调用终止操作(比如collect/first())的话,内部代码块根本不会触发执行- 原有
addWordBasePairOfWords返回的是Flow,既没收集也没触发终止操作,最后一步关联表插入不会实际执行;而且在Flow的map中间操作里调用挂起函数,本身不符合Flow的使用规范 - 多表分步插入没有加事务,万一中间某步崩溃,会留下没有关联的孤立脏数据,后续很难清理
修正方案
协程中的挂起函数默认按代码顺序逐行执行,不需要用Flow组合操作符做顺序控制,只需要先拿到提前查询的ID,再按顺序调用插入方法即可;同时给多步插入加数据库事务,保证操作原子性(要么全成功要么全回滚)。
第一步:调整ViewModel层代码
把关联插入方法改造成挂起函数,新增事务包裹的批量插入方法:
// 原有语言ID查询方法保持不变 fun getLanguageId(language: String) = repo.getLanguageId(language) suspend fun addWord_(name: String, id: Int) = repo.insertWord(Word(name = name, languageId = id)) suspend fun addPairOfWords(wordId: Int, translateId: Int) = repo.insertPairOfWords( PairOfWords(word_id = wordId, translate_id = translateId) ) // 改造成挂起函数,直接取查询结果执行插入 suspend fun addWordBasePairOfWords(wordBaseName: String, pairId: Int) { val baseId = repo.getWordBaseId(wordBaseName).first() // 取单次查询结果即可,不需要持续监听 repo.insertWordBasePairOfWords(PairOfWordsWordBaseCrossRef(baseId, pairId)) } // 新增事务方法,保证多步插入数据一致性 suspend fun addWordWithRelation( sourceWordName: String, sourceLangId: Int, targetWordName: String, targetLangId: Int, wordBaseName: String ) { // Room的@Transaction注解标记的方法会自动开启事务,所有操作完成后统一提交 repo.runInTransaction { val r1 = repo.insertWord(Word(name = sourceWordName, languageId = sourceLangId)) val r2 = repo.insertWord(Word(name = targetWordName, languageId = targetLangId)) val r3 = repo.insertPairOfWords(PairOfWords(word_id = r1.toInt(), translate_id = r2.toInt())) val baseId = repo.getWordBaseId(wordBaseName).first() repo.insertWordBasePairOfWords(PairOfWordsWordBaseCrossRef(baseId, r3.toInt())) } }
第二步:调整Fragment层代码
先获取两个语言ID,再直接调用事务插入方法即可:
viewLifecycleOwner.lifecycleScope.launch { // 先取两个语言的ID,用first()拿单次结果,不需要持续监听数据变化 val l1 = vocabularyViewModel.getLanguageId(word.wordLanguage).first() val l2 = vocabularyViewModel.getLanguageId(word.translatedLanguage).first() // 挂起函数默认顺序执行,不需要额外嵌套组合操作 vocabularyViewModel.addWordWithRelation( sourceWordName = word.wordToTranslate, sourceLangId = l1, targetWordName = word.translatedWord, targetLangId = l2, wordBaseName = adapter.wordBaseName ) }
注:标记了
suspend的Room插入方法会自动切换到IO线程执行,不需要手动指定协程调度器。
内容的提问来源于stack exchange,提问作者Daniil Zhukov
相关产品推荐
相关产品推荐

