如何在 Room 中声明 NUMERIC 字段

Posted

技术标签:

【中文标题】如何在 Room 中声明 NUMERIC 字段【英文标题】:How to declare NUMERIC filed in Room 【发布时间】:2021-09-23 04:12:19 【问题描述】:

我有实体:

data class DropsDeposit(
    val userId: Int,
    val articleId: Int,
    val quantity: BigDecimal
)

我的问题是 quantity 字段类型。我使用的是双精度类型,但在这种情况下,我在转换为浮点数时遇到了问题。 所以,a 使用了 BigDecimal,我的问题就消失了。

不幸的是,现在是另一个问题。当我尝试获取该值时,我只得到 6 位数字。 我还有用于从 String 到 BigDecimal 的自定义转换的类型转换器

class BigDecimalTypeConverter 
    @TypeConverter
    fun toBigDecimal(number: String): BigDecimal 
        Timber.e("BigDecimalTypeConverter numberStr: $number")
        return BigDecimal(number, MathContext.UNLIMITED)
    

    @TypeConverter
    fun fromBigDecimal(number: BigDecimal): String 
        return number.toPlainString()
    

例如

当列数有444.1234567值时,对象值对应的值为444.123

当列数量有0.0004567值时,对象值对应的值为0.0004567

Timber 的日志显示从数据库中获取的值是错误的(就像上面的示例一样)

我想将字段的亲和力设置为 NUMERIC @ColumnInfo(typeAffinity = ColumnInfo.NUMERIC) 但此亲和类型不可用。

有什么办法解决吗?

【问题讨论】:

数量的最大尺寸是多少? @Karol 可能是因为您在类型转换器中使用了 toPlainString() 如果您只使用 toString() 怎么样? @ChinthakaDevinda 问题是从数据库读取而不是转换。 number错了 @Eishon 它没有定义,但我认为它大约是 10000。但更重要的是保持正确的转换,所以 double/float 不是正确的解决方案 数量描述了医院的药物剂量,而to通常是一个分数 【参考方案1】:

我想将字段关联设置为 NUMERIC @ColumnInfo(typeAffinity = ColumnInfo.NUMERIC) 但此关联类型不可用。

那是因为 Room 不支持 NUMERIC,它无法解决问题。 NUMERIC 是一个捕获所有类型的关联。最适合的亲和力是 REAL ,它会使用 Float 或 Double 。虽然归根结底,亲和力并没有真正发挥作用,因为 SQLite 可以在任何列中存储任何类型,但重要的是插入数据时生成的 SQL,您几乎无法控制。

基于稍微修改的 DropsDeposit 实体考虑以下内容:-

@Entity
data class DropsDeposit(
    @PrimaryKey
    val userId: Int? = null,
    val articleId: Int = 0,
    val quantity: Double,
    val quantity2: Float
)

演示 Float 和 Double(这是您最终将数据存储为的内容)

另一种选择是将值存储为使用非数字字符的字符串,以防止 SQLite 将其存储为数字值,例如#4444.123456789

标准插入和提取 Dao 的存在(并且为了可重复性删除所有 Dao)。

使用以下代码:-

    db = TheDatabase.getInstance(this)
    dao = db.getAllDao()
    dao.deleteAllDropsDeposit()
    val dec = DecimalFormat("#.0000000000")
    var q: Double = 4444.123456789;
    var q2: Float = 4444.123456789F;
    Log.d("DDINFO","q = $q  q2 = $q2 qf = $dec.format(q) qf2 = $dec.format(q2)")
    dao.insert(DropsDeposit(quantity = q /*DOUBLE*/,quantity2 = q2 /*FLOAT*/))
    for (d: DropsDeposit in dao.getAllDropsDeposit()) 
        Log.d("DDINFO","q = $d.quantity  q2 = $d.quantity2 qf = $dec.format(d.quantity) qf2 = $dec.format(d.quantity2)")
    

然后:-

数据库(通过 android Studio 的 Database Inspector)显示:-

也就是说,数据存储在数据库中的精度比您所经历的要高(NUMERIC 不会改变这一点)。但是,您会注意到 Float 的 (quantity2) 精度仅为 4dp,并且第 4 位已从 4 舍入到 5。我建议不是 SQLite,而是因为 Float。

现在输出到日志:-

第一行是在插入任何数据之前,是要存储的值。在存储数据之前表明您面临的问题存在。

D/DDINFO: q = 4444.123456789  q2 = 4444.1235 qf = 4444.1234567890 qf2 = 4444.1235351562

与数据的存储方式一致,通过提取数据的输出是相同的

D/DDINFO: q = 4444.123456789  q2 = 4444.1235 qf = 4444.1234567890 qf2 = 4444.1235351562
q,Double,不带格式打印也可以。 q2,浮点,不带格式打印为 4 dp 并四舍五入。 qf, Formatted Double, 除了额外的尾随 0 来满足格式的精度之外很好 qf2,格式化浮点数,根据数据格式化打印。

如果代码被修改为在循环之前添加以下内容:-

    q = q + 0.000000000001  - q + 0.9
    q2 = q2 +  0.00001f - q2 + 0.9f
    Log.d("DDINFO","q = $q  q2 = $q2 qf = $dec.format(q) qf2 = $dec.format(q2)")
    dao.insert(DropsDeposit(quantity = q /*DOUBLE*/,quantity2 = q2 /*FLOAT*/))

那么在插入之前输出到日志的是:-

D/DDINFO: q = 0.9000000000009095  q2 = 0.9 qf = .9000000000 qf2 = .8999999762

数据库:-

提取后的输出与之前相同。

因此,您的问题不在于数据在数据库中的存储方式,而在于类型。 IE。 Float 的局限性。

【讨论】:

【参考方案2】:

我不知道你的数量有多大,但是如果在以下范围之间,你可以使用ULong(范围:0到2^64 -1)。

More info 关于基本类型。

更新

如果您必须保持小数输入,请使用 Double。它的范围也与 Long 相同。

【讨论】:

以上是关于如何在 Room 中声明 NUMERIC 字段的主要内容,如果未能解决你的问题,请参考以下文章

Android Persistence room:“无法弄清楚如何从光标读取此字段”

如何定制Numeric属性/字段验证消息

Android Room - 带有附加字段的多对多关系

如何在 PostgreSQL 中将长 NUMERIC 整数转换为位字符串?

GeoPoint 不允许存储在 Room 中

姜戈。如何在查询结果中添加字段?