【问题标题】:Android room persistent library - how to insert class that has a List object fieldAndroid 房间持久库 - 如何插入具有 List 对象字段的类
【发布时间】:2017-11-18 17:26:28
【问题描述】:

Android room persistent library 中,如何将整个模型对象插入到本身具有另一个列表的表中。

让我告诉你我的意思:

@Entity(tableName = TABLE_NAME)
public class CountryModel {

    public static final String TABLE_NAME = "Countries";

    @PrimaryKey
    private int idCountry;

    private List<CountryLang> countryLang = null;

    public int getIdCountry() {
        return idCountry;
    }

    public void setIdCountry(int idCountry) {
        this.idCountry = idCountry;
    }

    public String getIsoCode() {
        return isoCode;
    }

    public void setIsoCode(String isoCode) {
        this.isoCode = isoCode;
    }

    /** 
        here i am providing a list of coutry information how to insert 
        this into db along with CountryModel at same time 
    **/
    public List<CountryLang> getCountryLang() {
        return countryLang;
    }

    public void setCountryLang(List<CountryLang> countryLang) {
        this.countryLang = countryLang;
    }
}

我的 DAO 如下所示:

@Dao
public interface CountriesDao{

    @Query("SELECT * FROM " + CountryModel.TABLE_NAME +" WHERE isoCode =:iso_code LIMIT 1")
    LiveData<List<CountryModel>> getCountry(String iso_code);

    @Query("SELECT * FROM " + CountryModel.TABLE_NAME )
    LiveData<List<CountryModel>> getAllCountriesInfo();

    @Insert(onConflict = REPLACE)
    Long[] addCountries(List<CountryModel> countryModel);

    @Delete
    void deleteCountry(CountryModel... countryModel);

    @Update(onConflict = REPLACE)
    void updateEvent(CountryModel... countryModel);
}

当我调用database.CountriesDao().addCountries(countryModel); 时,我收到以下房间数据库编译错误: 错误:(58, 31) 错误:无法弄清楚如何将此字段保存到数据库中。您可以考虑为其添加类型转换器。

应该有另一个名为 CountryLang 的表吗?如果是这样,如何告诉空间在插入语句中连接它们?

CountryLang 对象本身如下所示:

public class CountryLang {


    private int idCountry;

    private int idLang;

    private String name;

    public int getIdCountry() {
        return idCountry;
    }

    public void setIdCountry(int idCountry) {
        this.idCountry = idCountry;
    }

    public int getIdLang() {
        return idLang;
    }

