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

ROOM数据库数据过滤实现是否高效?求最优方案

基于Room的待办APP过滤逻辑效率分析与优化建议

问题背景

我开发了一款基于ROOM数据库的待办事项列表APP,支持按标题、优先级或描述过滤列表。想了解ViewModel中实现的过滤逻辑是否高效?若存在优化空间,最优实现方式是什么?

当前实现代码

DAO层

@Dao
interface ToDoDao {

    @Insert(ToDoData::class, onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(toDoData: ToDoData)

    @Delete(ToDoData::class)
    suspend fun delete(item: ToDoData)

    @Update(ToDoData::class,onConflict = OnConflictStrategy.IGNORE)
    suspend fun update(item: ToDoData)

    @Query("DELETE FROM TODO_TABLE")
    suspend fun deleteAllData()

    @Query("SELECT * FROM TODO_TABLE ORDER BY id ASC ")
    fun getAllData(): LiveData<List<ToDoData>>

    @Query("SELECT * FROM TODO_TABLE ORDER BY CASE WHEN priority LIKE 'H%' THEN 1 WHEN priority LIKE 'M%' THEN 2 WHEN priority LIKE 'L%' THEN 3 END")
    fun getAllDataSortedByHighPriority(): LiveData<List<ToDoData>>

    @Query("SELECT * FROM TODO_TABLE ORDER BY CASE WHEN priority LIKE 'L%' THEN 1 WHEN priority LIKE 'M%' THEN 2 WHEN priority LIKE 'H%' THEN 3 END")
    fun getAllDataSortedByLowPriority(): LiveData<List<ToDoData>>

    @Query("SELECT * FROM TODO_TABLE WHERE title LIKE '%' || :titleContains || '%' AND priority IN (:prioritiesIn) AND description LIKE '%' || :descContains || '%' ORDER BY ID ASC")
    fun filterDataSortedByID(titleContains: String, prioritiesIn:Array<Priority>,descContains:String): LiveData<List<ToDoData>>
}

Repository层

class ToDoRepository(private val dao: ToDoDao) {

    val allData: LiveData<List<ToDoData>> = dao.getAllData()
    val allDataSortedByHighPriority = dao.getAllDataSortedByHighPriority()
    val allDataSortedByLowPriority = dao.getAllDataSortedByLowPriority()

    suspend fun insert(item: ToDoData) {
        dao.insert(item)
    }

    suspend fun delete(item: ToDoData) {
        dao.delete(item)
    }

    suspend fun update(item: ToDoData) {
        dao.update(item)
    }

    suspend fun deleteAll(){
        dao.deleteAllData()
    }

    fun filterDataSortedByID(titleContains: String, prioritiesIn:Array<Priority>,descContains:String): LiveData<List<ToDoData>>{
        return dao.filterDataSortedByID(titleContains,prioritiesIn,descContains)
    }
}

ViewModel层

class ToDoViewModel(context: Application) : AndroidViewModel(context) {

    private val toDoDao = ToDoDatabase.getDataBase(context.applicationContext).toDoDao()
    private val repository = ToDoRepository(toDoDao)

    private val filters: MutableLiveData<MutableMap<String, Any>> =
        MutableLiveData(
            mutableMapOf(
                "titleContains" to "",
                "prioritiesIn" to arrayOf(Priority.HIGH, Priority.MEDIUM, Priority.LOW),
                "descContains" to "",
            )
        )

    var titleContains: String
        get() = filters.value?.get("titleContains") as String
        set(value) {
            filters.value = filters.value?.apply {
                set("titleContains", value)
            }
        }
    var prioritiesIn: Array<Priority>
        get() = filters.value?.get("prioritiesIn") as Array<Priority>
        set(value) {
            filters.value = filters.value?.apply {
                set("prioritiesIn", value)
            }
        }
    var descContains: String
        get() = filters.value?.get("descContains") as String
        set(value) {
            filters.value = filters.value?.apply {
                set("descContains", value)
            }
        }

