Room DAO 层编写:任务持久化核心操作

DAO(Data Access Object)是 Room 持久化层与业务逻辑层之间的桥梁,它通过编译时生成的实现类将 Kotlin/Java 方法调用映射为 SQL 语句。本章按课程需求实现五个核心方法:插入任务、按状态查询任务、更新任务状态、删除任务、获取待恢复任务列表。下面先从接口定义出发,再逐一讲解每个方法的原理、注解用法、返回值类型,最后给出完整的 DAO 接口代码和调用示例。

DAO 方法一览

方法名 SQL 语义 Room 注解 返回值类型 说明
insertTask INSERT INTO task @Insert Long(rowId) 插入一条新任务,返回自动生成的主键 ID
queryTasksByStatus SELECT * FROM task WHERE status = :status @Query List<Task> 或 LiveData<List<Task>> 按指定状态查询任务列表
updateTaskStatus UPDATE task SET status = :newStatus WHERE id = :taskId @Query int(影响行数) 更新单个任务的状态
deleteTask DELETE FROM task WHERE id = :taskId @Query int(影响行数) 根据主键删除任务
getPendingTasks SELECT * FROM task WHERE status IN ('QUEUED','RETRYING') ORDER BY priority DESC @Query List<Task> 获取需要恢复的待处理任务列表,按优先级降序排列

实现流程

  1. 定义任务实体 TaskEntity(已在第 3 章完成)。
  2. 创建 DAO 接口 TaskDao,标记 @Dao 注解。
  3. 在 AppDatabase 的抽象方法中暴露 taskDao()。
  4. 在 Repository 或 ViewModel 中调用 DAO 方法。

完整 DAO 接口代码

@Dao
interface TaskDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertTask(task: TaskEntity): Long

    @Query("""
        SELECT * FROM task 
        WHERE status = :status 
        ORDER BY created_at DESC
    """)
    fun queryTasksByStatus(status: String): LiveData<List<TaskEntity>>

    @Query("UPDATE task SET status = :newStatus WHERE id = :taskId")
    suspend fun updateTaskStatus(taskId: Long, newStatus: String): Int

    @Query("DELETE FROM task WHERE id = :taskId")
    suspend fun deleteTask(taskId: Long): Int

    @Query("""
        SELECT * FROM task 
        WHERE status IN ('QUEUED', 'RETRYING') 
        ORDER BY priority DESC, created_at ASC
    """)
    suspend fun getPendingTasks(): List<TaskEntity>
}

关键注解与实现原理

  • @Insert:Room 自动生成 INSERT 语句。当 onConflict = REPLACE 时,如果主键冲突则替换整行(适用于幂等插入)。返回值 Long 为插入行的 rowId。
  • @Query:用于执行自定义 SQL。编译时校验语法,参数通过 :paramName 绑定。返回 LiveData 时,Room 会在数据表变化时自动通知观察者——这是实现任务恢复响应式更新的基础。
  • 挂起函数:DAO 方法声明为 suspend,确保在协程作用域内执行,避免阻塞主线程。
  • 排序策略:getPendingTasks 按优先级降序、创建时间升序排序,让高优先级任务优先恢复。
提示: 若项目使用 Kotlin Flow,可直接将返回类型改为 Flow<List<TaskEntity>>,Room 同样支持 Flow 的响应式查询。这在 WorkManager 恢复流程中可用于实时监听待处理任务的变化。

调试与常见错误

错误现象 可能原因 解决方案
编译错误:Not sure how to convert Cursor to... @Query 返回类型与查询列不匹配 确认 SELECT 字段数与实体构造函数参数一致;若只返回部分字段,使用 @ColumnInfo 或定义 POJO
插入时主键冲突 未指定 onConflict 策略 在 @Insert 中设置 onConflict = OnConflictStrategy.REPLACE 或 IGNORE
SQL 语法错误 SQL 中表名/字段名拼写错误或未用反引号包裹保留字 检查 TaskEntity 中 @Entity(tableName = "task") 对应表名;给列名加反引号 `status`
挂起函数在非协程中调用 ViewModel 中直接调用 suspend 方法未启动协程 使用 viewModelScope.launch { dao.insertTask(...) } 或 lifecycleScope.launch

性能与稳定性优化

  • 批量插入:如需一次插入多个任务,使用 @Insert 接受 List<TaskEntity>,Room 会自动生成事务执行批量插入,效率远高于循环单条插入。
  • 索引优化:在 status 和 priority 列上创建联合索引,可大幅提升 getPendingTasks 查询性能:
@Entity(
    tableName = "task",
    indices = [
        Index(value = ["status", "priority"])
    ]
)
data class TaskEntity(...)
  • 事务保证:若需要在一个原子操作中更新状态并插入日志,使用 @Transaction 注解标记 DAO 方法,Room 会将其包裹在数据库事务中。
  • 避免大对象查询:当待恢复任务列表中包含大量 Blob 字段(如序列化的 WorkSpec)时,考虑使用只返回 ID 和状态的轻量查询,再按需加载完整对象。

DAO 层架构示意

下图展示了 Room DAO 在整体持久化架构中的位置,以及本课程实现的五个方法如何参与任务恢复流程:

Room DAO 层与任务恢复方法关系图 Room DAO 层架构 — 5 个核心持久化方法 ViewModel / Repository 调用 DAO 方法 TaskDao @Dao 接口 编译时生成实现 AppDatabase 提供 DAO 实例 insertTask @Insert → Long queryTasksByStatus @Query → LiveData updateTaskStatus @Query → Int deleteTask @Query → Int getPendingTasks @Query → List 多个方法实现 方法职责与 WorkManager 恢复的关联 • insertTask:WorkManager 启动 Worker 时,将 WorkSpec 转换为 TaskEntity 并持久化。 • queryTasksByStatus:用于 UI 层按状态(PENDING/RUNNING/SUCCESS/FAILED)展示任务列表。 • updateTaskStatus:Worker 执行期间多次调用,更新进度/状态以支持断点续传。 • deleteTask:任务完成或取消后清理记录,保持数据库轻量。 • getPendingTasks:应用启动时由 WorkManager Initializer 调用,获取 QUEUED/RETRYING 任务列表触发恢复。
实践建议: 在实现 getPendingTasks 时,可以额外添加一个参数 maxCount: Int 限制每次恢复的任务数量,避免一次性加载过多任务导致内存压力。例如 LIMIT :maxCount。
注意: Room 不允许在主线程访问数据库(除非调用 allowMainThreadQueries(),但强烈不推荐生产环境使用)。所有 DAO 调用务必在协程或 ListenableFuture 中执行,否则会抛出 IllegalStateException。