    public void setIdLang(int idLang) {
        this.idLang = idLang;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

响应如下所示:

"country_lang": [
      {
        "id_country": 2,
        "id_lang": 1,
        "name": "Austria"
      }
    ]

对于每个国家/地区,因此这里不会超过一项。我很乐意为 country_lang 列表中的一项设计它。所以我可以为 country_lang 制作一个表格,然后将其链接到 CountryModel。但如何?我可以使用外键吗?我希望我不必使用平面文件。所以你说我必须将它存储为 json ?是否建议不要临时使用房间?改用什么?

【问题讨论】:

  • 忘记了 temporaray 的空间,那么您想在 CountryModel 中以哪种格式存储 countryLang 字段?是否要以逗号分隔存储?
  • 如果您确定只有一项,那么您应该使用对象而不是数组。因为如果您使用对象,您可以使用 @Embedded 注释轻松地将 countryLang 类嵌入 CountryModel 中
  • 如果你不能改变json那么你应该使用外键关系。
  • 感谢您的回复。关于@embedded 的好主意。你知道我是否可以使用嵌入列表吗?这个模型来自一个休息服务响应。它以 json 形式返回,然后我使用 gson 将其转换为 java 对象。所以我不必将其保留为 List 类型。正如您在我关于 country_lang 结构的声明中所见,json 将其作为列表对象。在这种情况下如何使用外键。
  • 可以使用@Relation注解。查看this answer

标签: android android-room


【解决方案1】:

您可以使用 TypeConverterGSON 轻松插入带有列表对象字段的类,

public class DataConverter {

    @TypeConverter
    public String fromCountryLangList(List<CountryLang> countryLang) {
        if (countryLang == null) {
            return (null);
        }
        Gson gson = new Gson();
        Type type = new TypeToken<List<CountryLang>>() {}.getType();
        String json = gson.toJson(countryLang, type);
        return json;
    }

    @TypeConverter
    public List<CountryLang> toCountryLangList(String countryLangString) {
        if (countryLangString == null) {
            return (null);
        }
        Gson gson = new Gson();
        Type type = new TypeToken<List<CountryLang>>() {}.getType();
        List<CountryLang> countryLangList = gson.fromJson(countryLangString, type);
        return countryLangList;
    }
 }

接下来,将@TypeConverters 注解添加到 AppDatabase 类中

    @Database(entities = {CountryModel.class}, version = 1)
    @TypeConverters({DataConverter.class})
    public abstract class AppDatabase extends RoomDatabase {
      public abstract CountriesDao countriesDao();
    }

有关 TypeConverters in Room 的更多信息,请查看我们的博客 hereofficial docs

【讨论】:

  • @Chance 都是不同的东西。这是为了使用 typeconverter simple 在房间数据库中插入列表对象。而序列化是一种将对象的状态转换为字节流的机制。
  • 使用 TypeConverters 的解决方案应该只用于具有少量字段的小列表,在数百个复杂对象的情况下,TypeConverters 的性能与 ForeignKey 相比较差。
  • 这个解决方案不好的原因不仅仅是性能。您在这里真正要做的就是将对象展平为 JSON 字符串。您不能执行查询或做任何使 SQL 在扁平字符串上表现良好的事情。
  • 有没有办法可以将列表数据保存在单独的表中,而不是列中的字符串。因为我的列表有近1000项的大数据,也想查询这些数据。
  • @EdwardvanRaak 总是有例外,软件开发不依赖于单一的事实来源。因此,由于您指出的原因,此解决方案确实很糟糕,但在您不需要的另一种情况下可能会很好。
【解决方案2】:

这是 Aman Gupta 的 Kotlin 转换器,适合喜欢复制粘贴的懒惰 Google 员工:

class DataConverter {

    @TypeConverter
    fun fromCountryLangList(value: List<CountryLang>): String {
        val gson = Gson()
        val type = object : TypeToken<List<CountryLang>>() {}.type
        return gson.toJson(value, type)
    }

    @TypeConverter
    fun toCountryLangList(value: String): List<CountryLang> {
        val gson = Gson()
        val type = object : TypeToken<List<CountryLang>>() {}.type
        return gson.fromJson(value, type)
    }
}

另外,将@TypeConverters 注解添加到 AppDatabase 类

@Database(entities = arrayOf(CountryModel::class), version = 1)
@TypeConverters(DataConverter::class)
abstract class AppDatabase : RoomDatabase(){
    abstract fun countriesDao(): CountriesDao
}

【讨论】:

  • 您能否解释一下,为什么这个答案是解决问题的好方法?格森在做什么?这么大的库值得加吗?
  • Gson 用于自动将 POJO 序列化和反序列化为 Json。如果类型约束是一个问题,您可以将它们作为字符串传递。我认为大多数应用程序都使用 gson 或 moshi 而不加考虑。像 gson 这样的大小/方法限制在 2009 年可能是一个问题,但现在已经不是问题了。
  • 如果您打算存储列表并且不想使用类型令牌,您可以使用 Moshi,它在撰写本文时非常快,或者 Kotlin 序列化非常好。跨度>
  • TypeConverter的使用方法有多个版本。在我的情况下,这是最好的选择。谢谢!
【解决方案3】:

正如 Omkar 所说,你不能。在这里,我根据文档描述为什么应该始终使用@Ignore 注释:https://developer.android.com/training/data-storage/room/referencing-data.html#understand-no-object-references

您将处理表中的 Country 对象以仅检索其权限的数据; Languages 对象将转到另一个表,但您可以保留相同的 Dao:

  • Countries 和Languages 对象是独立的,只需在Language 实体(countryId,languageId)中定义primaryKey 多字段即可。当活动线程为 Worker 线程时,您可以将它们串联保存在 Repository 类中:对 Dao 的两次插入请求。
  • 要加载 Country 对象,您需要使用 countryId。
  • 要加载相关的 Languages 对象,您已经拥有 countryId,但您需要先加载该国家/地区,然后再加载语言,以便您可以在父对象中设置它们并仅返回父对象。
  • 当您加载国家/地区时,您可能可以在 Repository 类中连续执行此操作,因此您将同步加载国家/地区,然后加载语言,就像您在服务器端所做的那样! (没有 ORM 库)。

【讨论】:

  • 你刚才说的似乎是合法的,但我一个字都听不懂,你能分享一些示例链接
【解决方案4】:

你不能。

实现这一点的唯一方法是使用@ForeignKey 约束。如果您仍想将对象列表保留在父 POJO 中,则必须使用 @Ignore 或提供 @TypeConverter

更多信息,请关注这篇博文:-

https://www.bignerdranch.com/blog/room-data-storage-on-android-for-everyone/

和示例代码:-

https://github.com/googlesamples/android-architecture-components

【讨论】:

  • 在插入主实体时我们是否必须手动创建嵌入式对象..我阅读了其他答案之一..您打开了一个错误,现在修复了吗?
  • 其实可以。我可以使用@TypeConverters 注释确认@Aman 的答案对我有用
【解决方案5】:

我也遇到过类似的情况。为了解决这个问题,我使用 TypeConvertsMoshi 将列表解析为字符串。

按照以下步骤操作:

1 - 创建一个带有转换器的类。

class Converters {

    private val moshi = Moshi.Builder().build()
    private val listMyData : ParameterizedType = Types.newParameterizedType(List::class.java, MyModel::class.java)
    private val jsonAdapter: JsonAdapter<List<MyModel>> = moshi.adapter(listMyData)

    @TypeConverter
    fun listMyModelToJsonStr(listMyModel: List<MyModel>?): String? {
        return jsonAdapter.toJson(listMyModel)
    }

    @TypeConverter
    fun jsonStrToListMyModel(jsonStr: String?): List<MyModel>? {
        return jsonStr?.let { jsonAdapter.fromJson(jsonStr) }
    }
}

2 - 在 RoomDatabase 类中使用转换器定义类。

@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {...}

...您将 @TypeConverters 注释添加到 AppDatabase 类,以便 Room 可以使用您为该 AppDatabase 中的每个实体和 DAO 定义的转换器...

...有时,您的应用需要使用您希望将其值存储在单个数据库列中的自定义数据类型。要为自定义类型添加这种支持,您需要提供一个 TypeConverter,它将自定义类与 Room 可以持久存在的已知类型相互转换。

参考资料:

Moshi Parsing List (Kotlin)

How to parse a list? #78 (answered by Jake Wharton)

Use type converters (official documentation)

Moshi library

【讨论】:

    【解决方案6】:

    我做了类似于@Daniel Wilson 的事情,但是,我使用了Moshi,因为它是建议的库。要了解更多关于 Moshi 和 Gson 之间的区别,我建议您观看this video

    就我而言,我必须在Room 数据库中存储一个List&lt;LatLng&gt;。如果您不知道LatLng 用于处理地理坐标,即纬度和经度。 为此,我使用了以下代码:

    class Converters {
    
        private val adapter by lazy {
            val moshi = Moshi.Builder()
                .add(KotlinJsonAdapterFactory())
                .build()
            val listMyData = Types.newParameterizedType(List::class.java, LatLng::class.java)
            return@lazy moshi.adapter<List<LatLng>>(listMyData)
        }
    
        @TypeConverter
        fun toJson(coordinates: List<LatLng>) : String {
            val json = adapter.toJson(coordinates)
            return json
        }
    
        @TypeConverter
        fun formJson(json: String) : List<LatLng>? {
            return adapter.fromJson(json)
        }
    }
    

    【讨论】:

      【解决方案7】:

      为自定义对象字段添加@Embedded(参考以下示例)

      //this class refers to pojo which need to be stored
      @Entity(tableName = "event_listing")
      public class EventListingEntity implements Parcelable {
      
          @Embedded  // <<<< This is very Important in case of custom obj 
          @TypeConverters(Converters.class)
          @SerializedName("mapped")
          public ArrayList<MappedItem> mapped;
      
          //provide getter and setters
          //there should not the duplicate field names
      }
      
      //add converter so that we can store the custom object in ROOM database
      public class Converters {
          //room will automatically convert custom obj into string and store in DB
          @TypeConverter
          public static String 
          convertMapArr(ArrayList<EventListingEntity.MappedItem> list) {
          Gson gson = new Gson();
          String json = gson.toJson(list);
          return json;
          }
      
        //At the time of fetching records room will automatically convert string to 
        // respective obj
        @TypeConverter
        public static ArrayList<EventsListingResponse.MappedItem> 
        toMappedItem(String value) {
          Type listType = new 
           TypeToken<ArrayList<EventsListingResponse.MappedItem>>() {
          }.getType();
          return new Gson().fromJson(value, listType);
        }
      
       }
      
      //Final db class
      @Database(entities = {EventsListingResponse.class}, version = 2)
      @TypeConverters({Converters.class})
      public abstract class AppDatabase extends RoomDatabase {
          ....
      }
      

      【讨论】:

      • 不愉快的流程(空或空字符串,空列表)不被管理
      猜你喜欢
      • 1970-01-01
      • 2018-03-02
      • 2018-01-22
      • 2017-11-08
      • 1970-01-01
      • 2011-05-13
      • 2020-11-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多