Room数据库一对一关联:Coin与CoinRevenue数据同步实现咨询
我有两个Room实体:Coin和CoinRevenue,Coin用来存储其他货币对应的USD价格,实体代码如下:
@Entity(tableName = "coin") data class Coin( @field:PrimaryKey(autoGenerate = false) var id: String = "", var symbol: String = "", var priceInUsd: Float = 0f )
CoinRevenue用来记录用户持有的对应货币数量,和Coin是严格的一对一关联,我最初写的实体代码是这样的:
@Entity(tableName = "coinRevenue") data class CoinRevenueNew( @field:PrimaryKey(autoGenerate = true) var id: Int = 0, var coin: Coin? = null, var amount: Float = 0f )
现在我的需求是:从数据库获取CoinRevenue时,能同步拿到数据库中最新的Coin对象。比如先保存一个价格为1.0253的EUR对应的Coin,再保存关联这个Coin的CoinRevenue;之后更新EUR的Coin价格为2.522,希望CoinRevenue里的Coin对象也能同步更新。
我知道@Embedded会把内部对象的字段合并到父表,但这样更新Coin的时候没法同步到CoinRevenue;而用@Relation的时候官方要求用List或Set,但这里是严格的一对一关联。
现有DAO代码如下:
// Coin相关DAO @Query("select * from coin order by rank") fun getAllCoins(): Flowable<List<Coin>> @Query("select * from coin where rank = 1") fun getFirstCoin(): Maybe<Coin> @Query("select * from coin where favourite = 1 order by rank") fun getAllFavouriteCoins(): Flowable<List<Coin>> @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertCoin(coin: Coin) @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertCoins(coinsList: List<Coin>) // CoinRevenue相关DAO @Query("select * from coinRevenue order by rank") fun getAllCoinsRevenue(): Flowable<List<CoinRevenue>> @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertCoinRevenue(coinRevenue: CoinRevenue) @Delete() fun deleteCoinRevenue(coinRevenue: CoinRevenue)
请问实现这个需求的最佳方式是什么?
嘿,我来帮你搞定这个问题!其实Room对一对一关联的支持很灵活,只是需要调整下实体定义和DAO的写法,就能完美实现你要的“获取CoinRevenue时同步拿到最新Coin数据”的需求,具体步骤如下:
第一步:修正实体定义,用外键替代直接嵌套对象
首先要改掉你原来在CoinRevenue里直接放Coin对象的写法——Room不会自动维护这种嵌套对象的同步,我们需要用外键来建立两个表的关联:
1.1 重构CoinRevenue实体
添加外键字段coinId,指向Coin表的主键id,同时定义外键约束(可选但推荐,保证数据完整性):
@Entity( tableName = "coinRevenue", foreignKeys = [ForeignKey( entity = Coin::class, parentColumns = ["id"], childColumns = ["coinId"], onDelete = ForeignKey.CASCADE // 可选,根据业务需求设置删除策略,比如删除Coin时自动删除关联的CoinRevenue )] ) data class CoinRevenue( @PrimaryKey(autoGenerate = true) var id: Int = 0, var coinId: String = "", // 外键,关联Coin表的id字段 var amount: Float = 0f )
1.2 创建关联查询的POJO
定义一个专门用来承载关联结果的数据类,用@Relation标记一对一的关联关系——虽然Room文档里@Relation常用集合,但一对一场景下直接返回单个Coin对象完全可行:
data class CoinRevenueWithCoin( @Embedded val coinRevenue: CoinRevenue, // 嵌入CoinRevenue的所有字段 @Relation( parentColumn = "coinId", // CoinRevenue的外键字段 entityColumn = "id" // Coin表的主键字段 ) val coin: Coin // 一对一关联的Coin对象 )
第二步:修改DAO,添加带事务的关联查询方法
在DAO里添加@Transaction注解的查询方法,Room会自动执行关联查询,并且保证查询的原子性,同时返回包含最新Coin数据的结果:
// 在你的DAO中新增以下方法 @Transaction @Query("SELECT * FROM coinRevenue ORDER BY rank") fun getAllCoinRevenueWithCoin(): Flowable<List<CoinRevenueWithCoin>> // 如果需要根据CoinId查询单个关联结果 @Transaction @Query("SELECT * FROM coinRevenue WHERE coinId = :coinId") fun getCoinRevenueWithCoin(coinId: String): Maybe<CoinRevenueWithCoin>
第三步:业务层使用方式
现在你就可以用新的DAO方法来获取数据了,而且当你更新Coin的价格后,再次查询拿到的CoinRevenueWithCoin里的coin对象一定是最新的——因为每次查询都是从Coin表实时拉取数据,而不是存在CoinRevenue表的旧数据:
举个例子:
- 插入EUR的
Coin:insertCoin(Coin(id="eur_123", symbol="EUR", priceInUsd=1.0253f)) - 插入关联的
CoinRevenue:insertCoinRevenue(CoinRevenue(coinId="eur_123", amount=100f)) - 更新EUR的价格:
insertCoin(Coin(id="eur_123", symbol="EUR", priceInUsd=2.522f)) - 获取关联数据:
getAllCoinRevenueWithCoin().subscribe { list -> list.firstOrNull()?.let { item -> // 这里item.coin.priceInUsd就是最新的2.522f } }
为什么这是最佳方案?
- 数据一致性:
Coin和CoinRevenue分表存储,更新Coin时不需要修改CoinRevenue表,查询时实时关联,彻底避免数据冗余和不一致的问题。 - 符合Room规范:用外键保证关联的完整性,
@Transaction保证查询的原子性,@Relation由Room自动处理关联逻辑,还会优化查询性能。 - 完美适配一对一需求:虽然Room文档里
@Relation示例多用集合,但一对一场景下直接返回单个实体是完全支持的,如果找不到关联的Coin,coin字段会是null,你可以根据业务需求处理这种情况。
额外注意事项
- 确保
Coin的id是唯一且稳定的,因为外键完全依赖这个字段来建立关联。 - 如果你用
Flowable来监听数据变化,当Coin或CoinRevenue表有任何更新时,Flowable会自动发射最新的数据,完美契合你要的实时同步需求。
内容的提问来源于stack exchange,提问作者Shahar

