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

Android ROOM中使用关联表查询单表数据的正确方案

正确实现方案

1. 修正关联表实体类定义

首先确保AppointmentJoinRef实体类正确映射关联表,同时配置外键级联规则以清理关联记录:

@Entity(
    tableName = "AppointmentJoinRef",
    foreignKeys = [
        ForeignKey(
            entity = AppointmentModel::class,
            parentColumns = ["ID"],
            childColumns = ["ID"],
            onDelete = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = AppointmentModel::class,
            parentColumns = ["ID"],
            childColumns = ["LINKED_ID"],
            onDelete = ForeignKey.CASCADE
        )
    ],
    primaryKeys = ["ID", "LINKED_ID"] // 复合主键避免重复双向关联记录
)
data class AppointmentJoinRef(
    @ColumnInfo(name = "ID") val appointmentId: Int,
    @ColumnInfo(name = "LINKED_ID") val linkedAppointmentId: Int
)

这里用复合主键可以避免存储重复的双向关联(比如同时存(1,2)和(2,1)),外键的onDelete = CASCADE会在删除预约时自动清理关联表中对应的记录。

2. 修正POJO的@Relation配置

原POJO的@Relation参数错误,导致查询到自身而非关联预约。正确配置需让Room通过关联表匹配当前预约与关联预约:

data class AppointmentWithLinked(
    @Embedded val appointment: AppointmentModel,
    @Relation(
        entity = AppointmentModel::class,
        parentColumn = "ID",
        entityColumn = "ID",
        associateBy = Junction(
            value = AppointmentJoinRef::class,
            parentColumn = "ID", // 关联表中对应父预约ID的字段
            entityColumn = "LINKED_ID" // 关联表中对应关联预约ID的字段
        )
    )
    val linkedAppointments: List<AppointmentModel>
)

通过指定Junction的两个列映射,Room就能正确查询到当前预约关联的其他预约,不会再返回自身重复数据。

3. 实现双向级联删除(删除预约时同步删除关联预约)

Room的外键级联仅能清理关联表记录,无法直接删除关联的预约实体。要实现删除1时自动删除2,可通过两种方式实现:

方式一:DAO层手动处理

在DAO中添加事务逻辑,先查询关联预约ID,再批量删除:

@Dao
interface AppointmentDao {
    @Transaction
    suspend fun deleteAppointmentAndLinked(appointmentId: Int) {
        // 查询当前预约的所有关联ID
        val linkedIds = getLinkedAppointmentIds(appointmentId)
        // 批量删除当前预约和关联预约
        deleteAppointments(listOf(appointmentId) + linkedIds)
    }

    // 若关联表仅存单向记录,用此查询获取双向关联ID
    @Query("SELECT CASE WHEN ID = :appointmentId THEN LINKED_ID ELSE ID END FROM AppointmentJoinRef WHERE ID = :appointmentId OR LINKED_ID = :appointmentId")
    suspend fun getLinkedAppointmentIds(appointmentId: Int): List<Int>

    @Query("DELETE FROM APPOINTMENTS WHERE ID IN (:ids)")
    suspend fun deleteAppointments(ids: List<Int>)
}

方式二:数据库触发器自动处理

通过Room的Callback添加数据库触发器,实现完全自动的级联删除:

val db = Room.databaseBuilder(context, AppDatabase::class.java, "app_db")
    .addCallback(object : RoomDatabase.Callback() {
        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            db.execSQL("""
                CREATE TRIGGER delete_linked_appointment AFTER DELETE ON APPOINTMENTS
                FOR EACH ROW
                BEGIN
                    DELETE FROM APPOINTMENTS WHERE ID IN (SELECT LINKED_ID FROM AppointmentJoinRef WHERE ID = OLD.ID);
                    DELETE FROM AppointmentJoinRef WHERE ID = OLD.ID OR LINKED_ID = OLD.ID;
                END;
            """)
        }
    })
    .build()

触发器会在删除任意预约后,自动删除其所有关联预约及对应的关联表记录。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 11:30:50