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

Room中不使用Relation实现User关联多层实体的一对多关系方案咨询

无@Relation注解的Room多层一对多关联实现方案

原有思路问题说明

你之前采用@Embedded嵌套实体的方案仅支持一对一关系,无法实现一对多关联:@Embedded注解会将嵌套类的字段平铺开存入当前实体的数据库表中,单个User表的一行数据只能存储一个University的字段值,无法满足一个用户对应多个University的需求。

核心实现思路

表结构设计

所有实体单独建表,每张表新增userId字段作为用户数据隔离的核心标识,同时添加上层关联实体的外键字段实现层级绑定:

  • User表:主键为userId,存储用户基础信息
  • University表:主键为universityId,新增userId字段关联所属用户,存储大学信息
  • Semester表:主键为semesterId,新增userId、universityId字段,分别关联所属用户和所属大学
  • Course表:主键为courseId,新增userId、semesterId字段,分别关联所属用户和所属学期
  • Event表:主键为eventId,新增userId字段关联所属用户,存储事件信息

实体类示例(Kotlin)

// User实体
@Entity(tableName = "users")
data class User(
    @PrimaryKey val userId: Long,
    val userName: String,
    // 其他用户自定义字段
)

// University实体
@Entity(
    tableName = "universities",
    foreignKeys = [ForeignKey(
        entity = User::class,
        parentColumns = ["userId"],
        childColumns = ["userId"],
        onDelete = ForeignKey.CASCADE
    )]
)
data class University(
    @PrimaryKey val universityId: Long,
    val userId: Long,
    val universityName: String,
    // 其他大学自定义字段
)

// Semester实体
@Entity(
    tableName = "semesters",
    foreignKeys = [
        ForeignKey(
            entity = User::class,
            parentColumns = ["userId"],
            childColumns = ["userId"],
            onDelete = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = University::class,
            parentColumns = ["universityId"],
            childColumns = ["universityId"],
            onDelete = ForeignKey.CASCADE
        )
    ]
)
data class Semester(
    @PrimaryKey val semesterId: Long,
    val userId: Long,
    val universityId: Long,
    val semesterName: String,
    // 其他学期自定义字段
)

// Course、Event实体同理,均添加userId字段和对应上层外键即可

数据隔离实现

所有操作强制绑定当前登录用户的userId即可实现数据完全隔离:

  • 新增操作:插入University、Semester、Course、Event实体时,必须赋值为当前登录用户的userId
  • 查询操作:所有查询方法都必须传入当前登录用户的userId作为查询条件,确保只会返回当前用户的关联数据

示例查询方法:

// 查询当前用户的所有大学
@Query("SELECT * FROM universities WHERE userId = :currentUserId")
suspend fun getUserUniversities(currentUserId: Long): List<University>

关联数据查询实现(无@Relation)

无需使用@Relation注解,手动分层查询后组装嵌套数据即可:

  1. 先查询当前用户的基础信息
  2. 用当前userId查询用户关联的所有University
  3. 遍历每个University的universityId+userId查询对应的所有Semester
  4. 遍历每个Semester的semesterId+userId查询对应的所有Course
  5. 用userId查询用户关联的所有Event
  6. 把以上数据按层级组装成自定义的嵌套数据类返回即可,自定义数据类无需添加Room注解

Dao设计方案

可选方案

  • 方案1:仅用一个UserDao即可满足需求,将所有实体的增删改查方法都定义在UserDao中,适合业务逻辑非常简单的场景
  • 方案2(更推荐):每个实体单独定义Dao,符合单一职责原则,后续扩展、维护更方便,比如修改大学的查询逻辑无需影响其他实体的代码

批量查询优化

如果需要一次性查询用户的全量关联数据,可在UserDao中添加带@Transaction注解的方法,避免多查询过程中数据发生变更导致的不一致问题:

@Dao
interface UserDao {
    @Transaction
    suspend fun getUserFullData(currentUserId: Long): UserFullData? {
        // 手动查询并组装数据
        val user = getUserById(currentUserId) ?: return null
        val universities = getUniversitiesByUserId(currentUserId).map { university ->
            val semesters = getSemestersByUniversityId(currentUserId, university.universityId).map { semester ->
                val courses = getCoursesBySemesterId(currentUserId, semester.semesterId)
                SemesterWithCourses(semester, courses)
            }
            UniversityWithSemesters(university, semesters)
        }
        val events = getEventsByUserId(currentUserId)
        return UserFullData(user, universities, events)
    }

    // 内部使用的基础查询方法
    @Query("SELECT * FROM users WHERE userId = :userId")
    suspend fun getUserById(userId: Long): User?

    @Query("SELECT * FROM universities WHERE userId = :userId")
    suspend fun getUniversitiesByUserId(userId: Long): List<University>

    @Query("SELECT * FROM semesters WHERE userId = :userId AND universityId = :universityId")
    suspend fun getSemestersByUniversityId(userId: Long, universityId: Long): List<Semester>

    @Query("SELECT * FROM courses WHERE userId = :userId AND semesterId = :semesterId")
    suspend fun getCoursesBySemesterId(userId: Long, semesterId: Long): List<Course>

    @Query("SELECT * FROM events WHERE userId = :userId")
    suspend fun getEventsByUserId(userId: Long): List<Event>
}

// 自定义嵌套返回类
data class UserFullData(
    val user: User,
    val universities: List<UniversityWithSemesters>,
    val events: List<Event>
)

data class UniversityWithSemesters(
    val university: University,
    val semesters: List<SemesterWithCourses>
)

data class SemesterWithCourses(
    val semester: Semester,
    val courses: List<Course>
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 04:06:04