Android 房间持久库 - 如何插入具有列表对象字段的 class
Android room persistent library - how to insert class that has a List object field
在 Android room persistent library 中如何将整个模型对象插入到 table 中,它本身有另一个列表。
让我告诉你我的意思:
@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) 错误:无法确定如何将此字段保存到数据库中。可以考虑为其添加类型转换器
应该有另一个 table 叫做 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"
}
]
对于每个国家/地区,因此这里不会超过一项。我很乐意 table 为 country_lang 列表中的一项设计它。所以我可以为 country_lang 制作一个 table,然后将其 link 制作成 CountryModel。但如何?我可以使用外键吗?我希望我不必使用平面文件。所以你说我必须将它存储为 json ?是否建议不要临时使用房间?用什么代替?
你不能。
实现此目的的唯一方法是使用 @ForeignKey constraint. If you want to still keep the list of object inside your parent POJO, you have to use @Ignore or provide a @TypeConverter
要了解更多信息,请关注此博客 post:-
https://www.bignerdranch.com/blog/room-data-storage-on-android-for-everyone/
和示例代码:-
https://github.com/googlesamples/android-architecture-components
正如 Omkar 所说,你不能。在这里,我描述了为什么你应该根据文档始终使用 @Ignore
注释:https://developer.android.com/training/data-storage/room/referencing-data.html#understand-no-object-references
您将 table 中的 Country 对象视为仅检索其权限的数据; Languages 对象将转到另一个 table 但您可以保留相同的 Dao:
- Countries和Languages对象是独立的,只需在Language实体(countryId, languageId)中定义primaryKey多字段即可。当活动线程是 Worker 线程时,您可以将它们串联保存在存储库中 class:两次插入 Dao 的请求。
- 要加载 Countries 对象,您需要 countryId。
- 要加载相关的语言对象,您已经拥有国家/地区 ID,但您需要先等待国家/地区加载,然后再加载语言,以便您可以在父对象中设置它们 return 仅限父对象。
- 加载国家/地区时,您可以在存储库 class 中连续执行此操作,因此您将同步加载国家/地区,然后加载语言,就像在服务器端所做的那样! (没有 ORM 库)。
您可以使用 TypeConverter 和 GSON,
轻松插入带有列表对象字段的 class
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 class
@Database(entities = {CountryModel.class}, version = 1)
@TypeConverters({DataConverter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract CountriesDao countriesDao();
}
有关 Room 中的 TypeConverters 的更多信息,请查看我们的博客 here and the official docs。
这是 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 class
@Database(entities = arrayOf(CountryModel::class), version = 1)
@TypeConverters(DataConverter::class)
abstract class AppDatabase : RoomDatabase(){
abstract fun countriesDao(): CountriesDao
}
为自定义对象字段添加@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 {
....
}
我也遇到过类似的情况。为了解决这个问题,我使用 TypeConverts 和 Moshi 将列表解析为字符串。
按照以下步骤操作:
1 - 创建带有转换器的 class。
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 class.
中使用转换器定义 class
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {...}
...you add the @TypeConverters annotation to the AppDatabase class so that Room can use the converter that you've defined for each entity and DAO in that AppDatabase...
...sometimes, your app needs to use a custom data type whose value you would like to store in a single database column. To add this kind of support for custom types, you provide a TypeConverter, which converts a custom class to and from a known type that Room can persist.
参考文献:
How to parse a list? #78 (answered by Jake Wharton)
我做了类似于@Daniel Wilson 的事情,但是,我使用了 Moshi since it is the suggested library. To learn more about the difference between Moshi and Gson I suggest you watch this video。
在我的例子中,我必须在 Room
数据库中存储一个 List<LatLng>
。如果您不知道 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)
}
}
在 Android room persistent library 中如何将整个模型对象插入到 table 中,它本身有另一个列表。
让我告诉你我的意思:
@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) 错误:无法确定如何将此字段保存到数据库中。可以考虑为其添加类型转换器
应该有另一个 table 叫做 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"
}
]
对于每个国家/地区,因此这里不会超过一项。我很乐意 table 为 country_lang 列表中的一项设计它。所以我可以为 country_lang 制作一个 table,然后将其 link 制作成 CountryModel。但如何?我可以使用外键吗?我希望我不必使用平面文件。所以你说我必须将它存储为 json ?是否建议不要临时使用房间?用什么代替?
你不能。
实现此目的的唯一方法是使用 @ForeignKey constraint. If you want to still keep the list of object inside your parent POJO, you have to use @Ignore or provide a @TypeConverter
要了解更多信息,请关注此博客 post:-
https://www.bignerdranch.com/blog/room-data-storage-on-android-for-everyone/
和示例代码:-
https://github.com/googlesamples/android-architecture-components
正如 Omkar 所说,你不能。在这里,我描述了为什么你应该根据文档始终使用 @Ignore
注释:https://developer.android.com/training/data-storage/room/referencing-data.html#understand-no-object-references
您将 table 中的 Country 对象视为仅检索其权限的数据; Languages 对象将转到另一个 table 但您可以保留相同的 Dao:
- Countries和Languages对象是独立的,只需在Language实体(countryId, languageId)中定义primaryKey多字段即可。当活动线程是 Worker 线程时,您可以将它们串联保存在存储库中 class:两次插入 Dao 的请求。
- 要加载 Countries 对象,您需要 countryId。
- 要加载相关的语言对象,您已经拥有国家/地区 ID,但您需要先等待国家/地区加载,然后再加载语言,以便您可以在父对象中设置它们 return 仅限父对象。
- 加载国家/地区时,您可以在存储库 class 中连续执行此操作,因此您将同步加载国家/地区,然后加载语言,就像在服务器端所做的那样! (没有 ORM 库)。
您可以使用 TypeConverter 和 GSON,
轻松插入带有列表对象字段的 classpublic 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 class
@Database(entities = {CountryModel.class}, version = 1)
@TypeConverters({DataConverter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract CountriesDao countriesDao();
}
有关 Room 中的 TypeConverters 的更多信息,请查看我们的博客 here and the official docs。
这是 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 class
@Database(entities = arrayOf(CountryModel::class), version = 1)
@TypeConverters(DataConverter::class)
abstract class AppDatabase : RoomDatabase(){
abstract fun countriesDao(): CountriesDao
}
为自定义对象字段添加@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 {
....
}
我也遇到过类似的情况。为了解决这个问题,我使用 TypeConverts 和 Moshi 将列表解析为字符串。
按照以下步骤操作:
1 - 创建带有转换器的 class。
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 class.
中使用转换器定义 class@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {...}
...you add the @TypeConverters annotation to the AppDatabase class so that Room can use the converter that you've defined for each entity and DAO in that AppDatabase...
...sometimes, your app needs to use a custom data type whose value you would like to store in a single database column. To add this kind of support for custom types, you provide a TypeConverter, which converts a custom class to and from a known type that Room can persist.
参考文献:
How to parse a list? #78 (answered by Jake Wharton)
我做了类似于@Daniel Wilson 的事情,但是,我使用了 Moshi since it is the suggested library. To learn more about the difference between Moshi and Gson I suggest you watch this video。
在我的例子中,我必须在 Room
数据库中存储一个 List<LatLng>
。如果您不知道 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)
}
}