基于一对一关系的 Room 数据库

14

我有两个实体,Coin和CoinRevenue。

基本上,coin保存了某种其他货币的美元价格。

例如,带有符号EUR且价值为1.0356的Coin。

@Entity(tableName = "coin")
data class Coin(
        @field:PrimaryKey(autoGenerate = false)
        var id: String = "",
        var symbol: String = "",
        var pricInUsd: Float = 0f)

CoinRevenue是我用来保存用户拥有特定货币数量的实体。 例如,CoinRevenue与具有EUR符号和1000数量的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。

例如,我保存了币种为(EUR,1.0253)的Coin,然后保存了一个带有该货币的CoinRevenue。

之后,我更新了具有(EUR,2.522)的Coin,我希望CoinRevenue内部的Coin对象也会更新。

我理解@Embedded只是将内部对象字段作为列添加到同一父对象中。当我使用关系时,我必须使用List或Set,但我始终只有一个Coin在CoinRevenue中。

我的coinDAO:

@Query("select * from coin order by rank")
fun getAllCoins(): Flowable<List<CoinDB>>

@Query("select * from coin where rank = 1")
fun getFirstCoin(): Maybe<CoinDB>

@Query("select * from coin where favourite = 1 order by rank")
fun getAllFavouriteCoins(): Flowable<List<CoinDB>>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCoin(coinDB: CoinDB)

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCoins(coinsList: List<CoinDB>)

// -----------------
// CoinRevenue
// -----------------

@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)

如何最好地创建这个?


你可以使用一个外键 https://android.jlelse.eu/android-architecture-components-room-relationships-bf473510c14a - Ahmed Hegazy
@AhmedHegazy 我看到了那篇帖子,但是我该如何查询以获取包含货币的coinRevenue? - Shahar
你可以使用@Relation注释 https://developer.android.com/reference/android/arch/persistence/room/Relation.html - Ahmed Hegazy
3
@AhmedHegazy,“@Relation” 对于一对一关系问题似乎不是合适的解决方案。文档说明了,使用“@Relation”注释的字段类型必须是列表或集合,因此与一对一关系不太匹配。在查看Yigit Boyar 的 Room介绍后,我认为最好的解决方案是创建第三个POJO类。 - hara
@hara 是的,你说得对 :+1: - Ahmed Hegazy
@hara,你能举个例子来说明如何实现第三个POJO吗? - Shahar
3个回答

16

经过多次尝试,我终于成功了。

我将CoinRevenue对象更改为持有对Coin id的外键。

@Entity(tableName = "coinRevenue", foreignKeys = (arrayOf(ForeignKey(
        entity = CoinDB::class,
        onUpdate = ForeignKey.CASCADE,
        parentColumns = arrayOf("coinId"),
        childColumns = arrayOf("coinDbId"))))
)
data class CoinRevenue(
        @ColumnInfo(name = "mid")
        @PrimaryKey(autoGenerate = true)
        var id: Long = 0L,
        @ColumnInfo(name = "coinDbId")
        var coinDbId: String? = null,
        @ColumnInfo(name = "amount")
        var amount: Double = 0.toDouble()
)

我需要创建一个POJO对象,其中包含这两个对象:

class CoinRevenueWithCoin() : Parcelable {
@Embedded lateinit var coinDB: CoinDB
@Embedded lateinit var coinRevenue: CoinRevenue
}

并且查询应该像这样:

@Query("select * from coinRevenue, coin where coinRevenue.coinDbId = coin.coinId order by coin.rank")
fun getAllCoinsRevenueWithCoin(): Flowable<List<CoinRevenueWithCoin>>

就这些。

此查询与任何其他常规对象查询一样,如果“coin”表或“coinRevenue”表发生任何更改,则会发出对象。


6

您的解决方案有几个主要缺点。其中之一是表格的列必须具有不同的名称。我建议使用@Relation而不是@embededed。

@Entity(tableName = "coin")
data class Coin(
        @field:PrimaryKey(autoGenerate = false)
        var id: String = "",
        var symbol: String = "",
        var pricInUsd: Float = 0f)

@Entity(tableName = "coinRevenue", foreignKeys = (arrayOf(ForeignKey(
        entity = CoinDB::class,
        onUpdate = ForeignKey.CASCADE,
        parentColumns = arrayOf("coinId"),
        childColumns = arrayOf("coinDbId"))))
)
data class CoinRevenue(
        @ColumnInfo(name = "mid")
        @PrimaryKey(autoGenerate = true)
        var id: Long = 0L,
        @ColumnInfo(name = "coinDbId")
        var coinDbId: String? = null,
        @ColumnInfo(name = "amount")
        var amount: Double = 0.toDouble()
) 

我不熟悉Kotlin,因此解决方案采用Java。

class CoinRevenueExt extends CoinRevenue {
        @Relation(parentColumn = "coinDbId", entityColumn = "coinId" ) 
        List<Coin> coins;

        public Coin getCoin() {
            return coins.get(0);
        }

}

而道就是这么简单
@Query("select * from coinRevenue")
public Flowable<List<CoinRevenueExt>> getAllCoinsRevenueWithCoin();

你能给我更多关于如何使用关系的信息吗?DAO很简单,但它包含哪些列?请向我解释得更详细一些。 - Junior Usca
@JuniorUsca 谷歌文档在描述关系方面非常好 https://developer.android.com/reference/android/arch/persistence/room/Relation - sim
你可以使用带有前缀的嵌套方式,而不必担心列名相同的问题。 - Leandro Ocampo
似乎使用前缀会导致此问题:https://dev59.com/ElYM5IYBdhLWcg3wrBjT?rq=1 - Leandro Ocampo

0

有点难以理解你到底想要实现什么。而且,你的命名有些奇怪。看起来,coin表格实际上包含了货币信息。coinRevenueNew是一个账簿条目或订单。如果你选择更易于理解的示例,更多人会尝试读完你的帖子。

此外,你要解决的问题有些不清楚。 - 你的问题是在数据库中对其进行建模吗? - 你的问题是当货币变化时想要自动更新所有数额吗? - 你的问题是当数据库发生更改时想要在内存中更新对象吗? - 你在使用Room时遇到了外键方面的问题吗?

建模方面的第一个问题已经被其他人提到过了。你使用了一个外键。有很多文章可以解释这个问题。

如果你使用一些更易于理解的域名,你将有如下两个表:

create table currency (id integer primary key, exchange_rate float);
create table order (id integer primary key, int total, int currency_id, foreign key(currency_id) references currency(id));

你需要为每个房间创建一个 Room 实体。你需要创建第三个类(不要使用 @Entity 标记它),将两个实体组合在一起。你可以在这里使用注释 @Embedded 或 @Relation。文档进一步解释了这一点:

https://developer.android.com/reference/androidx/room/Relation.html

如果您更改货币,存储系统不会自动更新所有订单总额。如果您有一个“total_in_foreign_currency”字段和一个“total_in_master_currency”字段,则数据库不会为您重新计算。您必须手动迭代每一行并重新计算它。

内存数据对象不会自动更新。您必须跟踪检索数据的时间以及当前是否仍在使用。您可以使用LiveData在数据更改时收到通知(但它不会自动更新您的对象)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接