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

Room Upsert成功更新却返回-1的问题排查求助

Room Upsert成功更新却返回-1的问题解析

问题描述

我之前了解到Room的Upsert操作出错时通常返回-1,但最近在项目中发现,即使更新操作成功执行(通过App Inspection确认数据库条目已被修改,比如性别从female改为male),Upsert仍然返回-1,导致错误提示被触发。当前项目使用Room版本为2.6.1,想知道哪里出现了认知偏差?

相关代码

Room配置

@Entity(tableName = "birth_defect_beneficiary_details")
data class BirthDefectBabyDetails(
    @PrimaryKey(autoGenerate = true)
    val id: Int? = null,

    val dateOfBirth: Long,
    val sex: String,

    val motherName: String,
    val fatherName: String,

    val address: String,
    val contactNumber: String,

    val condition: String,

    val patientContacted: Boolean
)

@Dao
interface BDBabyDetailsDao {

    @Query("SELECT * FROM birth_defect_beneficiary_details ORDER BY dateOfBirth DESC")
    fun getDetailsOfAllBabies(): Flow<List<BirthDefectBabyDetails>>

    @Upsert
    suspend fun upsertEntry(entry: BirthDefectBabyDetails): Long

    @Delete
    suspend fun deleteEntry(entry: BirthDefectBabyDetails): Int

    @Query("DELETE FROM birth_defect_beneficiary_details")
    suspend fun deleteAllEntries()
}

@Database(
    entities = [BirthDefectBabyDetails::class],
    version = 1,
    exportSchema = false
)
abstract class BDBabiesDatabase : RoomDatabase() {
    abstract val babyDao: BDBabyDetailsDao
}

interface BabyDetailsEntryRepository {
    fun getAllBabiesStream(): Flow<List<BirthDefectBabyDetails>>
    suspend fun upsertEntry(entry: BirthDefectBabyDetails): Long
    suspend fun deleteEntry(entry: BirthDefectBabyDetails): Int
    suspend fun deleteAllEntries()
}

class BDBabyDetailsLocalRepository @Inject constructor(
    private val babyDao: BDBabyDetailsDao
) : BabyDetailsEntryRepository {
    override fun getAllBabiesStream(): Flow<List<BirthDefectBabyDetails>> {
        return babyDao.getDetailsOfAllBabies()
    }

    override suspend fun upsertEntry(entry: BirthDefectBabyDetails): Long {
        return babyDao.upsertEntry(entry = entry)
    }

    override suspend fun deleteEntry(entry: BirthDefectBabyDetails): Int {
        return babyDao.deleteEntry(entry = entry)
    }

    override suspend fun deleteAllEntries() {
        babyDao.deleteAllEntries()
    }
}

ViewModel中执行Upsert的代码

private fun upsertEntry() {

    // some codes...

    viewModelScope.launch(IO) {
        try {
            val result = babyRepository.upsertEntry(newEntry)
            Log.d("DPDC", "$result")
            if (result != -1L) {
                triggerEvent(VMEvents.EntryAdded("B/O ${newEntry.motherName}"))
            } else {
                triggerEvent(VMEvents.ShowError("Failed to add/update entry"))
            }
        } catch (e: Exception) {
            triggerEvent(VMEvents.ShowError("Error occurred: ${e.message}"))
        }
    }

    // some more codes...
}

newEntry声明为val newEntry: BirthDefectBabyDetails

问题原因与解决方案

核心原因

你对Room @Upsert的返回值逻辑理解有误:

  • 当执行插入操作时,Room返回新插入条目的自增主键ID
  • 当执行更新操作时,如果实体使用autoGenerate = true的自增主键,Room固定返回**-1**,这是正常设计,并非操作失败

Room的Upsert操作只有在真正发生异常(比如约束冲突、数据库连接错误)时才会抛出Exception,返回-1仅仅代表本次是更新动作,而非插入。

解决方案

修改ViewModel中的判断逻辑,去掉基于返回值的错误判断,只要没有抛出异常就判定操作成功:

private fun upsertEntry() {
    // some codes...
    viewModelScope.launch(IO) {
        try {
            babyRepository.upsertEntry(newEntry)
            triggerEvent(VMEvents.EntryAdded("B/O ${newEntry.motherName}"))
        } catch (e: Exception) {
            triggerEvent(VMEvents.ShowError("Error occurred: ${e.message}"))
        }
    }
    // some more codes...
}

额外说明

如果需要区分插入和更新动作,可以通过实体的主键是否为null来判断:

  • 若newEntry.id == null,执行的是插入操作,返回新生成的ID
  • 若newEntry.id != null,执行的是更新操作,返回-1

这是Room 2.5及以上版本针对自增主键的@Upsert注解的标准行为。

内容的提问来源于stack exchange,提问作者TheSelfTaughtNoob

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 18:53:10