我该如何在ROOM数据库中存储货币?

14

我有一个实体:

@Entity(tableName = "products")
public class Product{

    @PrimaryKey(autoGenerate = true)
    private Long id;

    @ColumnInfo(name = "amount")
    private BigDecimal amount;

我需要将它存储在Room DB中。我无法存储BigDecimal,因此我创建了转换器:

public static class Converters {
        @TypeConverter
        public BigDecimal fromString(String value) {
            return value == null ? null : new BigDecimal(value);
        }

        @TypeConverter
        public Double amountToString(BigDecimal bigDecimal) {
            if (bigDecimal == null) {
                return null;
            } else {
                return bigDecimal.doubleValue();
            }
        }
    }

并添加到列中:

@TypeConverters(Converters.class)
    @ColumnInfo(name = "amount")
    private BigDecimal amount;

现在我将货币存储在双列中。我想要一个总结totalcurrency的方法:

@Query("SELECT SUM(amount) FROM products")
LiveData<Double> totalSum();

但我认为这是一种不好的方法,因为在转换时可能会丢失一些值。

我的问题:如何在ROOM DB中存储货币并返回方法?

LiveData<BigDecimal> totalSum();
3个回答

13

我使用StringBigDecimal之间的转换,而不是LongBigDecimal之间的转换。

警告:这种方式无法在Room DB中使用SUMTOTAL或其他表达式。因为我的应用程序旨在使用多种货币,所以我手动检索数据并基于当前汇率进行计算。

class BigDecimalTypeConverter {

    @TypeConverter
    fun bigDecimalToString(input: BigDecimal?): String {
        return input?.toPlainString() ?: ""
    }

    @TypeConverter
    fun stringToBigDecimal(input: String?): BigDecimal {
        if (input.isNullOrBlank()) return BigDecimal.valueOf(0.0)
        return input.toBigDecimalOrNull() ?: BigDecimal.valueOf(0.0)
    }

}

如果你只有一种货币,你可以使用Double代替String

class BigDecimalDoubleTypeConverter {

    @TypeConverter
    fun bigDecimalToDoubleinput: BigDecimal?): Double {
        return input?.toDoubleOrNull() ?: 0.0
    }

    @TypeConverter
    fun stringToBigDecimal(input: Double?): BigDecimal {
        if (input == null) return BigDecimal.ZERO
        return BigDecimal.valueOf(input) ?: BigDecimal.ZERO
    }

}

所以你可以从Dao中检索SUMTOTAL等内容:

@Query("SELECT SUM(transactions.amounts) FROM transactions WHERE transactions.timestamp >= :fromDateTimestamp")
fun getTotalAmount(fromDateTimestamp: Long): Double

使用 TypeConverter 处理您的数据库:

@Database(...)
@TypeConverters(BigDecimalTypeConverter::class)
internal abstract class AppDatabase : RoomDatabase() {
   ...
}

3
在你使用double的第二种解决方案中,它是否保留了数字的精确数量?我的意思是,许多最佳实践禁止将Double用作货币类型,因为它没有小数分数,而是具有二进制分数。 - Hossein Farrokhi

10

您可以将值存储在 long 类型中。例如,如果您有 1.55 的值在序列化时需要乘以 100 并将其存储为 long 类型在数据库中。在反序列化时从该 long 类型创建 BigDecimal 对象,并将其除以 100。

 public static class Converters {
        @TypeConverter
        public BigDecimal fromLong(Long value) {
            return value == null ? null : new BigDecimal(value).divide(new BigDecimal(100));
        }

        @TypeConverter
        public Long toLong(BigDecimal bigDecimal) {
            if (bigDecimal == null) {
                return null;
            } else {
                return bigDecimal.multiply(new BigDecimal(100)).longValue();
            }
        }
    }

2
我喜欢这个解决方案,但不幸的是它没有考虑到那些货币小数位数不同的国家。请参考这个 Stack Overflow 的问题(忽略它与 iPhone 开发相关的事实):(https://dev59.com/kXE85IYBdhLWcg3wmEpV) - shagberg

1

@ip696 你可以尝试将 BigDecimal 转换为 Double,而不是将其转换为 String。

@TypeConverter
fun amountToDouble(input: BigDecimal?): Double? {
    return input?.toDouble()
}

@TypeConverter
fun fromDouble(input: Double?): BigDecimal? {
    return input?.toBigDecimal()
}

然后下面的代码将会生效。

@Query("SELECT SUM(amount) FROM products")
LiveData<Double> totalSum();

附加说明: ORDER BY 子句也会按预期工作。

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