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

Android Room复合主键删除无效:匹配数据却无法删除

问题解决:Room批量删除复合主键实体失效

问题背景

从远程MySQL同步数据到本地Android Room数据库时,批量DELETE操作失效:待删除数据的复合主键(groupFK和readingFK)与数据库行完全匹配,但调用DAO的@Delete方法传入List<LHPsalmJoin>时,返回删除行数始终为0,对应行未被删除。手动执行DELETE SQL语句可正常删除目标行,需要实现批量删除功能且无需手动编写SQL。

相关代码

Entity实体类

@Entity(
    tableName = Constants.LH_PSALM_JOIN,
    primaryKeys = ["groupFK", "readingFK"],
    foreignKeys =
    [
        ForeignKey(
            entity = LHPsalmodyJoinEntity::class,
            parentColumns = arrayOf("groupID"),
            childColumns = arrayOf("groupFK"),
            onDelete = CASCADE,
            onUpdate = CASCADE
        ),
        ForeignKey(
            entity = LHPsalmEntity::class,
            parentColumns = arrayOf("psalmID"),
            childColumns = arrayOf("readingFK"),
            onDelete = CASCADE,
            onUpdate = CASCADE
        ), ForeignKey(
        entity = LHThemeEntity::class,
        parentColumns = arrayOf("themeID"),
        childColumns = arrayOf("themeFK"),
        onDelete = SET_NULL,
        onUpdate = CASCADE
    ), ForeignKey(
        entity = EpigraphEntity::class,
        parentColumns = arrayOf("epigraphID"),
        childColumns = arrayOf("epigraphFK"),
        onDelete = SET_NULL,
        onUpdate = CASCADE
    )]
)
data class LHPsalmJoinEntity(
    @ColumnInfo(name = "groupFK", index = true)
    var groupFK: Int,

    @ColumnInfo(name = "readingFK", index = true)
    var readingFK: Int,

    @ColumnInfo(name = "theOrder")
    var theOrder: Int,

    @ColumnInfo(name = "themeFK", defaultValue = "NULL", index = true)
    var themeFK: Int?,

    @ColumnInfo(name = "epigraphFK", defaultValue = "NULL", index = true)
    var epigraphFK: Int?,

    @ColumnInfo(name = "thePart", defaultValue = "NULL")
    var thePart: Int?
)

数据传输类(DTO)

@JsonClass(generateAdapter = true)
data class LHPsalmJoin(
    val groupFK:Int,
    val readingFK:Int,
    val theOrder:Int,
    val themeFK:Int?=null,
    val epigraphFK:Int?=null,
    val thePart:Int?=null,
)

DAO删除方法

@Delete(entity = LHPsalmJoinEntity::class)
fun lhPsalmJoinDeleteAll(d: List<LHPsalmJoin>):Int

调用代码

/* d为List<LHPsalmJoin>类型,已填充待删除数据 */
val count=mDao.lhPsalmJoinDeleteAll(d)
println("Deleted rows: $count") //始终返回0

问题原因及解决方案

核心原因

Room的@Delete注解配合entity参数使用时,会通过实体类的所有属性匹配数据库行,而非仅依赖主键。你的LHPsalmJoin和LHPsalmJoinEntity虽然主键字段一致,但其他字段(如theOrder、themeFK等)可能与数据库存储的实际值不匹配,导致Room无法找到对应行执行删除。

解决方案

方案1:仅传递主键字段删除

创建仅包含复合主键的类,让Room仅用主键匹配行:

// 定义仅含主键的类
data class LHPsalmJoinKey(val groupFK: Int, val readingFK: Int)

// 修改DAO方法
@Delete(entity = LHPsalmJoinEntity::class)
fun lhPsalmJoinDeleteAll(keys: List<LHPsalmJoinKey>): Int

// 调用时转换数据
val deleteKeys = d.map { LHPsalmJoinKey(it.groupFK, it.readingFK) }
val count = mDao.lhPsalmJoinDeleteAll(deleteKeys)

方案2:确保DTO与实体类全字段匹配

如果必须使用LHPsalmJoin删除,需保证传入对象的所有字段值与数据库对应行完全一致(包括theOrder、可为null的字段),但这种方式依赖数据完整性,可靠性低于方案1。

方案3:用@Query实现批量删除(无需手动拼接SQL)

通过@Query配合事务实现原子性批量删除:

@Query("DELETE FROM ${Constants.LH_PSALM_JOIN} WHERE groupFK = :groupFK AND readingFK = :readingFK")
fun deleteByKey(groupFK: Int, readingFK: Int): Int

// 事务内批量执行
@Transaction
fun lhPsalmJoinDeleteAll(d: List<LHPsalmJoin>): Int {
    var total = 0
    d.forEach { total += deleteByKey(it.groupFK, it.readingFK) }
    return total
}

验证建议

  • 检查传入的LHPsalmJoin对象非主键字段是否与数据库存储值一致,尤其是theOrder和可为null的字段。
  • 开启Room查询日志(在Database Builder中添加.setQueryCallback(...)),查看自动生成的DELETE语句,确认匹配条件是否正确。

内容的提问来源于stack exchange,提问作者A. Cedano

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 10:33:26