Room DAO 核心方法:插入、查询待处理、更新状态、删除已完成、分页获取
4.1 本章概况
Room 的 DAO(Data Access Object)是持久化层的核心入口,它将 SQL 操作映射为类型安全的 Java/Kotlin 方法。本章围绕任务队列的典型需求,详细实现五个关键 DAO 方法:插入任务、查询待处理任务、更新任务状态、删除已完成任务以及分页获取任务列表。这些方法构成了 WorkManager 队列的持久化基础,保证数据一致性与高效存取。
4.2 原理:Room DAO 编译期校验与事务模型
Room 在编译时通过注解处理器生成 _Impl 类,将 DAO 接口方法自动转换为 SQLite 语句。其关键特性包括:
- 编译期 SQL 验证:错误的表名、列名或语法会在构建时直接报错,避免运行时崩溃。
- 事务自动管理:使用
@Transaction注解的方法会被包裹在单个数据库事务中,保证原子性。 - 响应式观察:返回
LiveData或Flow的方法会自动监听表变化,实时推送新数据。 - 分页支持:配合
PagingSource或@Query中的LIMIT/OFFSET实现高效分页。
✅ 核心原则:DAO 方法只关注数据操作,业务逻辑应放在 Repository 或 ViewModel 层,保持单一职责。
4.3 实现流程与代码示例
4.3.1 TaskEntity 定义
@Entity(tableName = "tasks")
data class TaskEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val description: String? = null,
val status: String = "pending", // pending | processing | completed | failed
val priority: Int = 0, // 越大越优先
val createdAt: Long = System.currentTimeMillis(),
val updatedAt: Long = System.currentTimeMillis()
)
4.3.2 TaskDao 接口——五核心方法
@Dao
interface TaskDao {
// 1️⃣ 插入任务(支持单个或批量)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTask(task: TaskEntity): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTasks(tasks: List<TaskEntity>): List<Long>
// 2️⃣ 查询待处理任务(按优先级排序,限制数量)
@Query("""
SELECT * FROM tasks
WHERE status = 'pending'
ORDER BY priority DESC, createdAt ASC
LIMIT :limit
""")
suspend fun getPendingTasks(limit: Int = 50): List<TaskEntity>
// 3️⃣ 更新任务状态
@Query("UPDATE tasks SET status = :newStatus, updatedAt = :updatedAt WHERE id = :taskId")
suspend fun updateTaskStatus(taskId: Long, newStatus: String, updatedAt: Long = System.currentTimeMillis())
// 4️⃣ 删除已完成任务(可根据创建时间删旧数据)
@Query("DELETE FROM tasks WHERE status = 'completed' AND createdAt < :beforeTimestamp")
suspend fun deleteCompletedTasks(beforeTimestamp: Long): Int
// 5️⃣ 分页获取所有任务(按创建时间降序)
@Query("SELECT * FROM tasks ORDER BY createdAt DESC LIMIT :pageSize OFFSET :offset")
suspend fun getTasksPaged(pageSize: Int, offset: Int): List<TaskEntity>
}
4.3.3 分页进阶:使用 Paging 3 库
对于大数据集,推荐使用 Room + Paging 3 集成,自动管理分页请求:
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks ORDER BY createdAt DESC")
fun getAllTasksPaged(): PagingSource<Int, TaskEntity>
}
调用端通过 Pager 配置 pageSize 即可获得 Flow<PagingData<TaskEntity>>,实现无感加载。
4.4 调试与日志验证
Room 提供 SQL 日志开关,方便开发阶段检查生成的 SQL 语句:
// 在 Application 或 Database 构建时开启(仅 debug 模式)
Room.databaseBuilder(context, AppDatabase::class.java, "queue_db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries() // 仅调试用
.setQueryCallback(object : RoomDatabase.QueryCallback {
override fun onQuery(sql: String, bindArgs: List<Any?>) {
Log.d("RoomSQL", "SQL: $sql, Args: $bindArgs")
}
})
.build()
常见调试检查点:
- 插入后检查返回的
id是否递增且非负。 - 查询待处理任务时确认
ORDER BY顺序正确,LIMIT生效。 - 更新状态后立刻查询同一 ID,验证字段已被修改。
- 删除后再次查询应返回空集或减少计数。
- 分页时测试第一页与第二页数据不重复、不遗漏。
4.5 常见错误与解决方案
| 错误场景 | 原因 | 解决方案 |
|---|---|---|
插入时抛 SQLiteConstraintException | 主键冲突或唯一约束违反 | 使用 @Insert(onConflict = OnConflictStrategy.REPLACE) 或 IGNORE |
| 查询返回空列表(实际有数据) | 条件 status = 'pending' 实际存储为 "Pending"(大小写敏感) | SQLite 默认大小写敏感;建议改用 COLLATE NOCASE 或统一枚举值 |
| 更新状态后 UI 不刷新 | 未使用 LiveData/Flow返回类型,或未调用 invalidate | 将 suspend 函数改为返回 LiveData<List<TaskEntity>> 或 Flow<List<TaskEntity>> |
| 分页数据重复 / 丢失 | OFFSET 在有新数据插入时失效 | 改用 PagingSource 基于游标(如 id)分页;或确保 ORDER BY 列稳定 |
| 删除已完成耗时过长 | 未加索引导致全表扫描 | 在 status、createdAt 列添加复合索引 |
⚠️ 注意:
suspend 函数必须在协程作用域中调用,不要在 UI 线程直接调用 Room 挂起方法,否则会触发 allowMainThreadQueries() 异常。
4.6 性能与稳定性优化
- 批量操作使用事务:多次插入/更新时用
@Transaction包裹,减少 I/O 次数。 - 索引策略:在
status、priority、createdAt上建立索引,加速筛选和排序。 - 分页参数调优:
pageSize推荐 20~100,过大占用内存,过小增加请求次数。 - 避免 N+1 查询:DAO 方法尽可能一次返回所需关联数据,而非循环调用。
- 数据库版本迁移:使用
Migration对象而非fallbackToDestructiveMigration(),避免生产数据丢失。
4.7 DAO 核心方法流程总览
下图展示了五个核心方法在 Room 层与 SQLite 之间的调用关系:
4.8 本章小结
通过本节的实现,我们已经建立了完整的任务持久化操作层:
- 插入支持单个与批量,冲突策略按业务选择。
- 查询待处理按优先级与时间排序,限制数量防止内存溢出。
- 更新状态单字段更新,轻量高效。
- 删除已完成结合时间戳做批量清理。
- 分页提供基础 OFFSET 方案与 Paging 3 进阶方案,适应不同数据规模。