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

Room框架中带自增主键表关联的DAO查询方法实现问询

关联带主键的User与Department表查询DAO实现方案

我接触到的多是无主键表的关联实现示例,但我的数据库两张表都带主键,想写一个DAO方法关联User和Department表查询记录。核心需求是尽可能不创建额外数据类,比如用Map<User, Department>返回;如果这个方案不可行,其他办法也可以接受。


数据库表结构与实体类

User表

+----+----------+----------+---------------+
| id | username |   name   | department_id |
+----+----------+----------+---------------+
|  1 | johndoe  | John Doe |             3 |
|  2 | janedoe  | Jane Doe |             4 |
+----+----------+----------+---------------+

User实体类

@Entity(tableName = "user")
data class User(
    @PrimaryKey (autoGenerate = true) var id: Long,
    var username: String,
    var name: String,
    var department_id: Long)

Department表

+----+----------------+
| id |      name      |
+----+----------------+
|  1 | Sales          |
|  2 | Account        |
|  3 | Human Resource |
|  4 | Marketing      |
+----+----------------+

Department实体类

@Entity(tableName = "department")
data class Department(
    @PrimaryKey(autoGenerate = true) var id: Long,
    var name: String)

我的DAO尝试

@Dao
interface UserDAO {

    @Query("SELECT user.*, department.name AS 'department_name' FROM user " +
            "INNER JOIN department ON user.department_id = department.id " +
            "WHERE user.id = :id")
    fun findById(id: Long): Map<User, Department>
}

补充疑问

如果把Department嵌入User类中是否可行?如果可以,对应的DAO查询语句该怎么写?

修改后的User和Department实体类:

@Entity(tableName = "user")
data class User(
    @PrimaryKey (autoGenerate = true) var id: Long,
    var username: String,
    var name: String,
    @Embedded var department: Department)

data class Department(
    @PrimaryKey(autoGenerate = true)
    var id: Long,
    var name: String? = null): Parcelable

对应的DAO尝试:

@Query("SELECT * FROM user " +
    "INNER JOIN department d ON department.id = d.id " +
    "WHERE id = :id")
fun findById(id: Long): Map<User, Department>

可行解决方案

1. 直接返回Map不可行的原因

Room不支持直接返回Map<User, Department>这种类型,因为Room的返回类型只能是实体类、POJO、基本类型,或是它们的列表/数组,无法直接将查询结果映射为实体类作为键的Map结构。

2. 手动组装Map(无额外数据类)

通过两次查询或批量查询后,在业务层手动组装Map:

@Dao
interface UserDAO {
    // 查询单个用户
    @Query("SELECT * FROM user WHERE id = :id")
    suspend fun getUserById(id: Long): User

    // 根据部门ID查询部门
    @Query("SELECT * FROM department WHERE id = :deptId")
    suspend fun getDepartmentById(deptId: Long): Department

    // 查询所有用户
    @Query("SELECT * FROM user")
    suspend fun getAllUsers(): List<User>

    // 批量查询部门
    @Query("SELECT * FROM department WHERE id IN (:deptIds)")
    suspend fun getDepartmentsByIds(deptIds: List<Long>): List<Department>
}

在Repository层组装:

// 单个用户的Map组装
suspend fun getUserWithDepartment(userId: Long): Map<User, Department> {
    val user = userDao.getUserById(userId)
    val dept = userDao.getDepartmentById(user.department_id)
    return mapOf(user to dept)
}

// 多个用户的Map组装
suspend fun getAllUsersWithDepartments(): Map<User, Department> {
    val users = userDao.getAllUsers()
    val deptIds = users.map { it.department_id }
    val deptMap = userDao.getDepartmentsByIds(deptIds).associateBy { it.id }
    return users.associateWith { deptMap[it.department_id]!! }
}

3. 使用Room官方推荐的@Relation关联(简洁型)

虽然需要创建一个额外的包装类,但这是Room最规范的关联查询方式,类型安全且无需手动处理关联逻辑:

// 创建包装类
data class UserWithDepartment(
    @Embedded val user: User,
    @Relation(
        parentColumn = "department_id",
        entityColumn = "id"
    )
    val department: Department
)

对应的DAO方法:

@Dao
interface UserDAO {
    @Transaction
    @Query("SELECT * FROM user WHERE id = :id")
    fun getUserWithDepartment(id: Long): UserWithDepartment

    @Transaction
    @Query("SELECT * FROM user")
    fun getAllUsersWithDepartments(): List<UserWithDepartment>
}

4. 关于嵌入Department到User类的可行性

你尝试的@Embedded方式不可行:@Embedded的作用是将一个实体的字段直接嵌入到当前实体的数据库表中,意味着User表需要包含Department的所有字段(id、name),但你的数据库中User表只有department_id字段,与Department表是独立的关联关系,因此这种嵌入方式不符合现有数据库结构,会导致Room找不到对应列而报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 11:31:00