【问题标题】:Problem in TypeConverters In Room Database房间数据库中 TypeConverters 的问题
【发布时间】:2022-11-11 09:06:38
【问题描述】:

我正在尝试在 Android (Kotlin) 中使用类型转换器,所以我正在使用类型转换器类,但我很困惑,就像在云内部一样,我只有一个变量,所以我已经返回它但是

@Entity(tableName = "WeatherDb")
data class WeatherDTO(
    val base: String,
    val clouds: Clouds,
    val cod: Int,
    val coord: Coord,
    val dt: Int,
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    val main: Main,
    val name: String,
    val sys: Sys,
    val timezone: Int,
    val visibility: Int,
    val weather: List<Weather>,
    val wind: Wind
)

class TypeConverters {
    @TypeConverter
    fun fromCloudsToDouble(clouds: Clouds): Int {
        return clouds.all
    }

    fun fromCoordToDouble(coord: Coord): Double {

    }
}

在 coord 类中,这里有多个具有不同数据类型的如何隐藏这个?

data class Main(
    val feels_like: Double,
    val grnd_level: Int,
    val humidity: Int,
    val pressure: Int,
    val sea_level: Int,
    val temp: Double,
    val temp_max: Double,
    val temp_min: Double
)

【问题讨论】:

  • 在 RoomDatabase 中用于链接多个数据类,您应该使用 FOREIGN 键的概念,或者只在 WeatherDTO 类中创建一个字段 uniqueKey 并将其在 Coord 类中的值传递给其所有关联值
  • @KartikAgarwal 你能详细说明一下我对这个房间数据库很陌生,你能分享代码或任何文章或 yt 视频以供参考吗?

标签: android android-studio android-room typeconverter data-class


【解决方案1】:

这是我在 Kotlin 中的转换器:

class Converters {

    @TypeConverter
    fun valueFromDomainToStorage(value: Value): String {
        return value.convertToJson()
    }

    @TypeConverter
    fun valueFromStorageToDomain(str: String): Value {
        // we can not create an empty instance of value as TypeDecoder.java should call non-empty constructor
        return Value(
            "just a stub",
            BigInteger.valueOf(0),
            BigInteger.valueOf(0),
            false,
            BigInteger.valueOf(0)
        )
            .fromJson(str)
    }
}

其中.convertToJson().fromJson(str) 实现为Value 类中的扩展:

fun Value.convertToJson(): String {
    val result = JSONObject()
    result.put(ValueConst.OFFER_FIELD, offer)
    result.put(ValueConst.AVAILABLE_SINCE, availableSince.toLong())
    result.put(ValueConst.AVAILABLE_END, availabilityEnd.toLong())
    result.put(ValueConst.IS_CONSUMED, isConsumed)
    result.put(ValueConst.LOCKED_UNTIL, lockedUntil)
    return result.toString()
}

fun Value.fromJson(json: String): Value {
    val subj = JSONObject(json)
    return Value(
        subj.optString(ValueConst.OFFER_FIELD),
        BigInteger.valueOf(subj.optLong(ValueConst.AVAILABLE_SINCE)),
        BigInteger.valueOf(subj.optLong(ValueConst.AVAILABLE_END)),
        subj.optBoolean(ValueConst.IS_CONSUMED),
        BigInteger.valueOf(subj.optLong(ValueConst.LOCKED_UNTIL))
    )
}

您应该为每个非本地类类型实现Converter 类。不要忘记在数据库中注册您的转换器:

