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注解,手动分层查询后组装嵌套数据即可:
- 先查询当前用户的基础信息
- 用当前
userId查询用户关联的所有University - 遍历每个University的
universityId+userId查询对应的所有Semester - 遍历每个Semester的
semesterId+userId查询对应的所有Course - 用
userId查询用户关联的所有Event - 把以上数据按层级组装成自定义的嵌套数据类返回即可,自定义数据类无需添加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
相关产品推荐
相关产品推荐

