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

如何使用Room实现含嵌套结构的JSON API响应缓存?

解决Room存储嵌套列表与复杂实体的方案

Room本身不支持直接存储List类型和嵌套实体,针对你的场景,我们可以分两种情况处理:


1. 处理简单字符串列表(如keywords):使用TypeConverter

Room允许通过**类型转换器(TypeConverter)**将自定义类型(比如List<String>)转换为它支持的基本类型(比如String),存储时转成JSON字符串,读取时再解析回列表。

实现TypeConverter

import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

class ListTypeConverter {
    private val gson = Gson()

    @TypeConverter
    fun fromStringList(list: List<String>): String {
        return gson.toJson(list)
    }

    @TypeConverter
    fun toStringList(json: String): List<String> {
        val type = object : TypeToken<List<String>>() {}.type
        return gson.fromJson(json, type)
    }
}

在Room数据库类中注册转换器

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters

@Database(entities = [AlbumEntity::class, PicEntity::class], version = 1)
@TypeConverters(ListTypeConverter::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun albumDao(): AlbumDao
}

2. 处理嵌套实体列表(如pics):使用一对多关系

PicResponse包含自己的列表(emojis),所以需要将它定义为独立的Room实体,通过外键关联到对应的AlbumEntity,再用@Relation注解在查询时关联两个实体。

定义PicEntity实体

import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey

@Entity(
    tableName = "pics",
    foreignKeys = [
        ForeignKey(
            entity = AlbumEntity::class,
            parentColumns = ["id"],
            childColumns = ["albumId"],
            onDelete = ForeignKey.CASCADE // 当Album被删除时,关联的Pic也自动删除
        )
    ]
)
data class PicEntity(
    @PrimaryKey(autoGenerate = true)
    val picId: Long = 0, // 给Pic加自增主键,避免picUrl重复导致的冲突
    val albumId: String, // 关联Album的id
    val picUrl: String,
    val emojis: List<String> // 已通过TypeConverter支持存储
)

修改AlbumEntity实体

去掉原来的pics字段,保留核心字段:

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "albums")
data class AlbumEntity(
    @PrimaryKey(autoGenerate = false)
    val id: String,
    val title: String,
    val createdBy: String,
    val enabled: Boolean,
    val keywords: List<String> // 已通过TypeConverter支持
)

创建关联查询的数据类

为了一次性查询Album和它对应的所有Pic,定义包含@Relation的数据类:

import androidx.room.Relation

data class AlbumWithPics(
    val album: AlbumEntity,
    @Relation(
        parentColumn = "id",
        entityColumn = "albumId"
    )
    val pics: List<PicEntity>
)

3. 实现DAO层

定义包含插入、查询操作的DAO,用@Transaction保证关联操作的原子性:

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction

@Dao
interface AlbumDao {
    @Transaction
    suspend fun insertAlbumWithPics(album: AlbumEntity, pics: List<PicEntity>) {
        insertAlbum(album)
        insertPics(pics)
    }

    @Insert
    suspend fun insertAlbum(album: AlbumEntity)

    @Insert
    suspend fun insertPics(pics: List<PicEntity>)

    @Transaction
    @Query("SELECT * FROM albums")
    suspend fun getAllAlbumsWithPics(): List<AlbumWithPics>

    @Transaction
    @Query("SELECT * FROM albums WHERE id = :albumId")
    suspend fun getAlbumWithPicsById(albumId: String): AlbumWithPics?
}

4. 数据转换:API响应 ↔ Room实体 ↔ 视图层数据类

在数据源层完成三次转换,隔离各层数据结构:

// API响应 → Room实体
fun AlbumResponse.toEntity(): AlbumEntity {
    return AlbumEntity(
        id = id,
        title = title,
        createdBy = createdBy,
        enabled = enabled,
        keywords = keywords
    )
}

fun PicResponse.toEntity(albumId: String): PicEntity {
    return PicEntity(
        albumId = albumId,
        picUrl = picUrl,
        emojis = emojis
    )
}

// Room查询结果 → 视图层纯数据类
fun AlbumWithPics.toDomain(): Album {
    return Album(
        id = album.id,
        title = album.title,
        createdBy = album.createdBy,
        enabled = album.enabled,
        keywords = album.keywords,
        pics = pics.map { it.toDomain() }
    )
}

fun PicEntity.toDomain(): Pic {
    return Pic(
        picUrl = picUrl,
        emojis = emojis
    )
}

关键注意事项

  • 若偏好kotlinx.serialization替代Gson,只需修改TypeConverter的序列化/反序列化逻辑即可。
  • 外键的onDelete策略可根据业务调整,比如RESTRICT可阻止删除关联了Pic的Album。
  • 插入关联数据必须用@Transaction,避免出现Album插入成功但Pic插入失败的不一致情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 07:45:37