Cursor 操作 → Room DAO
老写法(Java + Cursor)
java
// 查询
Cursor cursor = db.rawQuery("SELECT * FROM item", null);
List<Item> items = new ArrayList<>();
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
items.add(new Item(id, name, price));
}
cursor.close();
// 统计
Cursor countCursor = db.rawQuery("SELECT COUNT(*) FROM item", null);
countCursor.moveToFirst();
int count = countCursor.getInt(0);
countCursor.close();
// 分页
Cursor pageCursor = db.rawQuery(
"SELECT * FROM item LIMIT ? OFFSET ?",
new String[]{String.valueOf(20), String.valueOf(offset)});
// ...遍历、关闭
问题在哪里
getColumnIndex 依赖字符串,字段名写错返回 -1,然后 getString(-1) 直接崩。每个查询都要手动遍历 Cursor、手动 close(),忘关就泄漏。LIMIT ? OFFSET ? 这种 SQL 拼接容易出错且可读性差。
新写法(Room DAO)
kotlin
@Dao
interface ItemDao {
// 基本查询
@Query("SELECT * FROM item")
suspend fun getAll(): List<Item>
// 按条件查询
@Query("SELECT * FROM item WHERE price > :minPrice")
suspend fun getByPrice(minPrice: Double): List<Item>
// 统计
@Query("SELECT COUNT(*) FROM item")
suspend fun count(): Int
// 分页 --- 用 PagingSource
@Query("SELECT * FROM item ORDER BY id ASC")
fun pagingSource(): PagingSource<Int, Item>
// 返回 Flow 自动监听数据变化
@Query("SELECT * FROM item ORDER BY name ASC")
fun getAllFlow(): Flow<List<Item>>
}
使用:
kotlin
// 普通查询
viewModelScope.launch(Dispatchers.IO) {
val items = db.itemDao().getAll()
val count = db.itemDao().count()
}
// Flow 自动更新
db.itemDao().getAllFlow()
.asLiveData()
.observe(viewLifecycleOwner) { items -> adapter.submitList(items) }
一句话注意
Room 的 @Query 在编译期验证 SQL 语法,表名或字段名写错了编译直接报错。这是 Room 相对手写 Cursor 最大的安全优势。
返回类型为 Flow 的查询,Room 会在数据变更时自动重新发出查询结果。底层是通过数据库的触发器实现的,不是轮询,所以不浪费性能。但注意 Flow 查询不支持 suspend,这两个互斥------要么一次性返回用 suspend,要么持续监听用 Flow。
cursor.getColumnIndex() 的失败模式是静默的(返回 -1),而 Room 的错误是编译期的,这也是迁移到 Room 最实在的好处。
Java Android 老项目迁移系列,持续更新中。