@Database(entities = [ChainTransaction::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase: RoomDatabase() {

当您编译代码并稍后引入新的更改时,您还必须增加version 参数才能使更改生效:

@Database(entities = [ChainTransaction::class], version = 2, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase: RoomDatabase() {

这是官方文档,甚至是有关此主题的培训: https://developer.android.com/training/data-storage/room

【讨论】:

    【解决方案2】:

    所以我正在使用类型转换器类,但我很困惑

    SQLite(Room 是面向对象包装器的数据库)不是面向对象(或感知)的数据库。它是一个数据库,可以存储原始类型的数据,这些数据是

    • INTEGER(例如 Int 或 Long)、REAL
    • REAL(例如 Float 或 Double)
    • 文本(如字符串)
    • BLOB(如 ByteArray)

    因此,要存储一种坐标、云或天气 .... 您有三个选项:-

    1. 嵌入类,在这种情况下,从嵌入类复制字段(如果嵌入类包含不受支持的类型,则会很复杂)。未包含在答案中
    2. 将类本身作为一个表,并在其与父级 (WeatherDTO) 之间建立关系。未包含在答案中
    3. 将类转换为一种 SQLite 类型(其中 TEXT 或 BLOB 可能只适用)。

      考虑到选项 3 (TyepConverters) 转换数据很少,如果有的话,只使用存储数据,因为您将无法检索数据。

      因此,类型转换器应始终配对。

      • 其中之一是将类转换为可存储的类型。
      • 另一个是将存储类型转换为类。

      因此,您将需要相当多的类型转换器,即每个字段 2 个:-

      • 云(云类)
      • 坐标(坐标类)
      • main(主类)
      • sys(系统类)
      • 天气(类列表)
      • 风(风类)

      Room 查看字段的 Class 以定位相应的类型转换器。

      转换对象(又名类)的最简单方法之一是将对象转换为 JSON 表示。尽管这样做的复杂性在于有许多 JSON 库,并且它们通常会有所不同。

      对于遵循 Google 的 JSON 库的示例,已使用。但是,将此库与 Room 一起使用似乎不直接支持使用 List<the_class> 例如列表。

      • 对此的依赖(例如)implementation 'com.google.code.gson:gson:2.10'

      作为一个新的班级WeatherList已按以下方式使用:-

      data class WeatherList(
          val weatherList: List<Weather>
      )
      

      并且 WeatherDTO 类已更改为按照以下方式使用它:-

      ....
      //val weather: List<Weather>,
      val weather: WeatherList,
      ....
      

      因此,TypeConverters 类可以是:-

      class TypeConverters {
          @TypeConverter
          fun fromCloudsToJSONString(clouds: Clouds): String = Gson().toJson(clouds)
          @TypeConverter
          fun toCloudsFromJSONString(jsonString: String): Clouds = Gson().fromJson(jsonString,Clouds::class.java)
          @TypeConverter
          fun fromCoordToJSONString(coord: Coord): String = Gson().toJson(coord)
          @TypeConverter
          fun toCoordFromJSONString(jsonString: String): Coord = Gson().fromJson(jsonString,Coord::class.java)
          @TypeConverter
          fun fromMaintoJSONString(main: Main): String = Gson().toJson(main)
          @TypeConverter
          fun toMainFromJSONString(jsonString: String): Main = Gson().fromJson(jsonString,Main::class.java)
          @TypeConverter
          fun fromSysToJSONString(sys: Sys): String = Gson().toJson(sys)
          @TypeConverter
          fun toSysFromJSONString(jsonString: String): Sys = Gson().fromJson(jsonString,Sys::class.java)
          @TypeConverter
          fun fromWeatherListFromJSONString(weatherList: WeatherList): String = Gson().toJson(weatherList)
          @TypeConverter
          fun toWeatherListFromJSOnString(jsonString: String): WeatherList = Gson().fromJson(jsonString,WeatherList::class.java)
          @TypeConverter
          fun fromWindToJSONString(wind: Wind): String = Gson().toJson(wind)
          @TypeConverter
          fun toWindFromJSONString(jsonString: String): Wind = Gson().fromJson(jsonString,Wind::class.java)
      }
      

      因此,不直接支持的所有类型/类/对象都将转换为/从类型/类/对象的 JSON 字符串表示形式转换。

      请注意,您需要添加@TypeConverters(@TypeConverters( value = [&lt;????&gt;.TypeConverters::class])。哪里必须区分你的项目 TypeConverters 类和 Room 的类(TypeConverters 可能不是该类的最佳名称,重命名它会克服区分的需要)

      工作示例

      下面将上述内容付诸实施。

      由于问题不包括基础类,因此使用了以下内容:-

      data class Coord(
          val longitude: Double,
          val latitude: Double
      )
      data class Clouds(
          val cover: Double,
          val type: String
      )
      data class Main(
          val main: Double
      )
      data class Sys(
          val sys: Double
      )
      data class WeatherList(
          val weatherList: List<Weather>
      )
      data class Weather(
          val weather: Double
      )
      data class Wind(
          val wind: Double
      )
      

      @Dao 注解的接口也是编出来的,简单来说就是:-

      @Dao
      interface AllDao {
          @Insert(onConflict = OnConflictStrategy.IGNORE)
          fun insert(weatherDTO: WeatherDTO)
          @Query("SELECT * FROM weatherdb")
          fun getAllFromWeatherDB(): List<WeatherDTO>
      }
      

      @Database 带注释的抽象类也由它组成:-

      @TypeConverters( value = [a.a.so74384736typeconverterconfusion.TypeConverters::class])
      @Database(entities = [WeatherDTO::class], exportSchema = false, version = 1)
      abstract class TheDatabase: RoomDatabase() {
          abstract fun getAllDao(): AllDao
      
          companion object {
              private var instance: TheDatabase? = null
              fun getInstance(context: Context): TheDatabase {
                  if (instance==null) {
                      instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
                          .allowMainThreadQueries()
                          .build()
                  }
                  return instance as TheDatabase
              }
          }
      }
      
      • 注意用于区分 TypeConverters 类和 Room 的 TypeConverters 类的包名

      • 包名不能在其他地方使用,所以如果上面是复制的,那么它就必须改变。不期望整个代码会被复制和使用。该代码仅用于演示 TypeConverters。

      最后一些活动代码来实际做某事(存储和检索一些数据):-

      class MainActivity : AppCompatActivity() {
      
          lateinit var db: TheDatabase
          lateinit var dao: AllDao
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
      
              db = TheDatabase.getInstance(this)
              dao = db.getAllDao()
      
              dao.insert(
                  WeatherDTO(
                  "base001",
                  Clouds(25.5,"cumulus"),10,
                      Coord(10.567,30.345),
                      11,
                      12,
                      Main(12345.67890),
                      "thename",
                      Sys(9.87654321),
                      14,
                      1000,
                      WeatherList(listOf(Weather(5.1234),Weather(6.5432), Weather(7.6543))),
                      Wind(23.12)
                  )
              )
              for (wdto in dao.getAllFromWeatherDB()) {
                  Log.d("DBINFO","base = ${wdto.base} longitude = ${wdto.coord.longitude} latitude = ${wdto.coord.latitude} etc ....")
              }
          }
      }
      

      结果

      运行日志时,如预期的那样:-

      D/DBINFO: base = base001 longitude = 10.567 latitude = 30.345 etc ....
      

      使用 App Inspection 然后数据库看起来像:-

      • 转换为 JSON 字符串的字段已突出显示。
      • 很明显,由于类的原因,数据很可能与您预期的不完全一样。

    【讨论】:

    • 谢谢这就是我真正想要的
    • @Jadu 这就是我的猜测。我添加了一个后续内容,涵盖了您可能希望考虑的@Embedded 的基本用法。
    【解决方案3】:

    继续上一个答案@Embedded 与类型转换器

    从前面的回答可以看出,在使用 TypeConverters 方面存在一些问题。从数据库的角度来看,TypeConverters 将不可避免地包含与规范化相反的膨胀/不必要的数据(而不是不必要地存储重复数据)。

    例如,每行的 JSON 表示将包含完全相同的字段名称浪费存储,所有行都将具有存储分隔符的额外开销([s 和 ]s、{s 和 }年代,:s ,s)。此外,由于膨胀以及多个值存储在单个列中,实际使用存储的数据可能会变得复杂,因此可能会受到限制。

    不存储膨胀会更有效,它可以消除复杂性并从数据库的角度增强存储数据的可用性(查询数据以进行检索),而不是在单个列中存储多个值。

    使用@Embedded 注释可以很容易地消除臃肿。考虑以下(WeatherDTO 类/实体的替代版本):-

    @Entity(tableName = "WeatherDbAlternative1")
    data class WeatherDTOAlternative1(
        val base: String,
        @Embedded
        val clouds: Clouds,
        val cod: Int,
        @Embedded
        val coord: Coord,
        val dt: Int,
        @PrimaryKey(autoGenerate = true)
        val id: Int,
        @Embedded
        val main: Main,
        val name: String,
        @Embedded
        val sys: Sys,
        val timezone: Int,
        val visibility: Int,
        //val weather: List<Weather>,
        /* Unable to embed directly so not embedding */
        val weather: WeatherList,
        @Embedded
        val wind: Wind
    )
    

    在天气字段栏上所做的就是添加 @Embedded 注释。注意到字段的类都有Room直接支持的类型字段。

    将此实体添加到 @Database 注释中,并在 @Dao 注释类中添加几个附加函数,如下所示:-

    @Query("SELECT * FROM weatherdbalternative1")
    fun getAllFromWeatherDBAlternative1(): List<WeatherDTOAlternative1>
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(weatherDTOAlternative1: WeatherDTOAlternative1)
    

    然后修改活动代码以包括:-

        /*ALTERNATIVE 1 All but WeatherList embedded */
        dao.insert(
            WeatherDTOAlternative1(
                "base001A",
                Clouds(25.5, "cumulus"),
                10,
                Coord(10.567, 30.345),
                11,
                12,
                Main(12345.67890),
                "thenameA1",
                Sys(9.87654321),
                14,
                1000,
                WeatherList(listOf(Weather(5.1234), Weather(6.5432), Weather(7.6543))),
                Wind(23.12)
            )
        )
        for (wdto in dao.getAllFromWeatherDBAlternative1()) {
            Log.d(
                "DBINFO",
                "base = ${wdto.base} longitude = ${wdto.coord.longitude} latitude = ${wdto.coord.latitude} etc ...."
            )
        }
    

    现在导致日志包括:-

    D/DBINFO: base = base001 longitude = 10.567 latitude = 30.345 etc ....
    D/DBINFO: base = base001A longitude = 10.567 latitude = 30.345 etc ....
    
    • 即有效地存储和检索相同的数据

    但是,数据现在以(忽略天气字段)的形式存储在数据库中:-

    • 即存储的数据更干净,但以额外的列为代价(这可能是有利的)。
    • 此外,虽然不明显,但具有 @Embedded 注释的字段不需要 TypeConverters。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-24
      相关资源
      最近更新 更多