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

Android Room 如何实现单日关联多膳食、多饮水记录的表关系配置

Android Room 一对多关联数据库实现方案

Room是关系型数据库,不支持直接存储嵌套数组结构,需要将你预期的嵌套结构拆分为3张独立表,通过外键做关联,再通过Room提供的@Relation注解实现嵌套对象的自动映射,完全匹配你需要的返回结构。

1. 实体类定义(对应3张数据库表)

  • Day表实体:存储每日基础数据,对date字段加唯一约束,避免同一天生成重复记录
@Entity(
    tableName = "days",
    indices = [Index(value = ["date"], unique = true)]
)
data class Day(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    @ColumnInfo(name = "date") val date: String,
    @ColumnInfo(name = "weight") val weight: Float? = null,
    @ColumnInfo(name = "height") val height: Int? = null,
    @ColumnInfo(name = "waist_circumference") val waistCircumference: Int? = null,
    @ColumnInfo(name = "neck_circumference") val neckCircumference: Int? = null,
    @ColumnInfo(name = "hip_circumference") val hipCircumference: Int? = null,
    @ColumnInfo(name = "steps") val steps: Long = 0,
    @ColumnInfo(name = "taken_kcal") val takenKcal: Float = 0f,
    @ColumnInfo(name = "burned_kcal") val burnedKcal: Float = 0f,
    @ColumnInfo(name = "carb") val carb: Float = 0f,
    @ColumnInfo(name = "protein") val protein: Float = 0f,
    @ColumnInfo(name = "fat") val fat: Float = 0f,
    @ColumnInfo(name = "water_ml") val waterMl: Int = 0,
    @ColumnInfo(name = "water_percent") val waterPercent: Float = 0f
)
  • DailyMeal表实体:存储每日膳食记录,通过day_id外键关联Day表,配置级联删除规则,删除某天记录时自动删除对应膳食数据,外键字段必须加索引避免性能问题
@Entity(
    tableName = "meals",
    foreignKeys = [
        ForeignKey(
            entity = Day::class,
            parentColumns = ["id"],
            childColumns = ["day_id"],
            onDelete = ForeignKey.CASCADE
        )
    ],
    indices = [Index("day_id")]
)
data class DailyMeal(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    @ColumnInfo(name = "day_id") val dayId: Int,
    @ColumnInfo(name = "food_meal_time") val foodMealTime: String,
    @ColumnInfo(name = "food_name") val foodName: String,
    @ColumnInfo(name = "food_image") val foodImage: String,
    @ColumnInfo(name = "food_kcal") val foodKcal: Float,
    @ColumnInfo(name = "food_portion") val foodPortion: String,
    @ColumnInfo(name = "food_carb") val foodCarb: Float,
    @ColumnInfo(name = "food_protein") val foodProtein: Float,
    @ColumnInfo(name = "food_fat") val foodFat: Float
)
  • DailyWater表实体:存储每日饮水记录,外键关联规则和膳食表一致
@Entity(
    tableName = "water",
    foreignKeys = [
        ForeignKey(
            entity = Day::class,
            parentColumns = ["id"],
            childColumns = ["day_id"],
            onDelete = ForeignKey.CASCADE
        )
    ],
    indices = [Index("day_id")]
)
data class DailyWater(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    @ColumnInfo(name = "day_id") val dayId: Int,
    @ColumnInfo(name = "water_ml") val waterMl: Float,
    @ColumnInfo(name = "water_percent") val waterPercent: Float,
    @ColumnInfo(name = "water_time") val waterTime: Long
)

2. 定义关联映射类

单独定义关系映射类,通过@Embedded和@Relation注解实现自动关联查询,返回结构和你预期的嵌套JSON完全一致,不需要手动做数据转换:

data class DayWithRelations(
    @Embedded val day: Day,
    @Relation(
        parentColumn = "id",
        entityColumn = "day_id"
    )
    val meals: List<DailyMeal>,
    @Relation(
        parentColumn = "id",
        entityColumn = "day_id"
    )
    val water: List<DailyWater>
)

3. DAO接口定义

@Dao
interface HealthDao {
    // 插入Day记录,冲突策略为IGNORE,已存在同日记录时直接跳过,不会重复创建
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insertDayIfNotExist(day: Day)

    // 根据日期查询对应日期的全量关联数据,必须加@Transaction保证数据一致性
    @Transaction
    @Query("SELECT * FROM days WHERE date = :date")
    suspend fun getDayByDate(date: String): DayWithRelations?

    // 新增膳食记录
    @Insert
    suspend fun insertMeal(meal: DailyMeal)

    // 新增饮水记录
    @Insert
    suspend fun insertWater(water: DailyWater)

    // 更新Day表的统计字段(总摄入、总饮水量、步数等)
    @Update
    suspend fun updateDay(day: Day)
}

4. 业务逻辑实现

  • 应用启动自动创建当日记录:在应用启动协程中执行以下逻辑,重复启动也不会生成重复数据
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val todayStr = dateFormat.format(Date())
healthDao.insertDayIfNotExist(Day(date = todayStr))
  • 日期切换逻辑:用户切换日期时,先调用getDayByDate传入格式化后的目标日期字符串,如果返回null说明该日期无记录,先插入一条空的对应日期Day记录,再查询返回即可。
  • 新增膳食/饮水逻辑:获取当前选中日期对应的Day主键id,构造DailyMeal/DailyWater对象时将dayId赋值为该id,调用对应插入方法即可;插入完成后重新统计当日的总卡路里、三大营养素占比、总饮水量等数据,更新Day表对应统计字段。

注意事项

  • 日期存储必须统一格式,建议固定使用yyyy-MM-dd字符串格式,方便排序、比较和查询,不要混用不同格式。
  • 所有数据库操作必须在协程或后台线程执行,禁止在主线程直接调用,否则会触发Room的主线程访问异常。
  • 新增、删除膳食或饮水记录后,必须同步更新Day表的统计字段,保证首页展示的汇总数据准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 18:39:27