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

Room框架下从关联表填充@Ignore字段的更优实现方案

问题背景

存在item和purchase两张关联数据表,purchase实体类包含被@Ignore标记的itemName字段,需要从item表获取对应itemName值填充该字段。
现有实现方案需要先从数据库读取全量item数据,再通过比对purchase.itemOwnerID与item.itemID完成字段赋值,全量加载商品数据存在明确性能隐患,现有实现代码如下:

现有UI层实现

LaunchedEffect(key1 = true) {
    sharedViewModel.requestAllPurchases()
    sharedViewModel.requestAllItems() // 全量请求所有商品数据
}

val allPurchases by sharedViewModel.allPurchases.collectAsState()
val allItems by sharedViewModel.allItems.collectAsState() // 收集全量商品数据

if (allPurchases is RequestState.Success && allItems is RequestState.Success) {
    (allPurchases as RequestState.Success<List<Purchase>>).data.forEach { purchase ->
        (allItems as RequestState.Success<List<Item>>).data.first { it.itemID == purchase.itemOwnerID } // 内存中比对关联
            .apply {
                purchase.itemName = itemName
                purchase.costPrice = salePrice
            }
    }
}

现有实体类定义

@Entity(tableName = "items", indices = [Index(value = ["itemName"], unique = true)])
data class Item(
    @PrimaryKey(autoGenerate = true)
    var itemID: Int,
    var categoryOwnerID: Int = 0,
    var itemName: String,
    var costPrice: Int,
    var salePrice: Int,
) {
    @Ignore
    var desiredQuantity: Int = 0
}

@Entity(tableName = "purchases")
data class Purchase(
    @PrimaryKey(autoGenerate = true)
    var purchaseID: Int,
    var itemOwnerID: Int,
    var quantity: Int,
    var soldPrice: Int,
) {
    @Ignore
    var itemName: String = ""
}

sealed class RequestState<out T> {
    object Idle : RequestState<Nothing>()
    object Loading : RequestState<Nothing>()
    data class Success<T>(val data: T) : RequestState<T>()
    data class Error(val error: Throwable) : RequestState<Nothing>()
}

现有Dao层实现

@Query("select * from items order by itemName asc")
fun getAllItems(): Flow<List<Item>>

@Query("select * from purchases order by purchaseID asc")
fun getAllPurchases(): Flow<List<Purchase>>

现有Repository层实现

val getAllItems: Flow<List<Item>> = itemDao.getAllItems()
val getAllPurchases: Flow<List<Purchase>> = itemDao.getAllPurchases()

现有SharedViewModel层实现

private var _allItems = MutableStateFlow<RequestState<List<Item>>>(RequestState.Idle)
val allItems: StateFlow<RequestState<List<Item>>> = _allItems

fun requestAllItems() {
    _allItems.value = RequestState.Loading
    try {
        viewModelScope.launch {
            repository.getAllItems.collect {
                _allItems.value = RequestState.Success(it)
            }
        }
    } catch (e: Exception) {
        _allItems.value = RequestState.Error(e)
    }
}

private var _allPurchases = MutableStateFlow<RequestState<List<Purchase>>>(RequestState.Idle)
val allPurchases: StateFlow<RequestState<List<Purchase>>> = _allPurchases

fun requestAllPurchases() {
    _allPurchases.value = RequestState.Loading
    try {
        viewModelScope.launch {
            repository.getAllPurchases.collect {
                _allPurchases.value = RequestState.Success(it)
            }
        }
    } catch (e: Exception) {
        _allPurchases.value = RequestState.Error(e)
    }
}
优化实现方案

核心思路是将关联逻辑下推到数据库层执行,避免全量加载商品数据到内存做比对,Room原生支持SQL JOIN查询,直接在查询阶段拼接需要的字段,性能远高于内存比对方案。

第一步:定义关联查询结果模型

不需要修改原有Item和Purchase实体类,新建专门的返回模型承载采购记录+关联的商品字段:

data class PurchaseWithItemInfo(
    // 采购记录原有字段
    val purchaseID: Int,
    val itemOwnerID: Int,
    val quantity: Int,
    val soldPrice: Int,
    // 关联查询从item表取的字段
    val itemName: String,
    val costPrice: Int
)

第二步:Dao层新增JOIN查询

直接在SQL中做左连接,一次性查出需要的所有数据,不需要单独查询全量商品:

@Query("""
    SELECT p.*, i.itemName, i.salePrice as costPrice 
    FROM purchases p 
    LEFT JOIN items i ON p.itemOwnerID = i.itemID
    ORDER BY p.purchaseID ASC
""")
fun getAllPurchasesWithItemInfo(): Flow<List<PurchaseWithItemInfo>>

第三步:调整Repository层

新增对应查询的暴露:

val getAllPurchasesWithItemInfo: Flow<List<PurchaseWithItemInfo>> = itemDao.getAllPurchasesWithItemInfo()

第四步:调整ViewModel层

删除原来单独请求全量商品的逻辑,只需要监听关联查询结果即可:

private var _allPurchasesWithInfo = MutableStateFlow<RequestState<List<PurchaseWithItemInfo>>>(RequestState.Idle)
val allPurchasesWithInfo: StateFlow<RequestState<List<PurchaseWithItemInfo>>> = _allPurchasesWithInfo

fun requestAllPurchases() {
    _allPurchasesWithInfo.value = RequestState.Loading
    try {
        viewModelScope.launch {
            repository.getAllPurchasesWithItemInfo.collect {
                _allPurchasesWithInfo.value = RequestState.Success(it)
            }
        }
    } catch (e: Exception) {
        _allPurchasesWithInfo.value = RequestState.Error(e)
    }
}

第五步:简化UI层逻辑

删除原来收集全量商品、内存比对赋值的代码,直接使用关联查询返回的结果即可:

LaunchedEffect(key1 = true) {
    sharedViewModel.requestAllPurchases()
}

val allPurchasesState by sharedViewModel.allPurchasesWithInfo.collectAsState()

when(allPurchasesState) {
    is RequestState.Loading -> { /* 展示加载态 */ }
    is RequestState.Error -> { /* 展示错误态 */ }
    is RequestState.Success -> {
        val purchaseList = (allPurchasesState as RequestState.Success<List<PurchaseWithItemInfo>>).data
        // 直接使用purchaseList中的itemName、costPrice字段渲染UI,无需额外赋值
    }
    RequestState.Idle -> {}
}

方案优势

  • 不需要全量加载item表所有数据,数据库仅返回采购记录关联到的商品字段,传输和加载的数据量大幅降低
  • 关联逻辑在SQLite底层执行,比上层内存循环比对效率高1~2个数量级,表数据量越大性能差距越明显
  • 移除了UI层手动遍历赋值的逻辑,不会出现商品信息更新后采购记录关联字段不同步的问题
  • 精简了数据流链路,去掉冗余的全量商品数据收集逻辑,代码可维护性更高

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 16:27:25