You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Room数据库分类同步问题:插入冲突失效及增删同步需求

解决Room同步分类时重复插入及favorite状态丢失问题

问题根源

  1. 主键设置错误:若MovieCategory实体未将idByAccountData设为主键,@Insert(onConflict = IGNORE)会因主键不重复无法触发忽略逻辑,导致每次同步都插入新数据,原有favorite状态被新插入的false覆盖。
  2. 同步逻辑不完整:仅处理插入新分类,未覆盖「更新现有分类信息」和「删除已失效分类」的场景。

解决步骤

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 08:25:06