    val filteredData = filters.switchMap { _ ->
        if (titleContains.isEmpty() && descContains.isEmpty()) {
            repository.allData
        } else {
            repository.filterDataSortedByID(titleContains, prioritiesIn, descContains)
        }
    }

    fun insertData(item: ToDoData) {
        viewModelScope.launch {
            repository.insert(item)
        }
    }

    fun updateData(item: ToDoData) {
        viewModelScope.launch {
            repository.update(item)
        }
    }

    fun deleteData(item: ToDoData) {
        viewModelScope.launch {
            repository.delete(item)
        }
    }

    fun deleteAll() {
        viewModelScope.launch {
            repository.deleteAll()
        }
    }
}

当前逻辑的优缺点

值得肯定的地方

  • 过滤逻辑放在数据库层处理,利用SQLite的查询优化能力,比内存中过滤更高效,数据量大时优势明显。
  • 用switchMap监听过滤器变化,自动触发查询更新,符合LiveData响应式设计思路。
  • 增加了简单判断:标题和描述过滤条件为空时直接返回全量数据,避免不必要的带条件查询。

可以优化的点

  1. 过滤器状态管理不安全:用MutableMap<String, Any>存储过滤条件,每次取值都要强制类型转换,容易出现类型错误,代码可读性差。
  2. 优先级过滤有语法风险:若用户取消所有优先级选择,prioritiesIn会是空数组,此时SQL中的IN ()会直接报错。
  3. SQL查询性能有优化空间:
    • LIKE '%xxx%'前后通配符查询无法利用索引,数据量大时查询速度变慢。
    • 优先级排序用LIKE 'H%'匹配,效率低——如果priority是枚举类型,直接匹配枚举值更快。
  4. 过滤器更新冗余:每次修改单个过滤条件都要重新创建整个Map对象,可更高效地处理状态更新。
  5. 过滤与排序未结合:当前filteredData仅支持按ID排序,无法让用户选择过滤后按优先级排序(如高优先在前)。

优化后的实现方案

1. 用数据类重构过滤器状态

用强类型数据类替代Map,类型安全且可读性更高:

// 定义过滤条件数据类
data class TodoFilters(
    val titleContains: String = "",
    val selectedPriorities: Set<Priority> = setOf(Priority.HIGH, Priority.MEDIUM, Priority.LOW),
    val descContains: String = ""
)

// ViewModel中替换过滤器定义
private val filters = MutableLiveData(TodoFilters())

// 简化属性访问,无需强制类型转换
var titleContains: String
    get() = filters.value?.titleContains ?: ""
    set(value) {
        filters.value = filters.value?.copy(titleContains = value) ?: TodoFilters(titleContains = value)
    }

var selectedPriorities: Set<Priority>
    get() = filters.value?.selectedPriorities ?: emptySet()
    set(value) {
        filters.value = filters.value?.copy(selectedPriorities = value) ?: TodoFilters(selectedPriorities = value)
    }

var descContains: String
    get() = filters.value?.descContains ?: ""
    set(value) {
        filters.value = filters.value?.copy(descContains = value) ?: TodoFilters(descContains = value)
    }

2. 修复SQL查询问题

处理空优先级集合

修改DAO过滤查询,优先级集合为空时返回空结果,避免SQL语法错误:

@Query("""
    SELECT * FROM TODO_TABLE 
    WHERE (:titleEmpty = 1 OR title LIKE '%' || :titleContains || '%')
    AND (:prioritiesEmpty = 1 OR priority IN (:selectedPriorities))
    AND (:descEmpty = 1 OR description LIKE '%' || :descContains || '%')
    ORDER BY ID ASC
""")
fun filterData(
    titleContains: String,
    titleEmpty: Int,
    selectedPriorities: Array<Priority>,
    prioritiesEmpty: Int,
    descContains: String,
    descEmpty: Int
): LiveData<List<ToDoData>>

Repository中调用时传入空判断参数:

fun filterData(filters: TodoFilters): LiveData<List<ToDoData>> {
    val titleEmpty = if (filters.titleContains.isEmpty()) 1 else 0
    val prioritiesEmpty = if (filters.selectedPriorities.isEmpty()) 1 else 0
    val descEmpty = if (filters.descContains.isEmpty()) 1 else 0
    return dao.filterData(
        filters.titleContains,
        titleEmpty,
        filters.selectedPriorities.toTypedArray(),
        prioritiesEmpty,
        filters.descContains,
        descEmpty
    )
}

优化优先级排序查询

若Priority是枚举类型,直接匹配枚举值替代LIKE:

@Query("""
    SELECT * FROM TODO_TABLE 
    ORDER BY CASE priority 
        WHEN :high THEN 1 
        WHEN :medium THEN 2 
        WHEN :low THEN 3 
    END ASC
""")
fun getAllDataSortedByHighPriority(
    high: Priority = Priority.HIGH,
    medium: Priority = Priority.MEDIUM,
    low: Priority = Priority.LOW
): LiveData<List<ToDoData>>

模糊查询性能优化(可选)

提升模糊查询速度可选择:

