Room数据库分类同步问题:插入冲突失效及增删同步需求
解决Room同步分类时重复插入及favorite状态丢失问题
问题根源
- 主键设置错误:若
MovieCategory实体未将idByAccountData设为主键,@Insert(onConflict = IGNORE)会因主键不重复无法触发忽略逻辑,导致每次同步都插入新数据,原有favorite状态被新插入的false覆盖。 - 同步逻辑不完整:仅处理插入新分类,未覆盖「更新现有分类信息」和「删除已失效分类」的场景。
解决步骤
1. 修正实体类主键
确保idByAccountData作为唯一主键(它是区分不同账户下同一分类的唯一标识):
@Entity data class MovieCategory( val id: Int, val title: String, val accountData: String, var favorite: Boolean, @PrimaryKey val idByAccountData: String )
2. 更新DAO方法
添加查询账户分类、删除失效分类的方法,并修改插入策略为REPLACE(实现新增+更新,同时保留favorite状态):
@Dao interface MovieCategoryDao { // 替换原有Insert方法,用REPLACE实现新增和更新 @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertMovieCategory(moviecategory: List<MovieCategory>) // 查询指定账户下的所有分类 @Query("SELECT * FROM MovieCategory WHERE accountData = :accountData") suspend fun getMovieCategoriesByAccount(accountData: String): List<MovieCategory> // 删除指定账户下不在同步列表中的分类 @Query("DELETE FROM MovieCategory WHERE accountData = :accountData AND idByAccountData NOT IN (:keepIds)") suspend fun deleteUnsyncedCategories(accountData: String, keepIds: List<String>) }
3. 重构ViewModel同步逻辑
同步时先获取本地分类的favorite状态,映射到API返回数据中,再执行插入更新和删除操作:
fun getMovieCategory(url: String, code: String) { viewModelScope.launch { val token = sessionManager.fetchAuthToken() val accountData = "$url$code" val response = myRepository.getMovieCategory(url, code, "Bearer $token") when (response) { is Resource.Success -> { val apiCategories = response.data.js ?: emptyList() if (apiCategories.isEmpty()) { // API返回空时,清空该账户下所有分类 dao.deleteUnsyncedCategories(accountData, emptyList()) _getMovieCategoryLiveData.value = Resource.Success(emptyList()) return@launch } // 获取本地分类,构建「主键-favorite」映射表 val localCategories = dao.getMovieCategoriesByAccount(accountData) val localFavoriteMap = localCategories.associate { it.idByAccountData to it.favorite } // 映射API数据,保留本地favorite状态 val mappedCategories = apiCategories.map { moviecatresponse -> val idByAccountData = "${moviecatresponse.id}$url$code" MovieCategory( id = moviecatresponse.id, title = moviecatresponse.title, accountData = accountData, favorite = localFavoriteMap.getOrDefault(idByAccountData, false), idByAccountData = idByAccountData ) } // 执行插入/更新、删除失效分类 dao.insertMovieCategory(mappedCategories) val keepIds = mappedCategories.map { it.idByAccountData } dao.deleteUnsyncedCategories(accountData, keepIds) _getMovieCategoryLiveData.value = Resource.Success(mappedCategories) } is Resource.Error -> { _getMovieCategoryLiveData.value = Resource.Error(response.message) } } } }
方案说明
- 保留favorite状态:通过本地查询的映射表,将用户修改的favorite值赋值给同步后的分类实体,避免被API返回的
false覆盖。 - 新增/更新分类:
REPLACE策略会在主键重复时更新现有数据(比如分类标题变化),主键不存在时插入新数据。 - 清理失效分类:通过
DELETE语句删除本地存在但API已不再返回的分类,保证本地数据与API完全同步。
内容的提问来源于stack exchange,提问作者Alex Mutschl
相关产品推荐
相关产品推荐