  • 给title和description添加全文索引,用MATCH替代LIKE(SQLite支持全文搜索)。
  • 引导用户使用前缀搜索(如'xxx%'),普通索引即可生效。

3. 结合过滤与排序

扩展过滤查询,支持传入排序参数,实现过滤后按优先级或ID排序:

@Query("""
    SELECT * FROM TODO_TABLE 
    WHERE (:titleEmpty = 1 OR title LIKE '%' || :titleContains || '%')
    AND (:prioritiesEmpty = 1 OR priority IN (:selectedPriorities))
    AND (:descEmpty = 1 OR description LIKE '%' || :descContains || '%')
    ORDER BY 
        CASE WHEN :sortByPriority = 1 THEN 
            CASE priority WHEN :high THEN 1 WHEN :medium THEN 2 WHEN :low THEN 3 END 
        ELSE id END
        ASC
""")
fun filterDataWithSort(
    titleContains: String,
    titleEmpty: Int,
    selectedPriorities: Array<Priority>,
    prioritiesEmpty: Int,
    descContains: String,
    descEmpty: Int,
    sortByPriority: Int,
    high: Priority = Priority.HIGH,
    medium: Priority = Priority.MEDIUM,
    low: Priority = Priority.LOW
): LiveData<List<ToDoData>>

4. 优化ViewModel的switchMap逻辑

直接使用filters当前值,避免重复访问属性,减少空指针风险:

val filteredData = filters.switchMap { currentFilters ->
    // 所有过滤条件为默认值时返回全量数据
    val isDefault = currentFilters.titleContains.isEmpty() &&
            currentFilters.descContains.isEmpty() &&
            currentFilters.selectedPriorities == setOf(Priority.HIGH, Priority.MEDIUM, Priority.LOW)
    
    if (isDefault) {
        repository.allData
    } else {
        repository.filterData(currentFilters)
    }
}

5. 可选:用Flow替代LiveData

若项目使用Kotlin协程,推荐用Flow替代LiveData,性能更好且支持更多操作符:

// DAO中返回Flow
@Query(...)
fun filterData(...): Flow<List<ToDoData>>

// Repository中返回Flow
fun filterData(filters: TodoFilters): Flow<List<ToDoData>> = dao.filterData(...)

// ViewModel中转换为LiveData(或直接用Flow收集)
val filteredData = filters.asFlow()
    .flatMapLatest { currentFilters ->
        val isDefault = currentFilters.titleContains.isEmpty() &&
                currentFilters.descContains.isEmpty() &&
                currentFilters.selectedPriorities == setOf(Priority.HIGH, Priority.MEDIUM, Priority.LOW)
        
        if (isDefault) repository.allData.asFlow() else repository.filterData(currentFilters)
    }
    .asLiveData(viewModelScope.coroutineContext)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 02:45:54